File Coverage

blib/lib/App/Pods2Site/Args.pm
Criterion Covered Total %
statement 185 248 74.6
branch 41 108 37.9
condition 6 21 28.5
subroutine 33 39 84.6
pod 0 11 0.0
total 265 427 62.0


line stmt bran cond sub pod time code
1             # Parses a commandline packaged as a list (e.g. normally just pass @ARGV)
2             # and processes it into real objects for later use by various functions
3             # in the Pods2Site universe
4             #
5             package App::Pods2Site::Args;
6            
7 2     2   12 use strict;
  2         4  
  2         54  
8 2     2   9 use warnings;
  2         3  
  2         125  
9            
10             our $VERSION = '1.001';
11             my $version = $VERSION;
12             $VERSION = eval $VERSION;
13            
14 2     2   1028 use App::Pods2Site::Util qw(slashify trim readData writeData expandAts $IS_PACKED $IS_WINDOWS $SHELL_ARG_DELIM $PATH_SEP);
  2         6  
  2         284  
15 2     2   807 use App::Pods2Site::SiteBuilderFactory;
  2         6  
  2         66  
16            
17 2     2   13 use Config;
  2         2  
  2         70  
18 2     2   476 use FindBin qw($RealBin $Script);
  2         1007  
  2         209  
19 2     2   1504 use Getopt::Long qw(GetOptionsFromArray :config require_order no_ignore_case bundling);
  2         21100  
  2         10  
20 2     2   461 use File::Spec;
  2         5  
  2         46  
21 2     2   17 use File::Basename;
  2         2  
  2         126  
22 2     2   1373 use File::Temp qw(tempdir);
  2         38627  
  2         125  
23 2     2   16 use File::Path qw(make_path);
  2         5  
  2         108  
24 2     2   13 use Config qw(%Config);
  2         4  
  2         54  
25 2     2   1053 use Pod::Usage;
  2         96647  
  2         280  
26 2     2   1292 use Pod::Simple::Search;
  2         12107  
  2         78  
27 2     2   1083 use List::MoreUtils qw(uniq);
  2         24901  
  2         13  
28 2     2   3490 use Grep::Query;
  2         90234  
  2         93  
29 2     2   1055 use POSIX;
  2         13115  
  2         13  
