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.0614';
7              
8 1     1   1045 use PPI::Document;
  1         91115  
  1         36  
9 1     1   385 use PPI::Dumper;
  1         844  
  1         27  
10              
11 1     1   1120 use Moo;
  1         9788  
  1         6  
12 1     1   1654 use strictures 2;
  1         1433  
  1         37  
13 1     1   653 use namespace::clean;
  1         10351  
  1         6  
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 1     1   2323 my ($self) = @_;
53 1         8 return $self->n_distinct_operators + $self->n_distinct_operands;
54             }
55              
56             sub _build_prog_length {
57 1     1   10 my ($self) = @_;
58 1         7 return $self->n_operators + $self->n_operands;
59             }
60              
61             sub _build_est_prog_length {
62 1     1   10 my ($self) = @_;
63 1         6 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 1     1   10 my ($self) = @_;
69 1         15 return $self->prog_length * _log2($self->prog_vocab);
70             }
71              
72             sub _build_difficulty {
73 3     3   646 my ($self) = @_;
74 3         26 return ($self->n_distinct_operators / 2)
75             * ($self->n_operands / $self->n_distinct_operands);
76             }
77              
78             sub _build_level {
79 1     1   469 my ($self) = @_;
80 1         14 return 1 / $self->difficulty;
81             }
82              
83             sub _build_lang_level {
84 1     1   475 my ($self) = @_;
85 1         19 return $self->volume / $self->difficulty / $self->difficulty;
86             }
87              
88             sub _build_intel_content {
89 1     1   521 my ($self) = @_;
90 1         14 return $self->volume / $self->difficulty;
91             }
92              
93             sub _build_effort {
94 1     1   472 my ($self) = @_;
95 1         14 return $self->difficulty * $self->volume;
96             }
97              
98             sub _build_time_to_program {
99 1     1   470 my ($self) = @_;
100 1         15 return $self->effort / 18;
101             }
102              
103             sub _build_delivered_bugs {
104 1     1   454 my ($self) = @_;
105 1         14 return ($self->effort ** (2/3)) / 3000;
106             }
107              
108              
109             sub BUILD {
110 4     4 0 6220 my ( $self, $args ) = @_;
111              
112 4         29 my $doc = PPI::Document->new( $self->file );
113              
114 4         14219 my $dump = PPI::Dumper->new( $doc, whitespace => 0, comments => 0 );
115              
116 4 100       174 die 'No document parsed for ', $self->file, ". Computation can't continue.\n"
117             unless $dump;
118              
119 3         4 my %halstead;
120              
121 3         11 for my $item ( $dump->list ) {
122 74         3529 $item =~ s/^\s*//;
123 74         300 $item =~ s/\s*$//;
124 74         155 my @item = split /\s+/, $item, 2;
125 74 100       125 next unless defined $item[1];
126 54 50 33     139 next if $item[0] eq 'PPI::Token::Pod' or $item[0] eq 'PPI::Token::End';
127 54         56 push @{ $halstead{ $item[0] } }, $item[1];
  54         130  
128             }
129             #use Data::Dumper;warn(__PACKAGE__,' ',__LINE__,' ',Dumper\%halstead);
130              
131 3         25 $self->{n_operators} = 0;
132 3         7 $self->{n_operands} = 0;
133              
134 3         8 for my $key ( keys %halstead ) {
135 17 100       40 if ( _is_operand($key) ) {
136 6         11 $self->{n_operands} += @{ $halstead{$key} };
  6         11  
137             }
138             else {
139 11         19 $self->{n_operators} += @{ $halstead{$key} };
  11         19  
140             }
141             }
142              
143 3         6 my %distinct;
144              
145 3         9 for my $key ( keys %halstead ) {
146 17         22 for my $item ( @{ $halstead{$key} } ) {
  17         24  
147 54 100       74 if ( _is_operand($key) ) {
148 9         22 $distinct{operands}->{$item} = undef;
149             }
150             else {
151 45         89 $distinct{operators}->{$item} = undef;
152             }
153             }
154             }
155             #use Data::Dumper;warn(__PACKAGE__,' ',__LINE__,' ',Dumper\%distinct);
156              
157 3         4 $self->{n_distinct_operators} = keys %{ $distinct{operators} };
  3         9  
158 3         6 $self->{n_distinct_operands} = keys %{ $distinct{operands} };
  3         5  
159              
160             die 'No distinct operands for ', $self->file, ". Computation cannot continue.\n"
161 3 50       40 unless $self->{n_distinct_operands};
162             }
163              
164              
165             sub report {
166 1     1 1 2271 my ( $self, $precision ) = @_;
167              
168 1   50     7 $precision //= 2;
169              
170 1         25 printf "Total operators: %d + Total operands: %d = Program length: %d\n",
171             $self->n_operators, $self->n_operands, $self->prog_length;
172 1         86 printf "Distinct operators: %d + Distinct operands: %d = Program vocabulary: %d\n",
173             $self->n_distinct_operators, $self->n_distinct_operands, $self->prog_vocab;
174 1         41 printf "Estimated program length: %.*f\n", $precision, $self->est_prog_length;
175 1         42 printf "Program volume: %.*f\n", $precision, $self->volume;
176 1         41 printf "Program difficulty: %.*f\n", $precision, $self->difficulty;
177 1         39 printf "Program level: %.*f\n", $precision, $self->level;
178 1         39 printf "Program language level: %.*f\n", $precision, $self->lang_level;
179 1         37 printf "Program intelligence content: %.*f\n", $precision, $self->intel_content;
180 1         50 printf "Program effort: %.*f\n", $precision, $self->effort;
181 1         41 printf "Time to program: %.*f\n", $precision, $self->time_to_program;
182 1         39 printf "Delivered bugs: %.*f\n", $precision, $self->delivered_bugs;
183             }
184              
185              
186             sub dump {
187 1     1 1 454 my ($self) = @_;
188             return {
189 1         27 n_operators => $self->n_operators,
190             n_operands => $self->n_operands,
191             n_distinct_operators => $self->n_distinct_operators,
192             n_distinct_operands => $self->n_distinct_operands,
193             prog_vocab => $self->prog_vocab,
194             prog_length => $self->prog_length,
195             est_prog_length => $self->est_prog_length,
196             volume => $self->volume,
197             difficulty => $self->difficulty,
198             level => $self->level,
199             lang_level => $self->lang_level,
200             intel_content => $self->intel_content,
201             effort => $self->effort,
202             time_to_program => $self->time_to_program,
203             delivered_bugs => $self->delivered_bugs,
204             };
205             }
206              
207             sub _is_operand {
208 71     71   85 my $key = shift;
209 71   66     418 return $key =~ /Number/
210             || $key eq 'PPI::Token::Symbol'
211             || $key eq 'PPI::Token::HereDoc'
212             || $key eq 'PPI::Token::Data'
213             || $key =~ /Quote/;
214             }
215              
216             sub _log2 {
217 3     3   26 my $n = shift;
218 3         38 return log($n) / log(2);
219             }
220              
221             1;
222              
223             __END__