File Coverage

lib/App/Relate.pm
Criterion Covered Total %
statement 59 100 59.0
branch 9 28 32.1
condition 0 5 0.0
subroutine 6 7 85.7
pod 2 2 100.0
total 76 142 53.5


line stmt bran cond sub pod time code
1             package App::Relate;
2             # doom@kzsu.stanford.edu
3             # 15 Mar 2010
4              
5              
6             =head1 NAME
7              
8             App::Relate - simple form of the "relate" script (wrapper around locate)
9              
10             =head1 SYNOPSIS
11              
12             use App::Relate ':all';
13              
14             relate( \@search, \@filter );
15              
16             relate( \@search, \@filter, $opts );
17              
18             =head1 DESCRIPTION
19              
20             relate simplifies the use of locate.
21              
22             Instead of:
23              
24             locate this | egrep "with_this" | egrep "and_this" | egrep -v "but_not_this"
25              
26             You can type:
27              
28             relate this with_this and_this -but_not_this
29              
30             This module is a simple back-end to implement the relate script.
31             See L for user documentation.
32              
33             =head2 EXPORT
34              
35             None by default. The following, on request (or via ':all' tag):
36              
37             =over
38              
39             =cut
40              
41 1     1   66209 use 5.008;
  1         12  
  1         41  
42 1     1   5 use strict;
  1         2  
  1         28  
43 1     1   6 use warnings;
  1         7  
  1         60  
44             my $DEBUG = 0;
45 1     1   5 use Carp;
  1         2  
  1         67  
46 1     1   5 use Data::Dumper;
  1         2  
  1         1263  
