File Coverage

blib/lib/Perl/Metrics.pm
Criterion Covered Total %
statement 99 109 90.8
branch 21 38 55.2
condition 6 15 40.0
subroutine 21 21 100.0
pod 4 4 100.0
total 151 187 80.7


line stmt bran cond sub pod time code
1             package Perl::Metrics;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Perl::Metrics - The Perl Code Metrics System
8              
9             =head1 SYNOPSIS
10              
11             # Load or create the metrics database
12             use Perl::Metrics '/var/cache/perl/metrics.sqlite';
13            
14             # Index and process a directory of code
15             Perl::Metrics->process_directory( '/home/adam/code/mycpan' );
16              
17             =head1 DESCRIPTION
18              
19             The Perl Code Metrics System is a module which provides a Perl document
20             metrics processing engine, and a database in which to store the
21             resulting metrics data.
22              
23             The intent is to be able to take a large collection of Perl documents,
24             and relatively easily parse the files and run a series of processes on
25             the documents.
26              
27             The resulting data can then be stored, and later used to generate useful
28             information about the documents.
29              
30             =head2 General Structure
31              
32             Perl::Metrics consists of two primary components. Firstly, a
33             L/L database that stores the metrics informationg.
34              
35             See L and L for the two
36             data classes stored in the database.
37              
38             Secondly, a plugin structure for creating metrics packages that can
39             interoperate with the system, allowing it to take care of document
40             processing and data storage while the plugin can concentrate on the
41             actual generation of the metrics.
42              
43             See L for more information.
44              
45             =head2 Getting Started
46              
47             C comes with on default plugin,
48             L, which provides a sampling of metrics.
49              
50             To get started load the module, providing the database location as a
51             param (it will create it if needed). Then call the C
52             method, providing it with an absolute path to a directory of Perl code
53             on the local filesystem.
54              
55             C will quitely sit there working away, and then when it
56             finishes you will have a nice database full of metrics data about your
57             files.
58              
59             Of course, how you actually USE that data is up to you, but you can
60             query L and L for the data
61             just like any other L database once you have collected it
62             all.
63              
64             =head1 METHODS
65              
66             =cut
67              
68 6     6   18773 use 5.00503;
  6         22  
  6         274  
69 6     6   35 use strict;
  6         9  
  6         239  
70 6     6   43 use Carp ();
  6         19  
  6         119  
71 6     6   61866 use DBI ();
  6         131400  
  6         239  
72 6     6   71 use File::Spec ();
  6         15  
  6         94  
73 6     6   5965 use PPI::Util ();
  6         25491  
  6         173  
74 6     6   6084 use File::Find::Rule ();
  6         54277  
  6         166  
75 6     6   6312 use File::Find::Rule::Perl ();
  6         24843  
  6         148  
76 6     6   5875 use Module::Pluggable;
  6         67228  
  6         43  
77              
78 6     6   599 use vars qw{$VERSION $TRACE};
  6         16  
  6         421  
79             BEGIN {
80 6     6   15 $VERSION = '0.09';
81            
82             # Enable the trace flag to show trace messages during the
83             # main processing loops in this class
84 6 50       212 $TRACE = 0 unless defined $TRACE;
85             }
86              
87             # The database structure
88             my $SQLITE_CREATE = <<'END_SQL';
89             CREATE TABLE files (
90             path TEXT NOT NULL,
91             checked INTEGER NOT NULL,
92             hex_id TEXT NOT NULL,
93             PRIMARY KEY (path)
94             );
95             CREATE TABLE metrics (
96             hex_id TEXT NOT NULL,
97             package TEXT NOT NULL,
98             version NUMERIC,
99             name TEXT NOT NULL,
100             value TEXT,
101             PRIMARY KEY (hex_id, package, name)
102             )
103             END_SQL
104              
105             # Load the components
106 6     6   4326 use Perl::Metrics::CDBI ();
  6         17  
  6         127  
107 6     6   4193 use Perl::Metrics::File ();
  6         22  
  6         160  
