.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Path::IsDev 3" .TH Path::IsDev 3 2023-07-26 "perl v5.38.0" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME Path::IsDev \- Determine if a given Path resembles a development source tree .SH VERSION .IX Header "VERSION" version 1.001003 .SH SYNOPSIS .IX Header "SYNOPSIS" .Vb 1 \& use Path::IsDev qw(is_dev); \& \& if( is_dev(\*(Aq/some/path\*(Aq) ) { \& ... \& } else { \& ... \& } .Ve .SH DESCRIPTION .IX Header "DESCRIPTION" This module is more or less a bunch of heuristics for determining if a given path is a development tree root of some kind. .PP This has many useful applications, notably ones that require behaviours for "installed" modules to be different to those that are still "in development" .SH FUNCTIONS .IX Header "FUNCTIONS" .SS debug .IX Subsection "debug" Debug callback. .PP To enable debugging: .PP .Vb 1 \& export PATH_ISDEV_DEBUG=1 .Ve .ie n .SS """is_dev""" .el .SS \f(CWis_dev\fP .IX Subsection "is_dev" Using an \f(CW\*(C`import\*(C'\fR'ed \f(CW\*(C`is_dev\*(C'\fR: .PP .Vb 1 \& if( is_dev( $path ) ) { \& \& } .Ve .PP Though the actual heuristics used will be based on how \f(CW\*(C`import\*(C'\fR was called. .PP Additionally, you can call .PP .Vb 1 \& Path::IsDev::is_dev .Ve .PP without \f(CW\*(C`import\*(C'\fRing anything, and it will behave exactly the same as if you'd imported it using .PP .Vb 1 \& use Path::IsDev qw( is_dev ); .Ve .PP That is, no \f(CW\*(C`set\*(C'\fR specification is applicable, so you'll only get the "default". .SH "UNDERSTANDING AND DEBUGGING THIS MODULE" .IX Header "UNDERSTANDING AND DEBUGGING THIS MODULE" Understanding how this module works, is critical to understand where you can use it, and the consequences of using it. .PP This module operates on a very simplistic level, and its easy for false-positives to occur. .PP There are two types of Heuristics, Postive/Confirming Heuristics, and Negative/Disconfirming Heuristics. .PP Positive Heuristics and Negative Heuristics are based solely on the presence of specific marker files in a directory, or special marker directories. .PP For instance, the files \f(CW\*(C`META.yml\*(C'\fR, \f(CW\*(C`Makefile.PL\*(C'\fR, and \f(CW\*(C`Build.PL\*(C'\fR are all \fBPositive Heuristic\fR markers, because their presence often indicates a "root" of a development tree. .PP And for instance, the directories \f(CW\*(C`t/\*(C'\fR, \f(CW\*(C`xt/\*(C'\fR and \f(CW\*(C`.git/\*(C'\fR are also \fBPositive Heuristic\fR markers, because these structures are common in \f(CW\*(C`perl\*(C'\fR development trees, and uncommon in install trees. .PP However, these markers sometimes go wrong, for instance, consider you have a \f(CW\*(C`local::lib\*(C'\fR or \f(CW\*(C`perlbrew\*(C'\fR install in \f(CW$HOME\fR .PP .Vb 3 \& $HOME/ \& $HOME/lib/ \& $HOME/perl5/perls/perl\-5.19.3/lib/site_perl/ .Ve .PP Etc. .PP Under normal circumstances, neither \f(CW$HOME\fR nor those 3 paths are considered \f(CW\*(C`dev\*(C'\fR. .PP However, all it takes to cause a false positive, is for somebody to install a \f(CW\*(C`t\*(C'\fR or \f(CW\*(C`xt\*(C'\fR directory, or a marker file in one of the above directories for \f(CWpath_isdev($dir)\fR to return true. .PP This may not be a problem, at least, until you use \f(CW\*(C`Path::FindDev\*(C'\fR which combines \f(CW\*(C`Path::IsDev\*(C'\fR with recursive up-level traversal. .PP .Vb 3 \& $HOME/ \& $HOME/lib/ \& $HOME/perl5/perls/perl\-5.19.3/lib/site_perl/ \& \& find_dev(\*(Aq$HOME/perl5/perls/perl\-5.19.3/lib/site_perl/\*(Aq) # returns false, because it is not inside a dev directory \& \& mkdir $HOME/t \& \& find_dev(\*(Aq$HOME/perl5/perls/perl\-5.19.3/lib/site_perl/\*(Aq) # returns $HOME, because $HOME/t exists. .Ve .PP And it is this kind of problem that usually catches people off guard. .PP .Vb 3 \& PATH_ISDEV_DEBUG=1 \e \& perl \-Ilib \-MPath::FindDev=find_dev \e \& \-E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl\-5.19.3/lib/site_perl})" \& \& ... \& [Path::IsDev=0] + ::Tool::Dzil => 0 : dist.ini does not exist \& [Path::IsDev=0] + ::Tool::MakeMaker => 0 : Makefile.PL does not exist \& [Path::IsDev=0] + ::Tool::ModuleBuild => 0 : Build.PL does not exist \& [Path::IsDev=0] + ::META => 0 : META.json does not exist \& [Path::IsDev=0] + ::META => 1 : META.yml exists \& [Path::IsDev=0] + ::META => 1 : /home/kent/perl5/META.yml is a file \& [Path::IsDev=0] + ::META matched path /home/kent/perl5 \& /home/kent/perl5 .Ve .PP Whoops!. .PP .Vb 2 \& [Path::IsDev=0] + ::META => 1 : META.yml exists \& [Path::IsDev=0] + ::META => 1 : /home/kent/perl5/META.yml is a file .Ve .PP No wonder! .PP .Vb 1 \& rm /home/kent/perl5/META.yml \& \& PATH_ISDEV_DEBUG=1 \e \& perl \-Ilib \-MPath::FindDev=find_dev \e \& \-E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl\-5.19.3/lib/site_perl})" \& \& ... \& [Path::IsDev=0] Matching /home/kent/perl5 \& ... \& [Path::IsDev=0] + ::TestDir => 0 : xt does not exist \& [Path::IsDev=0] + ::TestDir => 1 : t exists \& [Path::IsDev=0] + ::TestDir => 1 : /home/kent/perl5/t is a dir \& [Path::IsDev=0] + ::TestDir matched path /home/kent/perl5 \& /home/kent/perl5 .Ve .PP Double whoops! .PP .Vb 2 \& [Path::IsDev=0] + ::TestDir => 1 : t exists \& [Path::IsDev=0] + ::TestDir => 1 : /home/kent/perl5/t is a dir .Ve .PP And you could keep doing that until you rule out all the bad heuristics in your tree. .PP Or, you could use a negative heuristic. .PP .Vb 1 \& touch /home/kent/perl5/.path_isdev_ignore \& \& PATH_ISDEV_DEBUG=1 \e \& perl \-Ilib \-MPath::FindDev=find_dev \e \& \-E "say find_dev(q{/home/kent/perl5/perlbrew/perls/perl\-5.19.3/lib/site_perl})" \& ... \& [Path::IsDev=0] Matching /home/kent/perl5 \& [Path::IsDev=0] \- ::IsDev::IgnoreFile => 1 : .path_isdev_ignore exists \& [Path::IsDev=0] \- ::IsDev::IgnoreFile => 1 : /home/kent/perl5/.path_isdev_ignore is a file \& [Path::IsDev=0] \- ::IsDev::IgnoreFile excludes path /home/kent/perl5 \& [Path::IsDev=0] no match found \& ... \& [Path::IsDev=0] Matching / \& ... \& [Path::IsDev=0] no match found .Ve .PP Success! .PP .Vb 2 \& [Path::IsDev=0] \- ::IsDev::IgnoreFile => 1 : .path_isdev_ignore exists \& [Path::IsDev=0] \- ::IsDev::IgnoreFile => 1 : /home/kent/perl5/.path_isdev_ignore is a file .Ve .SH HEURISTICS .IX Header "HEURISTICS" .SS "Negative Heuristics bundled with this distribution" .IX Subsection "Negative Heuristics bundled with this distribution" Just remember, a \fBNegative\fR Heuristic \fBexcludes the path it is associated with\fR .IP \(bu 4 \&\f(CW\*(C`IsDev::IgnoreFile\*(C'\fR \- \f(CW\*(C`.path_isdev_ignore\*(C'\fR .SS "Positive Heuristics bundled with this distribution" .IX Subsection "Positive Heuristics bundled with this distribution" .IP \(bu 4 \&\f(CW\*(C`Changelog\*(C'\fR \- Files matching \f(CW\*(C`Changes\*(C'\fR, \f(CW\*(C`Changelog\*(C'\fR, and similar, case insensitive, extensions optional. .IP \(bu 4 \&\f(CW\*(C`DevDirMarker\*(C'\fR \- explicit \f(CW\*(C`.devdir\*(C'\fR file to indicate a project root. .IP \(bu 4 \&\f(CW\*(C`META\*(C'\fR \- \f(CW\*(C`META.yml\*(C'\fR/\f(CW\*(C`META.json\*(C'\fR .IP \(bu 4 \&\f(CW\*(C`MYMETA\*(C'\fR \- \f(CW\*(C`MYMETA.yml\*(C'\fR/\f(CW\*(C`MYMETA.json\*(C'\fR .IP \(bu 4 \&\f(CW\*(C`Makefile\*(C'\fR \- Any \f(CW\*(C`Makefile\*(C'\fR format documented supported by GNU Make .IP \(bu 4 \&\f(CW\*(C`TestDir\*(C'\fR \- A directory called either \f(CW\*(C`t/\*(C'\fR or \f(CW\*(C`xt/\*(C'\fR .IP \(bu 4 \&\f(CW\*(C`Tool::DZil\*(C'\fR \- A \f(CW\*(C`dist.ini\*(C'\fR file .IP \(bu 4 \&\f(CW\*(C`Tool::MakeMaker\*(C'\fR \- A \f(CW\*(C`Makefile.PL\*(C'\fR file .IP \(bu 4 \&\f(CW\*(C`Tool::ModuleBuild\*(C'\fR \- A \f(CW\*(C`Build.PL\*(C'\fR file .IP \(bu 4 \&\f(CW\*(C`VCS::Git\*(C'\fR \- A \f(CW\*(C`.git\*(C'\fR directory .SH "HEURISTIC SETS" .IX Header "HEURISTIC SETS" .SS "Heuristic Sets Bundled with this distribution" .IX Subsection "Heuristic Sets Bundled with this distribution" .IP \(bu 4 \&\f(CW\*(C`Basic\*(C'\fR \- The basic heuristic set that contains most, if not all heuristics. .SH "ADVANCED USAGE" .IX Header "ADVANCED USAGE" .SS "Custom Sets" .IX Subsection "Custom Sets" \&\f(CW\*(C`Path::IsDev\*(C'\fR has a system of "sets" of Heuristics, in order to allow for pluggable and flexible heuristic types. .PP Though, for the vast majority of cases, this is not required. .PP .Vb 2 \& use Path::IsDev is_dev => { set => \*(AqBasic\*(Aq }; \& use Path::IsDev is_dev => { set => \*(AqSomeOtherSet\*(Aq , \-as => \*(Aqis_dev_other\*(Aq }; .Ve .SS "Overriding the default set" .IX Subsection "Overriding the default set" If for whatever reason the \f(CW\*(C`Basic\*(C'\fR set is insufficient, or if it false positives on your system for some reason, the "default" set can be overridden. .PP .Vb 1 \& export PATH_ISDEV_DEFAULT_SET="SomeOtherSet" \& \& ... \& use Path::IsDev qw( is_dev ); \& is_dev(\*(Aq/some/path\*(Aq) # uses SomeOtherSet .Ve .PP Though this will only take priority in the event the set is not specified during \f(CW\*(C`import\*(C'\fR .PP If this poses a security concern for the user, then this security hole can be eliminated by declaring the set you want in code: .PP .Vb 1 \& export PATH_ISDEV_DEFAULT_SET="SomeOtherSet" \& \& ... \& use Path::IsDev is_dev => { set => \*(AqBasic\*(Aq }; \& is_dev(\*(Aq/some/path\*(Aq) # uses Basic, regardless of ENV .Ve .SH SECURITY .IX Header "SECURITY" Its conceivable, than an evil user could construct an evil set, containing arbitrary and vulnerable code, and possibly stash that evil set in a poorly secured privileged users \f(CW@INC\fR .PP And if they managed to achieve that, if they could poison the privileged users \f(CW%ENV\fR, they could trick the privileged user into executing arbitrary code. .PP Though granted, if you can do either of those 2 things, you're probably security vulnerable anyway, and granted, if you could do either of those 2 things you could do much more evil things by the following: .PP .Vb 1 \& export PERL5OPT="\-MEvil::Module" .Ve .PP So with that in understanding, saying this modules default utility is "insecure" is mostly a bogus argument. .PP And to that effect, this module does nothing to "lock down" that mechanism, and this module encourages you to \fBNOT\fR force a set, unless you \fBNEED\fR to, and strongly suggests that forcing a set for the purpose of security will achieve no real improvement in security, while simultaneously reducing utility. .SH AUTHOR .IX Header "AUTHOR" Kent Fredric .SH "COPYRIGHT AND LICENSE" .IX Header "COPYRIGHT AND LICENSE" This software is copyright (c) 2017 by Kent Fredric . .PP This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.