47              
48             require Exporter;
49              
50             our @ISA = qw(Exporter);
51             our %EXPORT_TAGS = ( 'all' => [
52             qw(
53             relate
54             ) ] );
55             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
56             our @EXPORT = qw( ); # items to export into callers namespace by default.
57             # (don't use this without a very good reason.)
58             our $VERSION = '0.10';
59              
60             =item relate
61              
62             The relate routine searches the filesystem for items whose
63             fullpath matches all of the terms specified the search terms aref
64             (first argument), and filters out any that match a term in the
65             filters aref (second argument). It's behavior can be modified
66             by options supplied in the options hashref (the third argument).
67              
68             The options hashref may have values:
69              
70             ignore_case do case insensitive searches
71              
72             dirs_only return only matching directories
73             files_only return only matching plain files
74             links_only return only matching symlinks
75              
76             all_results ignore any filter supplied as a second argument.
77             A convenience to script usage: idential to using an undef second arg.
78              
79             test_data For test purposes: an aref of strings to be searched and filtered.
80             Suppresses use of L.
81              
82             locate Alternate 'locate' invocation string. See L routine.
83             locatedb Specify non-standard locate db file. See L routine.
84              
85             Example usage:
86              
87             my $results = relate( \@search_terms, \@filter_terms, $opts );
88              
89              
90             Example usage (searching a test data set):
91              
92             my $skipdull = ['~$', '\bRCS\b', '\bCVS\b', '^#', '\.elc$' ];
93             my $results =
94             relate( [ 'whun' ], $skipdull,
95             { test_data => [ '/tmp/whun',
96             '/tmp/tew',
97             '/tmp/thruee',
98             '/etc/whun',
99             ],
100             } );
101              
102              
103             =cut
104              
105             sub relate {
106 5     5 1 3623 my $searches = shift;
107 5         8 my $filters = shift;
108 5         6 my $opts = shift;
109 5         11 my $DEBUG = $opts->{ DEBUG };
110              
111 5         7 my $all_results = $opts->{ all_results };
112 5         7 my $ignore_case = $opts->{ ignore_case };
113 5         8 my $test_data = $opts->{ test_data };
114 5         8 my $dirs_only = $opts->{ dirs_only };
115 5         6 my $files_only = $opts->{ files_only };
116 5         6 my $links_only = $opts->{ links_only };
117              
118 5         7 my $initial;
119 5 50       14 if ( ref( $test_data ) eq 'ARRAY' ) {
    0          
120 5         7 $initial = $test_data;
121             } elsif ( $test_data ) {
122 0         0 carp "The 'test_data' option should be an array reference.";
123             } else {
124 0         0 my $seed = shift @{ $searches };
  0         0  
125 0         0 $initial = locate( $seed, $opts );
126             }
127              
128             # dwim upcarets: usually should behave like boundary matches
129 5         6 my @rules = map{ s{^ \^ (?![/]) }{\\b}xg; $_ } @{ $searches };
  9         15  
  9         22  
  5         9  
130             # TODO why not qr{ $_ }, compile regexps at this stage? Bench this...
131              
132 5         9 my @set = @{ $initial };
  5         15  
133 5         8 my @temp;
134             # try each search term, winnowing down result on each pass
135 5 50       9 if ( not( $ignore_case ) ) {
136 5         9 foreach my $search ( @rules ) {
137             # leading minus means negation
138 9 100       23 if ( (my $term = $search) =~ s{ ^ - }{}x ) {
139 1         10 my $rule = qr{ $term }x;
140 1         4 @temp = grep { not m{ $rule }x } @set;
  3         21  
141             } else {
142 8         93 my $rule = qr{ $search }x;
143 8         14 @temp = grep { m{ $rule }x } @set;
  40         289  
144             }
145 9         21 @set = @temp;
146 9         21 @temp = ();
147             }
148             } else { # ignore case
149 0         0 foreach my $search ( @rules ) {
150             # leading minus means negation
151 0 0       0 if ( (my $term = $search) =~ s{ ^ - }{}x ) {
152 0         0 my $rule = qr{ $term }xi;
153 0         0 @temp = grep { not m{ $rule }x } @set;
  0         0  
154             } else {
155 0         0 my $rule = qr{ $search }xi;
156 0         0 @temp = grep { m{ $rule }x } @set;
  0         0  
157             }
158 0         0 @set = @temp;
159 0         0 @temp = ();
160             }
161             }
162              
163             # pre-compile each filter term
164 5         8 my @filters;
165 5 50       11 if ( not( $ignore_case ) ) {
166 5         5 @filters = map{ qr{ $_ }x } @{ $filters };
  24         264  
  5         10  
167             } else { # ignore case
168 0         0 @filters = map{ qr{ $_ }xi } @{ $filters };
  0         0  
  0         0  
169             }
170              
171             # apply each filter pattern, rejecting what matches
172 5 50       16 unless( $all_results ) {
173 5         9 foreach my $filter ( @filters ) {
174 24         38 @temp = grep { not m{ $filter }x } @set;
  44         337  
175 24         42 @set = @temp;
176 24         48 @temp = ();
177             }
178             }
179              
180 5 50       22 if( $dirs_only ) {
    50          
    50          
181 0         0 @set = grep{ -d $_ } @set;
  0         0  
182             } elsif ( $files_only ) {
183 0         0 @set = grep{ -f $_ } @set;
  0         0  
184             } elsif ( $links_only ) {
185 0         0 @set = grep{ -l $_ } @set;
  0         0  
186             }
187              
188 5         40 return \@set;
189             }
190              
191              
192             =item locate
193              
194             Runs the locate command on the given search term, the "seed".
195             Also accepts a hashref of options as a second argument.
196              
197             Makes use of options fields "DEBUG", "locate", and "locatedb"
198             (aka "database").
199              
200             The "locate" option defaults simply to "locate". Define it as
201             something else if you want to use a different program internally.
202             (note: you may include the path).
203              
204             Example:
205              
206             my $hits = locate( $seed, { locate => '/usr/local/bin/slocate' } );
207              
208             my $hits = locate( $seed, { locatedb => '/tmp/slocate.db' } );
209              
210             =cut
211              
212             sub locate {
213 0     0 1   my $seed = shift;
214 0           my $opts = shift;
215 0           my $DEBUG = $opts->{ DEBUG };
216              
217 0   0       my $locate = $opts->{ locate } || 'locate';
218 0   0       my $database = $opts->{ database } || $opts->{ locatedb };
219              
220 0           my $option_string = '';
221 0 0         if ( $opts->{ regexp } ) {
222 0           $option_string .= ' -r ';
223             }
224              
225 0 0         if ( $opts->{ ignore_case } ) {
226 0           $option_string .= ' -i ';
227             }
228              
229 0 0         if ( $database ) {
230 0           $option_string .= " -d $database ";
231             }
232              
233 0           my $cmd = qq{ $locate $option_string $seed };
234 0 0         ($DEBUG) && print STDERR "cmd: $cmd\n";
235              
236 0           my $raw = qx{ $cmd };
237 0           chomp( $raw );
238              
239 0           my @set = split /\n/, $raw;
240              
241 0           return \@set;
242             }
243              
244             1;
245              
246             =back
247              
248             =head1 SEE ALSO
249              
250             See the man page for "locate".
251              
252             L is a more complicated version of this project.
253             It's based on L, which was intended to allow the
254             sharing of filters between different projects.
255              
256             =head1 NOTES
257              
258             =head1 TODO
259              
260             =head1 AUTHOR
261              
262             Joseph Brenner, Edoom@kzsu.stanford.eduE
263              
264             =head1 COPYRIGHT AND LICENSE
265              
266             Copyright (C) 2010 by Joseph Brenner
267              
268             This program is free software; you can redistribute it and/or modify it
269             under the terms of either: the GNU General Public License as published
270             by the Free Software Foundation; or the Artistic License.
271              
272             See http://dev.perl.org/licenses/ for more information.
273              
274             =head1 BUGS
275              
276             See L.
277              
278             =cut