108 6     6   4321 use Perl::Metrics::Metric ();
  6         22  
  6         180  
109 6     6   3584 use Perl::Metrics::Plugin ();
  6         21  
  6         5824  
110              
111              
112              
113              
114              
115             #####################################################################
116             # Setup Methods
117              
118             sub import {
119 5     5   66 my $class = shift;
120 5 50       31 my $file = shift or Carp::croak(
121             "Did not provide a database location when loading Perl::Metrics"
122             );
123              
124             # Do we already have a DSN defined?
125 5 50       37 if ( $Perl::Metrics::CDBI::DSN ) {
126 0         0 Carp::croak("Perl::Metrics has already been initialised with database $Perl::Metrics::CDBI::DSN");
127             }
128              
129             # Set the file
130 5         19 $Perl::Metrics::CDBI::DSN = "dbi:SQLite:dbname=$file";
131              
132             # Does the file already exist?
133             # If not we'll need to create the tables now
134 5         121 my $create = ! -f $file;
135              
136             # Do a test connection to the database
137 5         168 my $dbh = Perl::Metrics::CDBI->db_Main;
138              
139             # Create the database if needed
140 5 100       62072 if ( $create ) {
141 4         31 foreach my $sql_create_table ( split /;/, $SQLITE_CREATE ) {
142             # Execute the table creation SQL
143 8 50       334208 $dbh->do( $sql_create_table ) or Carp::croak(
144             "Error creating database table",
145             $dbh->errstr,
146             );
147             }
148             }
149              
150 5         163235 1;
151             }
152              
153              
154              
155              
156              
157             #####################################################################
158             # Perl::Metrics Methods
159              
160             =pod
161              
162             =head2 index_file $absolute_path
163              
164             The C method takes a single absolute file path and creates
165             an entry in the C index, referencing the file name to its
166             C for later use.
167              
168             Note that this does not execute any metrics on the file, merely allows
169             the system to "remember" the file for later.
170              
171             =cut
172              
173             sub index_file {
174 10     10 1 2926 my $class = shift;
175              
176             # Get and check the filename
177 10         100 my $path = File::Spec->canonpath(shift);
178 10 50 33     128 unless ( defined $path and ! ref $path and $path ne '' ) {
      33        
179 0         0 Carp::croak("Did not pass a file name to index_file");
180             }
181 10 50       143 unless ( File::Spec->file_name_is_absolute($path) ) {
182 0         0 Carp::croak("Cannot index relative path '$path'. Must be absolute");
183             }
184 10 50       275 Carp::croak("Cannot index '$path'. File does not exist") unless -f $path;
185 10 50       197 Carp::croak("Cannot index '$path'. No read permissions") unless -r _;
186 10         52 my @f = stat(_);
187              
188 10         66 $class->_trace("Indexing $path... ");
189              
190             # Get the current record, if it exists
191 10         97 my $file = Perl::Metrics::File->retrieve( $path );
192              
193             # If we already have a record, and it's checked time
194             # is higher than the mtime of the file, the existing
195             # hex_id is corrent and we can shortcut.
196 10 100 66     26219 if ( $file and $file->checked > $f[9] ) {
197 1         250 $class->_trace("unchanged.\n");
198 1         4 return $file;
199             }
200              
201             # At this point we know we'll need to go to the expense of
202             # generating the MD5hex value.
203 9 50       65 my $md5hex = PPI::Util::md5hex_file( $path )
204             or Carp::croak("Cannot index '$path'. Failed to generate hex_id");
205              
206 9 50       1656 if ( $file ) {
207             # Update the record to the new values
208 0         0 $class->_trace("updating.\n");
209 0         0 $file->checked(time);
210 0         0 $file->hex_id($md5hex);
211 0         0 $file->update;
212             } else {
213             # Create a new record
214 9         67 $class->_trace("inserting.\n");
215 9         123 $file = Perl::Metrics::File->insert( {
216             path => $path,
217             checked => time,
218             hex_id => $md5hex,
219             } );
220             }
221              
222 9         196075 $file;
223             }
224              
225             =pod
226              
227             =head2 index_directory $absolute_path
228              
229             As for C, the C method will recursively scan
230             down a directory tree, locating all Perl files and adding them to the
231             file index.
232              
233             Returns the number of files added.
234              
235             =cut
236              
237             sub index_directory {
238 3     3 1 3039 my $class = shift;
239              
240             # Get and check the directory name
241 3         9 my $path = shift;
242 3 50 33     50 unless ( defined $path and ! ref $path and $path ne '' ) {
      33        
243 0         0 Carp::croak("Did not pass a directory name to index_directory");
244             }
245 3 50       59 unless ( File::Spec->file_name_is_absolute($path) ) {
246 0         0 Carp::croak("Cannot index relative path '$path'. Must be absolute");
247             }
248 3 50       81 Carp::croak("Cannot index '$path'. Directory does not exist") unless -d $path;
249 3 50       22 Carp::croak("Cannot index '$path'. No read permissions") unless -r _;
250 3 50       19 Carp::croak("Cannot index '$path'. No enter permissions") unless -x _;
251              
252             # Search for all the applicable files in the directory
253 3         22 $class->_trace("Search for files in $path...\n");
254 3         40 my @files = File::Find::Rule->perl_file->in( $path );
255 3         7696 $class->_trace("Found " . scalar(@files) . " file(s).\n");
256              
257             # Sort the files so we index in deterministic order
258 3         11 $class->_trace("Sorting files...\n");
259 3         17 @files = sort @files;
260              
261             # Index the files
262 3         17 $class->_trace("Indexing files...\n");
263 3         8 foreach my $file ( @files ) {
264 9         159 $class->index_file( $file );
265             }
266              
267 3         98 scalar(@files);
268             }
269              
270             =pod
271              
272             =head2 process_index
273              
274             The C method is the primary method for generating metrics
275             data. It triggering a metrics generation pass for all metrics on all files
276             currently in the index.
277              
278             =cut
279              
280             sub process_index {
281 1     1 1 3 my $class = shift;
282              
283             # Create the plugin objects
284 1         10 foreach my $plugin ( $class->plugins ) {
285 1         2786 $class->_trace("STARTING PLUGIN $plugin...\n");
286 1         110 eval "require $plugin";
287 1 50       8 die $@ if $@;
288 1         12 $plugin->new->process_index;
289             }
290              
291 1         6 1;
292             }
293              
294             =pod
295              
296             =head2 process_directory $absolute_path
297              
298             The C method is a convenience method. It runs an
299             C call for the directory, and then triggers a
300             C call after the index has been populated.
301              
302             =cut
303              
304             sub process_directory {
305 1     1 1 923 my $class = shift;
306 1         6 $class->index_directory( $_[0] );
307 1         9 $class->process_index;
308             }
309              
310              
311              
312              
313              
314             #####################################################################
315             # Support Methods
316              
317             sub _trace {
318 45     45   1173 my $class = shift;
319 45 50       197 return 1 unless $TRACE;
320 0           print @_;
321             }
322              
323             1;
324              
325             =pod
326              
327             =head1 TO DO
328              
329             - Provide a more useful set of default plugins
330              
331             - Provide the option to process for a subset of plugins
332              
333             - Implemented automatic integration with L
334              
335             =head1 SUPPORT
336              
337             Bugs should be reported via the CPAN bug tracker at
338              
339             L
340              
341             For other issues, contact the author.
342              
343             =head1 AUTHOR
344              
345             Adam Kennedy Eadamk@cpan.orgE
346              
347             =head1 COPYRIGHT
348              
349             Copyright 2005 - 2008 Adam Kennedy.
350              
351             This program is free software; you can redistribute
352             it and/or modify it under the same terms as Perl itself.
353              
354             The full text of the license can be found in the
355             LICENSE file included with this module.
356              
357             =cut