File Coverage

lib/Path/IsDev.pm
Criterion Covered Total %
statement 24 26 92.3
branch 1 4 25.0
condition 2 3 66.6
subroutine 7 8 87.5
pod 1 1 100.0
total 35 42 83.3


line stmt bran cond sub pod time code
1 12     12   89286 use 5.008; # utf8
  12         39  
  12         508  
2 12     12   65 use strict;
  12         20  
  12         409  
3 12     12   59 use warnings;
  12         20  
  12         371  
4 12     12   12388 use utf8;
  12         110  
  12         86  
5              
6             package Path::IsDev;
7              
8             our $VERSION = '1.001002';
9              
10             # ABSTRACT: Determine if a given Path resembles a development source tree
11              
12             our $AUTHORITY = 'cpan:KENTNL'; # AUTHORITY
13              
14              
15              
16              
17              
18              
19              
20              
21              
22              
23              
24              
25 12     12   13994 use Sub::Exporter -setup => { exports => [ is_dev => \&_build_is_dev, ], };
  12         159796  
  12         158  
26              
27             our $ENV_KEY_DEBUG = 'PATH_ISDEV_DEBUG';
28              
29             our $DEBUG = ( exists $ENV{$ENV_KEY_DEBUG} ? $ENV{$ENV_KEY_DEBUG} : undef );
30              
31              
32              
33              
34              
35              
36              
37              
38              
39              
40              
41             sub debug {
42 0 0   0 1 0 return unless $DEBUG;
43 0         0 return *STDERR->printf( qq{[Path::IsDev] %s\n}, shift );
44             }
45              
46             sub _build_is_dev {
47 21     21   12128 my ( undef, undef, $arg ) = @_;
48              
49 21         33 my $isdev_object;
50             return sub {
51 10     10   5917 my ($path) = @_;
52 10   66     66 $isdev_object ||= do {
53 9         10087 require Path::IsDev::Object;
54 9 50       35 Path::IsDev::Object->new( %{ $arg || {} } );
  9         136  
55             };
56 10         160 return $isdev_object->matches($path);
57 21         115 };
58             }
59              
60              
61              
62              
63              
64              
65              
66              
67              
68              
69              
70              
71              
72              
73              
74              
75              
76              
77              
78              
79              
80              
81              
82              
83             *is_dev = _build_is_dev( 'Path::IsDev', 'is_dev', {} );
84              
85             1;
86              
87             __END__
88              
89             =pod
90              
91             =encoding UTF-8
92              
93             =head1 NAME
94              
95             Path::IsDev - Determine if a given Path resembles a development source tree
96              
97             =head1 VERSION
98              
99             version 1.001002
100              
101             =head1 SYNOPSIS
102              
103             use Path::IsDev qw(is_dev);
104              
105             if( is_dev('/some/path') ) {
106             ...
107             } else {
108             ...
109             }
110              
111             =head1 DESCRIPTION
112              
113             This module is more or less a bunch of heuristics for determining if a given path
114             is a development tree root of some kind.
115              
116             This has many useful applications, notably ones that require behaviours for "installed"
117             modules to be different to those that are still "in development"
118              
119             =head1 FUNCTIONS
120              
121             =head2 debug
122              
123             Debug callback.
124              
125             To enable debugging:
126              
127             export PATH_ISDEV_DEBUG=1
128              
129             =head2 C<is_dev>
130              
131             Using an C<import>'ed C<is_dev>:
132              
133             if( is_dev( $path ) ) {
134              
135             }
136              
137             Though the actual heuristics used will be based on how C<import> was called.
138              
139             Additionally, you can call
140              
141             Path::IsDev::is_dev
142              
143             without C<import>ing anything, and it will behave exactly the same as if you'd imported
144             it using
145              
146             use Path::IsDev qw( is_dev );
147              
148             That is, no C<set> specification is applicable, so you'll only get the "default".
149              
150             =begin MetaPOD::JSON v1.1.0
151              
152             {
153             "namespace":"Path::IsDev",
154             "interface":"exporter"
155             }
156              
157              
158             =end MetaPOD::JSON
159              
160             =head1 UNDERSTANDING AND DEBUGGING THIS MODULE
161              
162             Understanding how this module works, is critical to understand where you can use it, and the consequences of using it.
163              
164             This module operates on a very simplistic level, and its easy for false-positives to occur.
165              
166             There are two types of Heuristics, Postive/Confirming Heuristics, and Negative/Disconfirming Heuristics.
167              
168             Positive Heuristics and Negative Heuristics are based solely on the presence of specific marker files in a directory, or special
169             marker directories.
170              
171             For instance, the files C<META.yml>, C<Makefile.PL>, and C<Build.PL> are all B<Positive Heuristic> markers, because their
172             presence often indicates a "root" of a development tree.
173              
174             And for instance, the directories C<t/>, C<xt/> and C<.git/> are also B<Positive Heuristic> markers, because these structures
175             are common in C<perl> development trees, and uncommon in install trees.
176              
177             However, these markers sometimes go wrong, for instance, consider you have a C<local::lib> or C<perlbrew> install in C<$HOME>
178              
179             $HOME/
180             $HOME/lib/
181             $HOME/perl5/perls/perl-5.19.3/lib/site_perl/
182              
183             Etc.
184              
185             Under normal circumstances, neither C<$HOME> nor those 3 paths are considered C<dev>.
186              
187             However, all it takes to cause a false positive, is for somebody to install a C<t> or C<xt> directory, or a marker file in one of
188             the above directories for C<path_isdev($dir)> to return true.
189              
190             This may not be a problem, at least, until you use C<Path::FindDev> which combines C<Path::IsDev> with recursive up-level
191             traversal.
192              
193             $HOME/
194             $HOME/lib/
195             $HOME/perl5/perls/perl-5.19.3/lib/site_perl/
196              
197             find_dev('$HOME/perl5/perls/perl-5.19.3/lib/site_perl/') # returns false, because it is not inside a dev directory
198              
199             mkdir $HOME/t
200              
201             find_dev('$HOME/perl5/perls/perl-5.19.3/lib/site_perl/') # returns $HOME, because $HOME/t exists.
202              
203             And it is this kind of problem that usually catches people off guard.
204              
205             PATH_ISDEV_DEBUG=1 \
206             perl -Ilib -MPath::FindDev=find_dev \
207             -E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl-5.19.3/lib/site_perl})"
208              
209             ...
210             [Path::IsDev=0] + ::Tool::Dzil => 0 : dist.ini does not exist
211             [Path::IsDev=0] + ::Tool::MakeMaker => 0 : Makefile.PL does not exist
212             [Path::IsDev=0] + ::Tool::ModuleBuild => 0 : Build.PL does not exist
213             [Path::IsDev=0] + ::META => 0 : META.json does not exist
214             [Path::IsDev=0] + ::META => 1 : META.yml exists
215             [Path::IsDev=0] + ::META => 1 : /home/kent/perl5/META.yml is a file
216             [Path::IsDev=0] + ::META matched path /home/kent/perl5
217             /home/kent/perl5
218              
219             Whoops!.
220              
221             [Path::IsDev=0] + ::META => 1 : META.yml exists
222             [Path::IsDev=0] + ::META => 1 : /home/kent/perl5/META.yml is a file
223              
224             No wonder!
225              
226             rm /home/kent/perl5/META.yml
227              
228             PATH_ISDEV_DEBUG=1 \
229             perl -Ilib -MPath::FindDev=find_dev \
230             -E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl-5.19.3/lib/site_perl})"
231              
232             ...
233             [Path::IsDev=0] Matching /home/kent/perl5
234             ...
235             [Path::IsDev=0] + ::TestDir => 0 : xt does not exist
236             [Path::IsDev=0] + ::TestDir => 1 : t exists
237             [Path::IsDev=0] + ::TestDir => 1 : /home/kent/perl5/t is a dir
238             [Path::IsDev=0] + ::TestDir matched path /home/kent/perl5
239             /home/kent/perl5
240              
241             Double whoops!
242              
243             [Path::IsDev=0] + ::TestDir => 1 : t exists
244             [Path::IsDev=0] + ::TestDir => 1 : /home/kent/perl5/t is a dir
245              
246             And you could keep doing that until you rule out all the bad heuristics in your tree.
247              
248             Or, you could use a negative heuristic.
249              
250             touch /home/kent/perl5/.path_isdev_ignore
251              
252             PATH_ISDEV_DEBUG=1 \
253             perl -Ilib -MPath::FindDev=find_dev \
254             -E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl-5.19.3/lib/site_perl})"
255             ...
256             [Path::IsDev=0] Matching /home/kent/perl5
257             [Path::IsDev=0] - ::IsDev::IgnoreFile => 1 : .path_isdev_ignore exists
258             [Path::IsDev=0] - ::IsDev::IgnoreFile => 1 : /home/kent/perl5/.path_isdev_ignore is a file
259             [Path::IsDev=0] - ::IsDev::IgnoreFile excludes path /home/kent/perl5
260             [Path::IsDev=0] no match found
261             ...
262             [Path::IsDev=0] Matching /
263             ...
264             [Path::IsDev=0] no match found
265              
266             Success!
267              
268             [Path::IsDev=0] - ::IsDev::IgnoreFile => 1 : .path_isdev_ignore exists
269             [Path::IsDev=0] - ::IsDev::IgnoreFile => 1 : /home/kent/perl5/.path_isdev_ignore is a file
270              
271             =head1 HEURISTICS
272              
273             =head2 Negative Heuristics bundled with this distribution
274              
275             Just remember, a B<Negative> Heuristic B<excludes the path it is associated with>
276              
277             =over 4
278              
279             =item * L<< C<IsDev::IgnoreFile>|Path::IsDev::NegativeHeuristic::IsDev::IgnoreFile >> - C<.path_isdev_ignore>
280              
281             =back
282              
283             =head2 Positive Heuristics bundled with this distribution
284              
285             =over 4
286              
287             =item * L<< C<Changelog>|Path::IsDev::Heuristic::Changelog >> - Files matching C<Changes>, C<Changelog>, and similar, case
288             insensitive, extensions optional.
289              
290             =item * L<< C<DevDirMarker>|Path::IsDev::Heuristic::DevDirMarker >> - explicit C<.devdir> file to indicate a project root.
291              
292             =item * L<< C<META>|Path::IsDev::Heuristic::META >> - C<META.yml>/C<META.json>
293              
294             =item * L<< C<MYMETA>|Path::IsDev::Heuristic::MYMETA >> - C<MYMETA.yml>/C<MYMETA.json>
295              
296             =item * L<< C<Makefile>|Path::IsDev::Heuristic::Makefile >> - Any C<Makefile> format documented supported by GNU Make
297              
298             =item * L<< C<TestDir>|Path::IsDev::Heuristic::TestDir >> - A directory called either C<t/> or C<xt/>
299              
300             =item * L<< C<Tool::DZil>|Path::IsDev::Heuristic::Tool::DZil >> - A C<dist.ini> file
301              
302             =item * L<< C<Tool::MakeMaker>|Path::IsDev::Heuristic::Tool::MakeMaker >> - A C<Makefile.PL> file
303              
304             =item * L<< C<Tool::ModuleBuild>|Path::IsDev::Heuristic::Tool::ModuleBuild >> - A C<Build.PL> file
305              
306             =item * L<< C<VCS::Git>|Path::IsDev::Heuristic::VCS::Git >> - A C<.git> directory
307              
308             =back
309              
310             =head1 HEURISTIC SETS
311              
312             =head2 Heuristic Sets Bundled with this distribution
313              
314             =over 4
315              
316             =item * L<< C<Basic>|Path::IsDev::HeuristicSet::Basic >> - The basic heuristic set that contains most, if not all heuristics.
317              
318             =back
319              
320             =head1 ADVANCED USAGE
321              
322             =head2 Custom Sets
323              
324             C<Path::IsDev> has a system of "sets" of Heuristics, in order to allow for pluggable
325             and flexible heuristic types.
326              
327             Though, for the vast majority of cases, this is not required.
328              
329             use Path::IsDev is_dev => { set => 'Basic' };
330             use Path::IsDev is_dev => { set => 'SomeOtherSet' , -as => 'is_dev_other' };
331              
332             =head2 Overriding the default set
333              
334             If for whatever reason the C<Basic> set is insufficient, or if it false positives on your system for some reason,
335             the "default" set can be overridden.
336              
337             export PATH_ISDEV_DEFAULT_SET="SomeOtherSet"
338              
339             ...
340             use Path::IsDev qw( is_dev );
341             is_dev('/some/path') # uses SomeOtherSet
342              
343             Though this will only take priority in the event the set is not specified during C<import>
344              
345             If this poses a security concern for the user, then this security hole can be eliminated by declaring the set you want in code:
346              
347             export PATH_ISDEV_DEFAULT_SET="SomeOtherSet"
348              
349             ...
350             use Path::IsDev is_dev => { set => 'Basic' };
351             is_dev('/some/path') # uses Basic, regardless of ENV
352              
353             =head1 SECURITY
354              
355             Its conceivable, than an evil user could construct an evil set, containing arbitrary and vulnerable code, and possibly stash that
356             evil set in a poorly secured privileged users @INC
357              
358             And if they managed to achieve that, if they could poison the privileged users %ENV, they could trick the privileged user into
359             executing arbitrary code.
360              
361             Though granted, if you can do either of those 2 things, you're probably security vulnerable anyway, and granted, if you could do
362             either of those 2 things you could do much more evil things by the following:
363              
364             export PERL5OPT="-MEvil::Module"
365              
366             So with that in understanding, saying this modules default utility is "insecure" is mostly a bogus argument.
367              
368             And to that effect, this module does nothing to "lock down" that mechanism, and this module encourages you
369             to B<NOT> force a set, unless you B<NEED> to, and strongly suggests that forcing a set for the purpose of security will achieve
370             no real improvement in security, while simultaneously reducing utility.
371              
372             =head1 AUTHOR
373              
374             Kent Fredric <kentfredric@gmail.com>
375              
376             =head1 COPYRIGHT AND LICENSE
377              
378             This software is copyright (c) 2014 by Kent Fredric <kentfredric@gmail.com>.
379              
380             This is free software; you can redistribute it and/or modify it under
381             the same terms as the Perl 5 programming language system itself.
382              
383             =cut