30            
31             # CTOR
32             #
33             sub new
34             {
35 3     3 0 10 my $class = shift;
36 3         23 my $version = shift;
37            
38 3         16 my $self = bless( {}, $class);
39 3         35 $self->__parseArgv($version, @_);
40            
41 3         14 return $self;
42             }
43            
44             sub getSiteDir
45             {
46 28     28 0 67 my $self = shift;
47            
48 28         113 return $self->{sitedir};
49             }
50            
51             sub getBinDirs
52             {
53 9     9 0 41 my $self = shift;
54            
55 9         26 return @{$self->{bindirs}};
  9         79  
56             }
57            
58             sub getLibDirs
59             {
60 9     9 0 27 my $self = shift;
61            
62 9         22 return @{$self->{libdirs}};
  9         137  
63             }
64            
65             sub getTitle
66             {
67 3     3 0 6 my $self = shift;
68            
69 3         80 return $self->{title};
70             }
71            
72             sub getStyle
73             {
74 3     3 0 20 my $self = shift;
75            
76 3         13 return $self->{style};
77             }
78            
79             sub getWorkDir
80             {
81 3     3 0 6 my $self = shift;
82            
83 3         13 return $self->{workdir};
84             }
85            
86             sub getGroupDefs
87             {
88 9     9 0 33 my $self = shift;
89            
90 9         65 return $self->{groupdefs};
91             }
92            
93             sub getCSS
94             {
95 6     6 0 19 my $self = shift;
96            
97 6         51 return $self->{css};
98             }
99            
100             sub getSiteBuilder
101             {
102 3     3 0 8 my $self = shift;
103            
104 3         10 return $self->{sitebuilder};
105             }
106            
107             sub isVerboseLevel
108             {
109 84     84 0 245 my $self = shift;
110 84         162 my $level = shift;
111            
112 84         974 return $self->{verbose} >= $level;
113             }
114            
115             # PRIVATE
116             #
117            
118             sub __parseArgv
119             {
120 3     3   9 my $self = shift;
121 3         11 my $version = shift;
122 3         31 my @argv = @_;
123            
124             # these options are persisted to the site
125             # and can't be used when updating
126             #
127 3         38 my @stickyOpts =
128             qw
129             (
130             bindirectory
131             libdirectory
132             group
133             css
134             style
135             title
136             );
137            
138 3         52 my %rawOpts =
139             (
140             usage => 0,
141             help => 0,
142             manual => 0,
143             v => 0,
144             workdirectory => undef,
145             quiet => 0,
146            
147             # hidden
148             #
149             _help => 0,
150             _pp => 0, # print basic PAR::Packer 'pp' command line
151             );
152            
153 3         35 my @specs =
154             (
155             'usage|?',
156             'help',
157             'manual',
158             'version',
159             'v|verbose+',
160             'workdirectory=s',
161             'quiet',
162             'bindirectory=s@',
163             'libdirectory=s@',
164             'group=s@',
165             'css=s',
166             'style=s',
167             'title=s',
168            
169             # hidden
170             #
171             '_help',
172             '_pp',
173             );
174            
175 3         10 my $_argsPodName = 'App/Pods2Site/_Args.pod';
176 3         65 my $_argsPodInput = Pod::Simple::Search->find($_argsPodName);
177 3         1949 my $argsPodName = 'App/Pods2Site/Args.pod';
178 3         22 my $argsPodInput = Pod::Simple::Search->find($argsPodName);
179 3         1287 my $manualPodName = 'App/Pods2Site.pod';
180 3         35 my $manualPodInput = Pod::Simple::Search->find($manualPodName);
181            
182             # for consistent error handling below, trap getopts problems
183             #
184             eval
185 3         1258 {
186 3         20 @argv = expandAts('.', @argv);
187 3     0   50 local $SIG{__WARN__} = sub { die(@_) };
  0         0  
188 3         24 GetOptionsFromArray(\@argv, \%rawOpts, @specs)
189             };
190 3 50       3875 if ($@)
191             {
192 0         0 pod2usage(-input => $argsPodInput, -message => "Failure parsing options:\n $@", -exitval => 255, -verbose => 0);
193             }
194            
195             # help with the hidden flags...
196             #
197 3 50       11 pod2usage(-input => $_argsPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{_help};
198            
199             # for the special selection of using --_pp, print command line and exit
200             #
201 3 50       12 if ($rawOpts{_pp})
202             {
203 0         0 $self->__print_pp_cmdline
204             (
205             $version,
206             $argsPodName, $argsPodInput,
207             $manualPodName, $manualPodInput
208             );
209 0         0 exit(0);
210             }
211            
212             # if any of the doc switches made, display the pod
213             #
214 3 50       12 pod2usage(-input => $manualPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{manual};
215 3 50       10 pod2usage(-input => $argsPodInput, -exitval => 0, -verbose => 2, -noperldoc => 1) if $rawOpts{help};
216 3 50       9 pod2usage(-input => $argsPodInput, -exitval => 0, -verbose => 0) if $rawOpts{usage};
217 3 50       14 pod2usage(-message => slashify($0) . " version $App::Pods2Site::VERSION", -exitval => 0, -verbose => 99, -sections => '_') if $rawOpts{version};
218            
219             # if -quiet has been given, it trumps any verbosity
220             #
221 3 50       34 $self->{verbose} = $rawOpts{quiet} ? -1 : $rawOpts{v};
222            
223             # manage the sitedir
224             # assume we need to create it
225             #
226 3         10 my $newSiteDir = 1;
227 3         18 my $sitedir = $self->__getSiteDir($argv[0]);
228 3 50       12 die("You must provide a sitedir (use ':std' for a default location)\n") unless $sitedir;
229 3         43 $sitedir = slashify(File::Spec->rel2abs($sitedir));
230 3 100       68 if (-e $sitedir)
231             {
232 1         18 $newSiteDir = 0;
233            
234             # if the sitedir exists as a dir, our sticky opts better be found in it
235             # otherwise it's not a sitedir
236             #
237 1 50       18 die("The output '$sitedir' exists, but is not a directory\n") unless -d $sitedir;
238 1         10 my $savedOpts = readData($sitedir, 'opts');
239 1 50       12 die("The sitedir '$sitedir' exists, but is missing our marker file\n") unless $savedOpts;
240            
241             # clean up any sticky opts given by the user
242             #
243 1 50       5 print "NOTE: updating '$sitedir' - reusing options used when created!\n" if $self->isVerboseLevel(0);
244 1         12 foreach my $opt (@stickyOpts)
245             {
246 6 50       17 warn("WARNING: The option '$opt' ignored when updating the existing site '$sitedir'\n") if exists($rawOpts{$opt});
247 6         10 delete($rawOpts{$opt});
248             }
249 1         19 %rawOpts = ( %rawOpts, %$savedOpts );
250             }
251             else
252             {
253 2 50       11 print "Creating '$sitedir'...\n" if $self->isVerboseLevel(0);
254             }
255            
256             # fix up any user given bindir locations or get us the standard ones
257             #
258 3         32 my @bindirs = uniq($self->__getBinLocations($rawOpts{bindirectory}));
259 3 50       24 warn("WARNING: No bin directories found\n") unless @bindirs;
260 3         13 $self->{bindirs} = $rawOpts{bindirectory} = \@bindirs;
261            
262             # fix up any user given libdir locations or get us the standard ones
263             #
264 3         14 my @libdirs = uniq($self->__getLibLocations($rawOpts{libdirectory}));
265 3 50       42 warn("WARNING: No lib directories found\n") unless @libdirs;
266 3         16 $self->{libdirs} = $rawOpts{libdirectory} = \@libdirs;
267            
268 3         8 my $workdir;
269 3 50       9 if ($rawOpts{workdirectory})
270             {
271             # if user specifies a workdir this implies that it should be kept
272             # just make sure there is no such directory beforehand, and create it here
273             # (similar to below; tempdir() will also create one)
274             #
275 0         0 $workdir = slashify(File::Spec->rel2abs($rawOpts{workdirectory}));
276 0 0       0 die("The workdir '$workdir' already exists\n") if -e $workdir;
277 0 0       0 make_path($workdir) or die("Failed to create workdir '$workdir': $!\n");
278             }
279             else
280             {
281             # create a temp dir; use automatic cleanup
282             #
283 3         44 $workdir = slashify(tempdir("pods2site-XXXX", TMPDIR => 1, CLEANUP => 1));
284             }
285 3         13 $self->{workdir} = $workdir;
286            
287             # Ensure we have group definitions, and test queries before storing
288             #
289 3         26 my @rawGroupDefs = $self->__getRawGroupDefs($rawOpts{group});
290 3         9 my @groupDefs;
291             my %groupsSeen;
292 3         13 foreach my $rawGroupDef (@rawGroupDefs)
293             {
294             eval
295 6         14 {
296 6 50       69 die("Group definition not in form 'name=query': '$rawGroupDef'\n") unless $rawGroupDef =~ /^([^=]+)=(.+)/s;
297 6         40 my ($name, $query) = (trim($1), trim($2));
298 6 50       25 die("Group '$name' multiply defined\n") if $groupsSeen{$name};
299 6         19 $groupsSeen{$name} = 1;
300 6         59 push(@groupDefs, { name => $name, query => Grep::Query->new($query) });
301             };
302 6 50       86139 pod2usage(-message => "Problem with group definition '$rawGroupDef':\n $@", -exitval => 255, -verbose => 0) if $@;
303             }
304 3         18 $rawOpts{group} = \@rawGroupDefs;
305 3         13 $self->{groupdefs} = \@groupDefs;
306            
307             # fix up any css path given by user
308             #
309 3 50       14 if ($rawOpts{css})
310             {
311 0         0 my $css = slashify(File::Spec->rel2abs($rawOpts{css}));
312 0 0       0 die("No such file: -css '$css'\n") unless -f $css;
313 0         0 $self->{css} = $css;
314             }
315            
316 3   66     145 $rawOpts{title} = $rawOpts{title} || ($Config{myuname} ? "Pods2Site : $Config{myuname}" : 'Pods2Site');
317 3         12 $self->{title} = $rawOpts{title};
318            
319 3         9 $self->{style} = $rawOpts{style};
320 3         59 my $sbf = App::Pods2Site::SiteBuilderFactory->new($rawOpts{style});
321 3         16 $self->{style} = $sbf->getRealStyle();
322 3         19 $self->{sitebuilder} = $sbf->createSiteBuilder();
323            
324             # if we need to create the site dir...
325             #
326 3 100       16 if ($newSiteDir)
327             {
328             # ...do it and persist the sticky options
329             #
330 2 50       479 make_path($sitedir) || die("Failed to create sitedir '$sitedir': $!\n");
331 2         11 my %opts2save = map { $_ => $rawOpts{$_} } @stickyOpts;
  12         43  
332 2         13 writeData($sitedir, 'opts', \%opts2save);
333             }
334            
335 3         54 $self->{sitedir} = $sitedir;
336             }
337            
338             sub __getSiteDir
339             {
340 3     3   8 my $self = shift;
341 3         7 my $sitedir = shift;
342            
343 3 50 33     30 if ($sitedir && $sitedir eq ':std')
344             {
345 0 0       0 die("Sorry, don't have a ':std' directory when running a packed binary\n") if $IS_PACKED;
346 0         0 $sitedir = slashify((dirname(dirname($^X)) . '/pods2site'));
347             }
348            
349 3         10 return $sitedir;
350             }
351            
352             sub __getRawGroupDefs
353             {
354 3     3   10 my $self = shift;
355 3         7 my $groupDefs = shift;
356            
357 3 50 33     34 my @newDefs = ($groupDefs && @$groupDefs) ? @$groupDefs : ':std';
358 3         8 my $ndx = 0;
359 3         15 while ($ndx <= $#newDefs)
360             {
361 6 50       19 if ($newDefs[$ndx] =~ /^:/)
362             {
363 0         0 splice(@newDefs, $ndx, 1, $self->__getInternalRawGroupDefs($newDefs[$ndx]));
364             }
365             else
366             {
367 6         16 $ndx++;
368             }
369             }
370            
371 3         13 return @newDefs;
372             }
373            
374             sub __getInternalRawGroupDefs
375             {
376 0     0   0 my $self = shift;
377 0         0 my $internal = shift;
378            
379 0         0 my @groupDefs;
380 0 0       0 if ($internal eq ':std')
    0          
    0          
    0          
    0          
381             {
382 0         0 @groupDefs = qw(:std-core :std-scripts :std-pragmas :std-modules);
383             }
384             elsif ($internal eq ':std-core')
385             {
386 0         0 @groupDefs = <<'CORE',
387             Core=
388             /*
389             Select any pods in library/pod locations that are named with prefix 'perl'
390             */
391             type.eq(corepod)
392             CORE
393             }
394             elsif ($internal eq ':std-scripts')
395             {
396 0         0 @groupDefs = <<'SCRIPTS',
397             Scripts=
398             /*
399             Assume all pods in bin locations are scripts. Also add the PAR::Packer
400             'pp' pod; while there is a pp script it has no pod docs, they're in
401             the toplevel pp.pm.
402             */
403             type.eq(bin) || name.eq(pp)
404             SCRIPTS
405             }
406             elsif ($internal eq ':std-pragmas')
407             {
408 0         0 @groupDefs = <<'PRAGMAS',
409             Pragmas=
410             /*
411             Select any pods in library locations that are named with a lower-case
412             initial in their package name and consider them pragmas.
413             Avoid those pods picked up by the Core/Script groups.
414             */
415             type.eq(lib) &&
416             name.regexp{^[a-z]} &&
417             NOT name.eq(pp)
418             PRAGMAS
419             }
420             elsif ($internal eq ':std-modules')
421             {
422 0         0 @groupDefs = <<'MODULES'
423             Modules=
424             /*
425             Any pods not selected by the other three are assumed to
426             be 'normal' modules.
427             */
428             NOT
429             (
430             type.eq(bin) ||
431             type.eq(corepod) ||
432             name.eq(pp) ||
433             name.regexp{^[a-z]}
434             )
435             MODULES
436             }
437             else
438             {
439 0         0 die("Unknown internal group definition: '$internal'\n");
440             }
441            
442 0         0 return @groupDefs;
443             }
444            
445             sub __getBinLocations
446             {
447 3     3   7 my $self = shift;
448 3         6 my $argLocs = shift;
449            
450             # if the user provided any bin locations, interpret them
451             # otherwise return the default places
452             #
453 3         6 my @locs;
454 3 50       11 if (defined($argLocs))
455             {
456 3         16 foreach my $loc (@$argLocs)
457             {
458 3 50 33     29 if (defined($loc) && length($loc) > 0)
459             {
460 3 50       19 if ($loc eq ':std')
    50          
    50          
461             {
462 0         0 push(@locs, $self->__getDefaultBinLocations());
463             }
464             elsif ($loc eq ':path')
465             {
466 0         0 push(@locs, split(/\Q$PATH_SEP\E/, $ENV{PATH}));
467             }
468             elsif ($loc eq ':none')
469             {
470             # do nothing
471             }
472             else
473             {
474 3 50       68 push(@locs, $loc) if -d $loc;
475             }
476             }
477             }
478             }
479             else
480             {
481 0         0 @locs = $self->__getDefaultBinLocations();
482             }
483            
484             # ensure all paths are absolute and clean
485             #
486 3         50 $_ = slashify(File::Spec->rel2abs($_)) foreach (@locs);
487            
488 3         27 return @locs;
489             }
490            
491             sub __getDefaultBinLocations
492             {
493 0     0   0 my $self = shift;
494            
495             # a somewhat guessed list for Config keys for scripts...
496             # note: order is important
497             #
498 0         0 return $self->__getConfigLocations
499             (
500             qw
501             (
502             installsitebin
503             installsitescript
504             installvendorbin
505             installvendorscript
506             installbin
507             installscript
508             )
509             );
510             }
511            
512             sub __getLibLocations
513             {
514 3     3   7 my $self = shift;
515 3         6 my $argLocs = shift;
516            
517             # if the user provided any lib locations, interpret them
518             # otherwise return the default places
519             #
520 3         5 my @locs;
521 3 50       12 if (defined($argLocs))
522             {
523 3         19 foreach my $loc (@$argLocs)
524             {
525 3 50 33     22 if (defined($loc) && length($loc) > 0)
526             {
527 3 50       20 if ($loc eq ':std')
    50          
    50          
528             {
529 0         0 push(@locs, $self->__getDefaultLibLocations());
530             }
531             elsif ($loc eq ':inc')
532             {
533 0         0 push(@locs, @INC);
534             }
535             elsif ($loc eq ':none')
536             {
537             # do nothing
538             }
539             else
540             {
541 3 50       56 push(@locs, $loc) if -d $loc;
542             }
543             }
544             }
545             }
546             else
547             {
548 0         0 @locs = $self->__getDefaultLibLocations();
549             }
550            
551             # ensure all paths are absolute and clean
552             #
553 3         42 $_ = slashify(File::Spec->rel2abs($_)) foreach (@locs);
554            
555 3         19 return @locs;
556             }
557            
558             sub __getDefaultLibLocations
559             {
560 0     0     my $self = shift;
561            
562             # a somewhat guessed list for Config keys for lib locations...
563             # note: order is important
564             #
565 0           return $self->__getConfigLocations
566             (
567             qw
568             (
569             installsitearch
570             installsitelib
571             installvendorarch
572             installvendorlib
573             installarchlib
574             installprivlib
575             )
576             );
577             }
578             sub __getConfigLocations
579             {
580 0     0     my $self = shift;
581 0           my @cfgnames = @_;
582            
583             # the keys don't always contain anything useful
584             #
585 0           my @locs;
586 0           foreach my $loc (@cfgnames)
587             {
588 0           my $cfgloc = $Config{$loc};
589 0 0 0       if ( defined($cfgloc)
      0        
590             && length($cfgloc) > 0
591             && -d $cfgloc)
592             {
593 0           push(@locs, $cfgloc);
594             }
595             }
596            
597 0           return @locs;
598             }
599            
600             sub __print_pp_cmdline
601             {
602 0     0     my $self = shift;
603 0           my $version = shift;
604 0           my $argsPodName = shift;
605 0           my $argsPodInput = shift;
606 0           my $manualPodName = shift;
607 0           my $manualPodInput = shift;
608            
609 0 0         die("Sorry, you're already running a binary/packed instance\n") if $IS_PACKED;
610            
611 0           eval "require PAR::Packer";
612 0 0         warn("Sorry, it appears PAR::Packer is not installed/working!\n") if $@;
613            
614 0 0         my $os = $IS_WINDOWS ? 'windows' : $^O;
615 0           my $arch = (POSIX::uname())[4];
616 0 0         my $exeSuffix = $IS_WINDOWS ? '.exe' : '';
617 0           my $bnScript = basename($Script);
618 0           my $output = "$bnScript-$version-$os-$arch$exeSuffix";
619 0 0         my @liblocs = map { $_ ne '.' ? ('-I', slashify(File::Spec->rel2abs($_))) : () } @INC;
  0            
620 0           my @cmd =
621             (
622             'pp',
623             @liblocs,
624             '-a', "$argsPodInput;lib/$argsPodName",
625             '-a', "$manualPodInput;lib/$manualPodName",
626             '-o', $output,
627             slashify("$RealBin/$Script")
628             );
629            
630 0           my $cmdline = '';
631 0           $cmdline .= "$SHELL_ARG_DELIM$_$SHELL_ARG_DELIM " foreach (@cmd);
632 0           chop($cmdline);
633 0           print "$cmdline\n";
634             }
635            
636             1;