File Coverage

blib/lib/Perl/Metrics/Halstead.pm
Criterion Covered Total %
statement 89 89 100.0
branch 10 12 83.3
condition 4 8 50.0
subroutine 21 21 100.0
pod 2 3 66.6
total 126 133 94.7


line stmt bran cond sub pod time code
1             package Perl::Metrics::Halstead;
2             our $AUTHORITY = 'cpan:GENE';
3              
4             # ABSTRACT: Compute Halstead complexity metrics
5              
6             our $VERSION = '0.0616';
7              
8 1     1   862 use PPI::Document;
  1         75624  
  1         32  
9 1     1   345 use PPI::Dumper;
  1         716  
  1         23  
10              
11 1     1   387 use Moo;
  1         8390  
  1         5  
12 1     1   1360 use strictures 2;
  1         1154  
  1         30  
13 1     1   498 use namespace::clean;
  1         8612  
  1         5  
14              
15              
16             has file => (
17             is => 'ro',
18             required => 1,
19             );
20              
21              
22             has [qw(
23             n_operators
24             n_operands
25             n_distinct_operators
26             n_distinct_operands
27             )] => (
28             is => 'ro',
29             init_arg => undef,
30             );
31              
32              
33             has [qw(
34             prog_vocab
35             prog_length
36             est_prog_length
37             volume
38             difficulty
39             level
40             lang_level
41             intel_content
42             effort
43             time_to_program
44             delivered_bugs
45             )] => (
46             is => 'lazy',
47             init_arg => undef,
48             builder => 1,
49             );
50              
51             sub _build_prog_vocab {
52 2     2   2385 my ($self) = @_;
53 2         23 return $self->n_distinct_operators + $self->n_distinct_operands;
54             }
55              
56             sub _build_prog_length {
57 2     2   16 my ($self) = @_;
58 2         22 return $self->n_operators + $self->n_operands;
59             }
60              
61             sub _build_est_prog_length {
62 2     2   16 my ($self) = @_;
63 2         8 return $self->n_distinct_operators * _log2($self->n_distinct_operators)
64             + $self->n_distinct_operands * _log2($self->n_distinct_operands);
65             }
66              
67             sub _build_volume {
68 2     2   16 my ($self) = @_;
69 2         24 return $self->prog_length * _log2($self->prog_vocab);
70             }
71              
72             sub _build_difficulty {
73 5     5   1979 my ($self) = @_;
74 5         55 return ($self->n_distinct_operators / 2)
75             * ($self->n_operands / $self->n_distinct_operands);
76             }
77              
78             sub _build_level {
79 2     2   16 my ($self) = @_;
80 2         24 return 1 / $self->difficulty;
81             }
82              
83             sub _build_lang_level {
84 2     2   464 my ($self) = @_;
85 2         25 return $self->volume / $self->difficulty / $self->difficulty;
86             }
87              
88             sub _build_intel_content {
89 2     2   1651 my ($self) = @_;
90 2         24 return $self->volume / $self->difficulty;
91             }
92              
93             sub _build_effort {
94 2     2   544 my ($self) = @_;
95 2         25 return $self->difficulty * $self->volume;
96             }
97              
98             sub _build_time_to_program {
99 2     2   510 my ($self) = @_;
100 2         25 return $self->effort / 18;
101             }
102              
103             sub _build_delivered_bugs {
104 2     2   480 my ($self) = @_;
105 2         24 return ($self->effort ** (2/3)) / 3000;
106             }
107              
108              
109             sub BUILD {
110 6     6 0 10856 my ( $self, $args ) = @_;
111              
112 6         35 my $doc = PPI::Document->new( $self->file );
113              
114 6         17338 my $dump = PPI::Dumper->new( $doc, whitespace => 0, comments => 0 );
115              
116 6 100       274 die 'No document parsed for ', $self->file, ". Computation can't continue.\n"
117             unless $dump;
118              
119 5         8 my %halstead;
120              
121 5         15 for my $item ( $dump->list ) {
122 100         4305 $item =~ s/^\s*//;
123 100         378 $item =~ s/\s*$//;
124 100         195 my @item = split /\s+/, $item, 2;
125 100 100       155 next unless defined $item[1];
126 72 50 33     172 next if $item[0] eq 'PPI::Token::Pod' or $item[0] eq 'PPI::Token::End';
127 72         108 push @{ $halstead{ $item[0] } }, $item[1];
  72         141  
128             }
129              
130 5         29 $self->{n_operators} = 0;
131 5         10 $self->{n_operands} = 0;
132              
133 5         15 for my $key ( keys %halstead ) {
134 23 100       44 if ( _is_operand($key) ) {
135 8         13 $self->{n_operands} += @{ $halstead{$key} };
  8         16  
136             }
137             else {
138 15         18 $self->{n_operators} += @{ $halstead{$key} };
  15         25  
139             }
140             }
141              
142 5         8 my %distinct;
143              
144 5         11 for my $key ( keys %halstead ) {
145 23         26 for my $item ( @{ $halstead{$key} } ) {
  23         27  
146 72 100       85 if ( _is_operand($key) ) {
147 11         41 $distinct{operands}->{$item} = undef;
148             }
149             else {
150 61         117 $distinct{operators}->{$item} = undef;
151             }
152             }
153             }
154              
155 5         8 $self->{n_distinct_operators} = keys %{ $distinct{operators} };
  5         13  
156 5         6 $self->{n_distinct_operands} = keys %{ $distinct{operands} };
  5         12  
157              
158             die 'No distinct operands for ', $self->file, ". Computation cannot continue.\n"
159 5 50       52 unless $self->{n_distinct_operands};
160             }
161              
162              
163             sub report {
164 1     1 1 2027 my ( $self, $precision ) = @_;
165              
166 1   50     5 $precision //= 2;
167              
168 1         22 printf "Total operators: %d + Total operands: %d = Program length: %d\n",
169             $self->n_operators, $self->n_operands, $self->prog_length;
170 1         69 printf "Distinct operators: %d + Distinct operands: %d = Program vocabulary: %d\n",
171             $self->n_distinct_operators, $self->n_distinct_operands, $self->prog_vocab;
172 1         37 printf "Estimated program length: %.*f\n", $precision, $self->est_prog_length;
173 1         39 printf "Program volume: %.*f\n", $precision, $self->volume;
174 1         35 printf "Program difficulty: %.*f\n", $precision, $self->difficulty;
175 1         34 printf "Program level: %.*f\n", $precision, $self->level;
176 1         34 printf "Program language level: %.*f\n", $precision, $self->lang_level;
177 1         32 printf "Program intelligence content: %.*f\n", $precision, $self->intel_content;
178 1         33 printf "Program effort: %.*f\n", $precision, $self->effort;
179 1         33 printf "Time to program: %.*f\n", $precision, $self->time_to_program;
180 1         33 printf "Delivered bugs: %.*f\n", $precision, $self->delivered_bugs;
181             }
182              
183              
184             sub dump {
185 1     1 1 448 my ($self) = @_;
186             return {
187 1         27 n_operators => $self->n_operators,
188             n_operands => $self->n_operands,
189             n_distinct_operators => $self->n_distinct_operators,
190             n_distinct_operands => $self->n_distinct_operands,
191             prog_vocab => $self->prog_vocab,
192             prog_length => $self->prog_length,
193             est_prog_length => $self->est_prog_length,
194             volume => $self->volume,
195             difficulty => $self->difficulty,
196             level => $self->level,
197             lang_level => $self->lang_level,
198             intel_content => $self->intel_content,
199             effort => $self->effort,
200             time_to_program => $self->time_to_program,
201             delivered_bugs => $self->delivered_bugs,
202             };
203             }
204              
205             sub _is_operand {
206 95     95   95 my $key = shift;
207 95   66     436 return $key =~ /Number/
208             || $key eq 'PPI::Token::Symbol'
209             || $key eq 'PPI::Token::HereDoc'
210             || $key eq 'PPI::Token::Data'
211             || $key =~ /Quote/;
212             }
213              
214             sub _log2 {
215 6     6   40 my $n = shift;
216 6         77 return log($n) / log(2);
217             }
218              
219             1;
220              
221             __END__