File Coverage

blib/lib/App/Pods2Site/Args.pm
Criterion Covered Total %
statement 193 256 75.3
branch 40 106 37.7
condition 9 24 37.5
subroutine 36 42 85.7
pod 0 14 0.0
total 278 442 62.9


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