File Coverage

blib/lib/Scriptalicious.pm
Criterion Covered Total %
statement 249 618 40.2
branch 109 362 30.1
condition 33 136 24.2
subroutine 38 95 40.0
pod 33 53 62.2
total 462 1264 36.5


line stmt bran cond sub pod time code
1              
2             # Copyright 2005-2008, Sam Vilain. All rights reserved. This program
3             # is free software; you can use it and/or distribute it under the same
4             # terms as Perl itself; either the latest stable release of Perl when
5             # the module was written, or any subsequent stable release.
6             #
7             # Please note that this applies retrospectively to all Scriptalicious
8             # releases; apologies for the lack of an explicit license.
9              
10             package Scriptalicious;
11              
12 39     39   489285 use 5.006;
  39         137  
  39         1619  
13 39     39   216 use strict;
  39         78  
  39         1191  
14 39     39   180 use warnings;
  39         86  
  39         1195  
15 39     39   188 use Carp qw(croak);
  39         80  
  39         3511  
16              
17             our $VERSION = "1.17";
18              
19 39     39   47533 use Getopt::Long;
  39         745329  
  39         237  
20 39     39   6751 use base qw(Exporter);
  39         90  
  39         5733  
21              
22             BEGIN {
23             # export groups, phtoey!
24              
25 39     39   3464 our @EXPORT = qw(say mutter whisper abort moan barf run run_err
26             capture capture_err getopt $VERBOSE $PROGNAME
27             $CONFIG
28             start_timer show_delta show_elapsed getconf
29             getconf_f sci_unit prompt_for prompt_passwd
30             prompt_yn prompt_Yn prompt_yN prompt_string
31             prompt_nY prompt_Ny prompt_ny
32             prompt_int tsay anydump prompt_regex prompt_sub
33             prompt_file hush_exec unhush_exec
34             getopt_lenient time_unit
35             );
36             }
37              
38             # define this in subclasses where appropriate
39 0     0   0 sub __package__ { __PACKAGE__ }
40              
41             our ($VERBOSE, $closure, $SHOW_CMD_VERBOSE, $gotconf);
42             $VERBOSE = 0;
43             $SHOW_CMD_VERBOSE = 1;
44              
45             #---------------------------------------------------------------------
46             # parse import arguments and export symbols
47             #---------------------------------------------------------------------
48             sub import {
49 58     58   12315 my $pkg = shift;
50 39     39   238 no strict 'refs';
  39         83  
  39         9688  
51              
52             # look for options in the importer arguments
53 58         658 for ( my $i = 0; $i < $#_; $i++ ) {
54 5 50       134 if ( $_[$i] =~ m/^-(.*)/ ) {
55 5         43 die "Bad option `$1' from $pkg"
56 5 50       9 unless *{uc($1)}{SCALAR};
57 5 50       18 my $x = uc($1); ($x eq "VERSION") && ($x="main::$x");
  5         22  
58 5         15 ${$x} = $_[$i+1];
  5         11  
59 5         29 (@_) = (@_[0..($i-1)], @_[($i+2)..$#_]);
60 5         16 $i--;
61             }
62             }
63              
64 58         137 unshift @_, $pkg;
65 58         40128 goto &Exporter::import;
66             }
67              
68             # automatically guess the program name if called for
69             (our $PROGNAME = $0) =~ s{.*/}{} unless $PROGNAME;
70             our $CONFIG;
71              
72             BEGIN {
73 39     39   171 Getopt::Long::config("bundling", "pass_through");
74             }
75              
76 20 50   20   11512 END { $closure->() if $closure }
77              
78             sub getopt_lenient {
79 3     3 1 6 local($closure) = \&show_usage;
80              
81 3         9 $gotconf = 1;
82             Getopt::Long::GetOptions
83             (
84             'help|h' => \&show_help,
85 3     3   1719 'verbose|v' => sub { $VERBOSE++ },
86 0     0   0 'quiet|q' => sub { $VERBOSE = -1 },
87 0     0   0 'debug|d' => sub { $VERBOSE = 2 },
88 3         39 'version|V' => \&show_version,
89             @_,
90             );
91              
92             # check for unknown arguments and print a nice error message
93             # instead of the nasty default Getopt::Long message
94              
95 3 50 33     609 shift @ARGV, return if $#ARGV >= 0 and $ARGV[0] eq "--";
96              
97             }
98              
99             sub getopt {
100 3     3 1 1515 local($closure) = \&show_usage;
101              
102 3         15 getopt_lenient(@_);
103              
104 3 50 33     21 abort("unrecognised option: $ARGV[0]")
105             if $#ARGV >= 0 and $ARGV[0] =~ m/^-/;
106             }
107              
108 0 0   0 1 0 sub say { _autoconf() unless $gotconf;
109 0 0       0 print "$PROGNAME: @_\n" unless $VERBOSE < 0 }
110 0 0   0 1 0 sub mutter { say @_ if $VERBOSE }
111 10 50   10 1 32 sub whisper { say @_ if $VERBOSE > 1 }
112 1 50   1   4 sub _err_say { _autoconf() unless $gotconf;
113 1         9 print STDERR "$PROGNAME: @_\n" }
114 0     0 1 0 sub abort { _err_say "aborting: @_"; &show_usage; }
  0         0  
115 1     1 1 10 sub moan { _err_say "warning: @_" }
116 0     0 1 0 sub protest { _err_say "error: @_" }
117 0 0   0 1 0 sub barf { if($^S){die @_}else{ _err_say "ERROR: @_"; exit(1); } }
  0         0  
  0         0  
  0         0  
118 0     0   0 sub _autoconf { getopt_lenient( eval{ my @x = getconf(@_); @x } ) }
  0         0  
  0         0  
119              
120             #---------------------------------------------------------------------
121             # helpers for running commands and/or capturing their output
122             #---------------------------------------------------------------------
123             our (@output, $next_cmd_no_hide, $next_cmd_capture);
124              
125             # use Shell::QuoteEscape? nah :-)
126             my %map = ((map { chr($_) => sprintf("\\%.3o",$_) } (0..31, 127..255)),
127             " "=>" ","\t"=>"\\t","\r"=>"\\r","\n"=>"\\n",
128             "\""=>"\\\"");
129             sub shellquote {
130 0 0       0 return join(" ",map { m/[\s\']/ && do {
  0         0  
131 0         0 s/[\0-\031"\s\177-\377]/$map{$&}/eg;
  0         0  
132 0         0 $_ = "\"$_\"";
133 0     0 0 0 }; $_ } map { $_ } @_);
  0         0  
134             }
135              
136             our @SHOW_CMD_VERBOSE;
137             sub hush_exec {
138 0     0 1 0 push @SHOW_CMD_VERBOSE, $SHOW_CMD_VERBOSE;
139 0         0 $SHOW_CMD_VERBOSE=2;
140             }
141             sub unhush_exec {
142 0     0 1 0 $SHOW_CMD_VERBOSE = pop @SHOW_CMD_VERBOSE;
143             }
144              
145             our @last_cmd;
146             sub run {
147 127     127 1 2947 &run_err(@_);
148 101         1759 @_ = @last_cmd;
149 101         507 my $start = $#output - 10;
150 101 100       2399 chomp($output[$#output]) if @output;
151 101 50       962 $start = 0 if $start < 0;
152 101 0 0     2981 barf(
    0          
    0          
    0          
    0          
    50          
153             (ref $_[0] ? "Sub-process " : "Command `".shellquote(@_)."' ").
154             (($? >> 8)
155             ? "exited with error code ".($?>>8)
156             : "killed by signal $?")
157             .(($VERBOSE >= $SHOW_CMD_VERBOSE or $next_cmd_no_hide) ? ""
158             : (($start != 0
159             ? "\nlast lines of output:\n"
160             : "\nprogram output:\n")
161             .join("", @output[$start .. $#output])
162             .($start != 0
163             ? "(use -v to show complete program output)"
164             : "")))
165             ) if ($?);
166             }
167              
168             sub do_fork {
169 153     153 0 499 @output = ();
170 153 50 33     1272 if (not $next_cmd_capture and
      66        
171             ( $VERBOSE >= $SHOW_CMD_VERBOSE or $next_cmd_no_hide )) {
172             return fork()
173 0         0 } else {
174 153         202533 my $pid = open CHILD, "-|";
175 153 100 66     12209 if (defined($pid) && !$pid) {
176 30         7879 open STDERR, ">&STDOUT";
177             }
178 153         11536 return $pid;
179             }
180             }
181              
182             sub _waitpid {
183 123     123   1154 my $pid = shift;
184              
185 123 50 33     1786 if (not $next_cmd_capture and
      66        
186             ($VERBOSE >= $SHOW_CMD_VERBOSE or $next_cmd_no_hide)) {
187 0         0 waitpid($pid, 0);
188             } else {
189 123         163243404 while (my $line = <CHILD>) {
190 201         266782 push @output, $line;
191             }
192 123         23764 close CHILD;
193             }
194             }
195              
196             sub _load_hires {
197 309 100   309   1251 return if defined &gettimeofday;
198 36     36   66447 eval "use Time::HiRes qw(gettimeofday tv_interval)";
  36         107690  
  36         209  
  36         2538  
199 0     0   0 *gettimeofday = sub { return time() }
200 36 50       6745 unless defined &gettimeofday;
201 0     0   0 *tv_interval = sub { return ${$_[0]}[0] - ${$_[1]}[0] }
  0         0  
  0         0  
202 36 50       193 unless defined &tv_interval;
203             }
204              
205             sub run_err {
206 153     153 1 354 my %fds;
207 153         408 my $fd_desc = "";
208 153   33     3950 while ( $_[0] and !ref $_[0] and $_[0]=~/^-(in|out|rw)(\d+)?$/ ) {
      66        
209 150         372 shift;
210 150 50       1050 my $mode = ($1 eq "in" ? "<" : ($1 eq "out" ? ">" : "+<") );
    100          
211 150   66     1245 my $fd = $2 || ($1 eq "out" ? 1 : 0);
212 150         1086 $fds{"$fd"} = [ $mode, shift ];
213 150 100       2070 $fd_desc .= ($fd_desc ? ", " : "") . "fd$fd=$mode$fds{$fd}";
214             }
215 153         1328 @last_cmd = @_;
216 153 50       650 if ( $VERBOSE >= $SHOW_CMD_VERBOSE ) {
217 0 0       0 say("running `".shellquote(@last_cmd)."'"
    0          
    0          
218             .($next_cmd_capture
219             ? " (captured)"
220             : "")
221             .($fd_desc?"($fd_desc)":"")
222             ) unless ref($_[0]);
223             }
224 153         499 _load_hires;
225              
226 153         1589 my $start = start_timer();
227 153         1478 my $output;
228              
229 153 100       690 if (my $pid = do_fork) {
230              
231 123     0   14775 local $SIG{INT} = sub { kill 2, $pid };
  0         0  
232 123         1826 $output = &_waitpid($pid);
233              
234             } else {
235 30 50       1637 barf "Fork failed; $!" if not defined $pid;
236 30 100       1909 setup_fds(\%fds) if $fd_desc;
237 21 50       529 if (ref $_[0]) {
238 0         0 my $code = shift;
239 0         0 $code->(@_);
240 0         0 exit(0);
241             } else {
242 21 0       0 exec(@_) ||
243             barf "exec failed; $!";
244             }
245             }
246              
247 123 50       802 if ( $VERBOSE >= $SHOW_CMD_VERBOSE ) {
248 0         0 say sprintf("command completed in ".show_elapsed($start))
249             }
250              
251 123         2969 return $?
252              
253             }
254              
255             sub capture {
256 124     124 1 333137 local($next_cmd_capture) = 1;
257 124         848 run(@_);
258 99 100       3544 return (wantarray ? @output : join("", @output));
259             }
260              
261             sub capture_err {
262 26     26 1 12043 local($next_cmd_capture) = 1;
263 26         160 my $rv = run_err(@_);
264 22         717 return ($rv, @output)
265             }
266              
267             sub capture2 {
268 0     0 0 0 die "capture2 not implemented yet"
269             }
270              
271             our $DATA = join "", <DATA>; close DATA;
272 39 50   39   2639 our ($AUTOLOAD, $l);sub AUTOLOAD{croak"No such function $AUTOLOAD"if
273 39         616 $l;(undef,my($f,$n))=ll();$n+=1;eval"package ".__PACKAGE__.";\n"
  39         170  
  39         91253  
274 39 50       208 ."# line $n \"$f\"\n$DATA"; $@&&die"Error in autoload: $@";
275 39     39 0 100 $l=1;goto &{$AUTOLOAD};}sub ll{sub{caller()}->();} "P E A C E";
  39     39   105  
  39         315  
  39         211  
  39         228  
276             __DATA__
277              
278             our ($NAME, $SHORT_DESC, $SYNOPSIS, $DESCRIPTION, @options);
279              
280             #---------------------------------------------------------------------
281             # get the synopsis, etc, from the calling script.
282             #---------------------------------------------------------------------
283 0 0   0   0 sub _get_pod_usage {
284 0         0 return if $SYNOPSIS;
285 0 0       0 our $level;
286 0         0 open SCR_POD, $0 or warn "failed to open $0 for reading; $!";
287             my $targ;
288 0         0 my $in_options;
289 0         0 my $name_desc;
290 0         0 local($_);
291 0 0 0     0 while (<SCR_POD>) {
292 0         0 if ( !m{^=} and $targ ) {
293             $$targ .= $_;
294 0 0       0 }
    0          
    0          
    0          
    0          
    0          
295 0         0 if ( m{^=encoding (\w+)} ) {
296             binmode SCR_POD, ":$1";
297             }
298 0         0 elsif ( m{^=head\w\s+SYNOPSIS\s*$} ) {
299             $targ = \$Scriptalicious::SYNOPSIS;
300             }
301 0         0 elsif ( m{^=head\w\s+DESCRIPTION\s*$} ) {
302             $targ = \$Scriptalicious::DESCRIPTION;
303             }
304 0         0 elsif ( m{^=head\w\s+NAME\s*$} ) {
305             $targ = \$name_desc;
306             }
307 0         0 elsif ( m{^=head\w\s+COMMAND[\- ]LINE OPTIONS\s*$} ) {
308 0         0 undef($targ);
309             $in_options = 1;
310             }
311 0 0       0 elsif ( $in_options ) {
    0          
    0          
312 0         0 if ( m{^=over} ) {
313             $level++
314             }
315 0 0       0 elsif ( m{^=item\s+(.*)} ) {
316 0         0 next unless $level == 1;
317 0         0 my $switches = $1;
318 0         0 $switches =~ s{[BCI]<([^>]*)>}{$1}g;
319 0         0 my (@switches, $longest);
320 0         0 $longest = "";
321             for my $switch
322             ($switches =~ m/\G
323             ((?:-\w|--\w+))
324             (?:,\s*)?
325 0         0 /gx) {
326 0 0       0 push @switches, $switch;
327 0         0 if ( length $switch > length $longest) {
328             $longest = $switch;
329             }
330 0         0 }
331 0         0 $longest =~ s/^-*//;
332             my $opt_hash = {
333             options => \@switches,
334             description => "",
335 0         0 };
336 0         0 $targ = \$opt_hash->{description};
337             push @options, $longest, $opt_hash;
338             }
339 0 0       0 elsif ( m{^=back} ) {
340 0         0 if ( --$level == 0 ) {
341             undef($in_options);
342             }
343             }
344             }
345 0 0       0 }
346 0         0 if ( $name_desc ) {
347 0   0     0 $name_desc =~ m{^(\S+)(?:\s+-\s+(.*))?$};
348 0   0     0 $PROGNAME ||= $1;
349             $SHORT_DESC ||= $2;
350             }
351 0         0  
352 0   0     0 foreach ( $SYNOPSIS, $SHORT_DESC, $DESCRIPTION ) {
353             $_ ||= "(not found in POD)";
354             }
355             }
356              
357 0     0 0 0 sub short_usage {
358 0 0       0 _get_pod_usage;
359             return ("Usage: $SYNOPSIS\n"
360             ."Try "
361             .($SHORT_DESC
362             ? "`$PROGNAME --help' for a summary of options."
363             : "`perldoc $0' for more information")
364             ."\n");
365             }
366              
367 0     0 0 0 sub usage {
368 0 0       0 _get_pod_usage;
369 0         0 if ( !$SHORT_DESC ) {
370             moan("failed to extract usage information from POD; calling "
371 0 0       0 ."perldoc");
372             exec("perldoc", $0) ||
373             barf "exec failed; $!";
374             }
375 0         0  
376 0 0   0   0 eval "use Text::Wrap qw(wrap fill)";
  0         0  
377 0 0   0   0 *wrap = sub { return join "", @_ } unless defined &wrap;
  0         0  
378             *fill = sub { return join "", @_ } unless defined &fill;
379 0         0  
380 0         0 my $TOTAL_WIDTH;
381 0 0       0 eval "use Term::ReadKey;";
382 0         0 if ( defined &GetTerminalSize ) {
383             $TOTAL_WIDTH = (GetTerminalSize())[0] - 10;
384 0   0     0 }
385             $TOTAL_WIDTH ||= 70;
386 0         0  
387 0         0 my $options_string;
388 0         0 my $OPTIONS_INDENT = 2;
389 0         0 my $OPTIONS_WIDTH = 20;
390             my $OPTIONS_GAP = 2;
391 0         0  
392             my $DESCRIPTION_WIDTH = ($TOTAL_WIDTH - $OPTIONS_GAP -
393             $OPTIONS_INDENT - $OPTIONS_WIDTH);
394              
395             # go through each option, and format it for the screen
396 0         0  
397 0         0 for ( my $i = 0; $i < (@options>>1); $i ++ ) {
398             my $option = $options[$i*2 + 1];
399 0         0  
400 0         0 $Text::Wrap::huge = "overflow";
401 0         0 $Text::Wrap::columns = $OPTIONS_WIDTH;
  0         0  
402             my @lhs = map { split /\n/ }
403 0         0 wrap("","",join ", ",
404 0         0 sort { length $a <=> length $b }
405             @{$option->{options}});
406 0         0  
407 0         0 $Text::Wrap::huge = "wrap";
408 0         0 $Text::Wrap::columns = $DESCRIPTION_WIDTH;
  0         0  
409             my @rhs = map { split /\n/ }
410             fill("","",$option->{description});
411 0   0     0  
412 0         0 while ( @lhs or @rhs ) {
413 0         0 my $left = shift @lhs;
414 0   0     0 my $right = shift @rhs;
415 0   0     0 $left ||= "";
416 0         0 $right ||= "";
417 0         0 chomp($left);
418             $options_string .= join
419             ("",
420             " " x $OPTIONS_INDENT,
421             $left . (" " x ($OPTIONS_WIDTH - length $left)),
422             " " x $OPTIONS_GAP,
423             $right,
424             "\n");
425             }
426             }
427 0         0  
428 0         0 $Text::Wrap::huge = "overflow";
429             $Text::Wrap::columns = $TOTAL_WIDTH;
430 0         0  
431 0         0 $DESCRIPTION =~ s{\n\n}{\n\n<-->\n\n}gs;
432 0         0 $DESCRIPTION = fill(" ", " ", $DESCRIPTION);
433             $DESCRIPTION =~ s{^.*<-->.*$}{}mg;
434 0         0  
435             return (fill("","",$PROGNAME . " - " . $SHORT_DESC)
436             ."\n\n"
437             ."Usage: ".$SYNOPSIS."\n\n"
438             .$DESCRIPTION."\n\n"
439             .fill(""," ","Command line options:")
440             ."\n\n"
441             .$options_string."\n"
442             ."See `perldoc $0' for more information.\n\n");
443              
444             }
445              
446 0     0 0 0 sub show_usage {
447 0         0 print STDERR &short_usage;
448             exit(1);
449             }
450              
451 0 0   0 0 0 sub show_version {
452             print "This is ".$PROGNAME.", "
453             .( defined($main::VERSION)
454             ? "version ".$main::VERSION."\n"
455             : "with no version, so stick it up your source repository!\n" );
456 0         0  
457             exit(0);
458             }
459              
460 0     0 0 0 sub show_help {
461 0         0 print &usage;
462             exit(0);
463             }
464              
465             my ($start, $last);
466 156     156 1 511 sub start_timer {
467             _load_hires();
468 156 100       528  
469 3         36 if ( !defined wantarray ) {
470             $last = $start = [gettimeofday()];
471 153         1291 } else {
472             return [gettimeofday()];
473             }
474             }
475              
476 1   33 1 1 21 sub show_elapsed {
477             my $e = tv_interval($_[0]||$start, [gettimeofday()]);
478 1         17  
479             return time_unit($e, 3);
480             }
481              
482 1     1 1 6127 sub show_delta {
483 1   33     40 my $now;
484 1         15 my $e = tv_interval($_[0]||$last, $now = [gettimeofday()]);
485 1         16 $last = $now;
486             return time_unit($e, 3);
487             }
488 39     39   40248  
  39         297342  
  39         345  
489             use POSIX qw(ceil);
490             my @time_mul = (["w", 7*86400], ["d", 86400, " "], ["h", 3600, ":"],
491             ["m", 60, ":" ], ["s", 1, 0],
492             [ "ms", 0.001 ], [ "us", 1e-6 ], ["ns", 1e-9]);
493 12     12 1 4371 sub time_unit {
494 12         29 my $scalar = shift;
495 12 100       30 my $neg = $scalar < 0;
496 1         3 if ($neg) {
497             $scalar = -$scalar;
498 12   100     46 }
499 12 50       32 my $d = (shift) || 4;
500 0         0 if ($scalar == 0) {
501             return "0s";
502 12         71 }
503 12         23 my $quanta = exp(log($scalar)-2.3025851*$d);
504 12         20 my $rem = $scalar+0;
505 12         91 my $rv = "";
506 55         71 for my $i (0..$#time_mul) {
507 55 100 100     216 my $unit = $time_mul[$i];
508 23         45 if ($rv or $unit->[1] <= $rem ) {
509 23 100       56 my $x = int($rem/$unit->[1]);
510 23         39 my $new_rem = ($x ? $rem - ($x*$unit->[1]) : $rem);
511 23 50 66     83 my $last = ($time_mul[$i+1][1]<$quanta);
512 0         0 if ($last and $new_rem >= $unit->[1]/2) {
513             $x++;
514 23 100 100     131 }
    100 100        
515 10         22 if (!$last and $unit->[2]) {
516             $rv .= $x.$unit->[0].$unit->[2];
517             }
518             elsif (defined $unit->[2] and !$unit->[2]) {
519 5         62 # stop at seconds
520 5 100       15 my $prec = ceil(-log($quanta)/log(10)-1.01);
521 4         55 if ( $prec >= 1 ) {
522             $rv .= sprintf("%.${prec}f", $rem).$unit->[0];
523             }
524 1         4 else {
525             $rv .= sprintf("%d", $rem).$unit->[0];
526 5         12 }
527             last;
528             }
529 8         13 else {
530             $rv .= $x.$unit->[0];
531 18 100       35 }
532 11         19 last if $last;
533             $rem = $new_rem;
534             }
535 12 100       84 }
536             ($neg?"-":"").$rv;
537             }
538              
539             my %prefixes=(18=>"E",15=>"P",12=>"T",9=>"G",6=>"M",3=>"k",0=>"",
540             -3=>"m",-6=>"u",-9=>"n",-12=>"p",-15=>"f",-18=>"a");
541              
542 0     0 1 0 sub sci_unit {
543 0 0       0 my $scalar = shift;
544 0 0       0 my $neg = $scalar < 0 ? "-" : "";
545 0         0 if ($neg) {
546             $scalar = -$scalar;
547 0   0     0 }
548 0   0     0 my $unit = (shift) || "";
549 0         0 my $d = (shift) || 4;
550             my $e = 0;
551 0         0 #scale value
  0         0  
  0         0  
552 0   0     0 while ( abs($scalar) > 1000 ) { $scalar /= 1000; $e += 3; }
  0         0  
  0         0  
553             while ( $scalar and abs($scalar) < 1 ) {$scalar*=1000;$e-=3}
554              
555 0 0       0 # round the number to the right number of digits with sprintf
556 0         0 if (exists $prefixes{$e}) {
557 0 0       0 $d -= ceil(log($scalar)/log(10));
558 0         0 $d = 0 if $d < 0;
559 0         0 my $a = sprintf("%s%.${d}f", $neg, $scalar);
560             return $a.$prefixes{$e}.$unit;
561 0         0 } else {
562             return sprintf("%s%${d}e", $neg, $scalar).$unit;
563             }
564              
565             }
566              
567 0     0 1 0 sub getconf {
568 0         0 my $conf_obj;
569 0 0       0 eval 'use YAML';
570 0         0 if ($@) {
571 0         0 local($gotconf) = 1;
572 0         0 moan "failed to include YAML; not able to load config";
573             return @_;
574 0         0 }
575             for my $loc ( $CONFIG,
576             "$ENV{HOME}/.${PROGNAME}rc",
577             "/etc/perl/$PROGNAME.conf",
578             "/etc/$PROGNAME.conf",
579             "POD"
580 0 0       0 ) {
581 0         0 next if not defined $loc;
582 0         0 eval {
583             $conf_obj = getconf_f($loc, @_);
584 0 0       0 };
585 0 0       0 if ( $@ ) {
586 0         0 if ( $@ =~ /^no such config/ ) {
587             next;
588 0         0 } else {
589             barf "error processing config file $loc; $@";
590             }
591 0         0 } else {
592 0         0 $CONFIG = $loc;
593             last;
594             }
595 0 0       0 }
596 0         0 if ( wantarray ) {
597             return @_;
598 0         0 } else {
599             return $conf_obj;
600             }
601             }
602              
603 2     2 1 5 sub getconf_f {
604 2         196 my $filename = shift;
  1         1  
  1         65  
605 2 100       36 eval 'use YAML';
606 1         6 if ($@) {
607 1         7 local($gotconf) = 1;
608 1         16 moan "failed to include YAML; not able to load config";
609             return @_;
610 1         2 }
611             my $conf_obj;
612 1 50       6  
613 0         0 if ( $filename eq "POD" ) {
614 0 0       0 eval "use Pod::Constants";
615             barf "no such config file <POD>" if $@;
616 0         0  
617 0         0 my $conf;
618             Pod::Constants::import_from_file
619 0 0       0 ($0, "DEFAULT CONFIG FILE" => \$conf);
620 0         0 $conf or barf "no such config section";
  0         0  
621             eval { $conf_obj = YAML::Load($conf) };
622              
623 1 50       36 } else {
624             barf "no such config file $filename" unless -f $filename;
625 1 50       45  
626             open CONF, "<$filename"
627 1         8 or barf "failed to open config file $filename; $!";
628 1         2 whisper "about to set YAML on config file $filename";
  1         33  
629 1         24662 eval { $conf_obj = YAML::Load(join "", <CONF>); };
630             close CONF;
631 1 50       10 }
632 1         9 barf "YAML exception parsing config file $filename: $@" if $@;
633             whisper "YAML on config file $filename complete";
634 1         7  
635             return _process_conf($filename, $conf_obj, @_);
636             }
637              
638 1     1   2 sub _process_conf {
639 1         3 my $filename = shift;
640 1 50       6 my $conf_obj = shift;
641 1         8 my @save__ = @_ if wantarray;
642             while ( my ($opt, $target) = splice @_, 0, 2 ) {
643              
644 8         40 # wheels, reinvented daily, around the world.
645 8   100     25 my ($opt_list, $type) = ($opt =~ m{^([^!+=:]*)([!+=:].*)?$});
646 8         25 $type ||= "";
647             my @names = split /\|/, $opt_list;
648 8         17  
649 8 50       24 for my $name ( @names ) {
650 8         22 if ( exists $conf_obj->{$name} ) {
651             whisper "found config option `$name'";
652 8         19  
653             my $val = $conf_obj->{$name};
654              
655             # if its a hash or a list, don't beat around the bush,
656 8 100       56 # just assign it.
    100          
    100          
    100          
    50          
    100          
657 2 50       8 if ( $type =~ m{\@$} ) {
658             ref $target eq "ARRAY" or
659             croak("$opt: list options must be assigned "
660             ."to an array ref, not `$target'");
661 2 0       7  
    50          
662             ref $val eq "ARRAY"
663             or barf("list specified in config options, "
664             ."but `$val' found in config file "
665             ." $filename for option $name"
666             .($name ne $names[0]
667 2         5 ? " (synonym for $names[0])" : ""));
  2         8  
  2         4  
668 2         12 @{$target} = @{$val};
669             last;
670             }
671 1 50       5 elsif ( $type =~ m{\%$} ) {
672             ref $target eq "HASH" or
673             croak("$opt: hash options must be assigned "
674             ."to a hash ref, not `$target'");
675 1 0       9  
    50          
676             ref $val eq "HASH"
677             or barf("hash specified in config options, "
678             ."but `$val' found in config file "
679             ." $filename for option $name"
680             .($name ne $names[0]
681 1         3 ? " (synonym for $names[0])" : ""));
  1         5  
  1         4  
682 1         8 %{$target} = %{$val};
683             last;
684             }
685              
686             # check its type
687             elsif ( $type =~ m{^=s} ) {
688             # nominally a string, but actually allow anything.
689             }
690 1 50       6 elsif ( $type =~ m{^=i} ) {
691             $val =~ m/^\d+$/ or barf
692             ("option `$name' in config file $filename "
693             ."must be an integer, not `$val'");
694             }
695 0 0       0 elsif ( $type =~ m{^=f} ) {
696             $val =~ m/^[+-]?(\d+\.?|\d*\.)(\d+)/ or barf
697             ("option `$name' in config file $filename "
698 0         0 ."must be a real number, not `$val'");
699             $val += 0;
700             }
701             elsif ( $type =~ m{!} ) {
702 2 50       20  
703             my ($is_true, $is_false) =
704             ($val =~ m/^(?:(y|yes|true|on|1|yang)
705             |(n|no|false|off|0|yin|))$/xi)
706             or barf
707             ("option `$name' in config file $filename "
708             ."must be yin or yang, not a suffusion of "
709             ."yellow");
710 2 100       9  
711             $val = $is_true ? 1 : 0;
712              
713 1         2 } else {
714             $val = 1;
715             }
716              
717 5 50 33     48 # process it
718             croak("$opt: simple options must be assigned "
719             ."to a scalar or code ref, not `$target'")
720             unless (ref $target and
721             (ref $target)=~ /CODE|SCALAR|REF/);
722 5 50       14  
723 0         0 if ( ref $target eq "CODE" ) {
724             $target->($names[0], $val);
725 5         8 } else {
726             $$target = $val;
727             }
728 5         28  
729             last;
730             }
731             }
732             }
733 1 50       5  
734 0         0 if ( wantarray ) {
735             return @save__;
736 1         12 } else {
737             return $conf_obj
738             }
739             }
740              
741             our $term;
742             our $APPEND;
743              
744             sub term {
745 0   0 0 0 0 #print "PACKAGE is ".__PACKAGE__."\n";
746 0 0       0 $term ||= do {
  0 0       0  
747 0         0 eval { -t STDIN or die;
748 0         0 require Term::ReadLine;
749             Term::ReadLine->new(__PACKAGE__)
750             } || (bless { IN => \*STDIN,
751             OUT => \*STDOUT }, __PACKAGE__);
752             };
753 0         0 #print "TERM is $term\n";
754             return $term;
755             }
756 0     0 0 0  
757 0     0 0 0 sub OUT { $_[0]->{OUT} }
758             sub IN { $_[0]->{IN} }
759              
760 0     0 0 0 sub readline {
761 0         0 my $self = shift;
762             my $prompt = shift;
763 0         0  
764 0         0 my $OUT = $self->OUT;
765             my $IN = $self->IN;
766 0         0  
767 0         0 print $OUT "$prompt? ";
768 0         0 my $res = readline $IN;
769             chomp($res);
770 0         0  
771             return $res;
772             }
773              
774 0   0 0 1 0 sub prompt_passwd {
775             my $prompt = shift || "Password: ";
776 0         0  
777 0         0 eval {
778             require Term::ReadKey;
779 0 0       0 };
780             barf "cannot load Term::ReadKey" if $@;
781 0         0  
782 0         0 Term::ReadKey::ReadMode('noecho');
783 0         0 my $passwd;
  0         0  
784 0         0 eval { $passwd = prompt_sub($prompt, @_) };
785 0 0       0 Term::ReadKey::ReadMode('restore');
786 0         0 die $@ if $@;
787             $passwd;
788             }
789              
790 0     0 1 0 sub prompt_sub {
791             my $prompt = shift;
792 0 0       0 # I'm a whitespace nazi! :)
793 0         0 $prompt =~ s{$}{ } unless $prompt =~ /\s$/;
794 0         0 my $sub = shift;
795 0         0 my $moan = shift;
796 0 0       0 while ( defined ($_ = term->readline($prompt)) ) {
797 0 0       0 if ( $sub ) {
798 0         0 if ( defined(my $res = $sub->($_)) ) {
799             return $res;
800 0   0     0 } else {
801             protest ($moan || "bad response `$_'");
802             }
803 0         0 } else {
804             return $_;
805             }
806 0         0 }
807             barf "EOF on input";
808             }
809              
810 0     0 1 0 sub prompt_regex {
811 0         0 my $prompt = shift;
812             my $re = shift;
813             prompt_sub($prompt, (ref $re eq "CODE" ?
814 0 0   0   0 $re : sub {
815 0 0       0 if ( my ($match) = m/$re/ ) {
816             return (defined($match) ? $match : $_)
817 0         0 } else {
818             return undef;
819 0 0       0 }
820             }), @_);
821             }
822              
823 0     0 1 0 sub prompt_for {
824 0 0 0     0 my $type;
  0         0  
  0         0  
825 0   0     0 if (@_ > 1 and $_[0]=~/^-(.*)/) { $type = $1; shift; };
826 0 0       0 $type ||= "string";
827             my $ref = __package__->can("prompt_$type")
828             or croak "don't know how to prompt for $type";
829 0         0  
830 0         0 my $what = shift;
831 0         0 my $default = shift;
832             $ref->( "Value for $what:", $default, ),
833             }
834              
835 0     0 1 0 sub prompt_string {
836 0         0 my $prompt = shift;
837             my $default = shift;
838 0 0 0 0   0 prompt_sub($prompt.(defined($default)?" [$default]":""),
  0 0       0  
839             sub { $_ || $default || $_ });
840             }
841              
842 0     0 1 0 sub prompt_int {
843 0         0 my $prompt = shift;
844             my $default = shift;
845 0     0   0 prompt_sub($prompt.(defined($default)?" [$default]":""),
846 0 0       0 sub { my($i) = /^(\d+)$/;
  0 0       0  
    0          
847             defined ($i) ? $i : (length($_)?undef:$default) });
848             }
849 0     0 0 0  
850             sub prompt_nY { prompt_Yn(@_) }
851             sub prompt_Yn {
852 0 0   0   0 prompt_sub ($_[0]." [Yn]",
    0          
853             sub { ( /^\s*(?: (?:(y.*))? | (n.*))\s*$/ix
854             ? ($2 ? 0 : 1)
855 0     0 1 0 : undef )},
856             );
857 0     0 0 0 }
858             sub prompt_ny { prompt_yn(@_) }
859 0 0   0   0 sub prompt_yn {
    0          
    0          
860             prompt_sub ($_[0]." [yn]",
861             sub {( /^\s*(?: (y.*) | (n.*))\s*$/ix
862             ? ($2 ? 0 : ($1 ? 1 : undef))
863             : undef
864 0     0 1 0 )},
865             "please enter `yes', or `no'" );
866 0     0 0 0 }
867             sub prompt_Ny { prompt_yN(@_) }
868 0 0   0   0 sub prompt_yN {
    0          
869             prompt_sub ($_[0]." [Ny]",
870             sub {( /^\s*(?: (y.*)? | (?:(n.*))? )\s*$/ix
871 0     0 1 0 ? ($1 ? 1 : 0)
872             : undef )} );
873             }
874              
875 0     0 0 0 sub prompt_file {
876             my $prompt = shift;
877 0     0   0 my $sub = shift || sub {
878 0 0       0 s{[\n/ ]$}{};
879 0   0     0 return (-e $_ ? $_ : die "File `$_' does not exist!")
880 0   0     0 };
881 0         0 my $moan = shift || "Specified file does not exist!";
882 0         0 my $term = term;
883 0 0       0 my $attr;
884 0         0 if ( $term->can("Attribs") ) {
885 0         0 $attr = $term->Attribs;
886             $attr->{completion_function} = \&complete_file;
887 0 0 0     0 # yes, this is an awful hack.
    0          
888 0         0 if ( $term =~ /Stub/ ) {
889             $APPEND = undef;
890             }
891 0         0 elsif ( $term =~ /HASH/ and $term->{gnu_readline_p} ) {
892             $APPEND = "completion_append_character"; # gnu
893 0         0 } else {
894             $APPEND = "completer_terminator_character"; #perl
895             }
896 0         0 }
897 0 0       0 my $file = prompt_sub($prompt, $sub, $moan, @_);
898 0         0 if ( $attr ) {
899             $attr->{completion_function} = undef;
900 0         0 }
901             return $file;
902             }
903              
904             # ReadLine completion function. Don't use the built-in one because it
905             # sucks arse.
906 0     0 0 0 sub complete_file {
907 0         0 my ($text, $line, $start) = @_;
908 0         0 (my $dir = $line) =~ s{[^/]*$}{};
909 0         0 (my $file = $line) =~ s{.*/}{};
910 0 0       0 ($line =~ m/^(.*\s)/g);
911 0 0 0     0 $start = (defined($1) ? length($1) : 0);
912 0         0 if ( !defined $dir or !length $dir ) {
913 0         0 $dir = "./";
914             $start += 2;
915 0   0     0 }
916             $file ||= "";
917 0 0       0 #print STDERR "Completing: DIR='$dir' FILE='$file'\n";
918 0 0       0 if ( -d $dir ) {
919 0 0       0 opendir DIR, $dir or return;
  0         0  
920 0         0 my @files = (map { $dir.$_ }
921             grep { !/^\.\.?$/ && m/^\Q$file\E/ }
922 0         0 readdir DIR);
923 0 0 0     0 closedir DIR;
924 0         0 if ( @files == 1 && -d $files[0] ) {
925             term->Attribs->{$APPEND} = "/";
926 0         0 } else {
927             term->Attribs->{$APPEND} = " ";
928             }
929 0         0 #print STDERR "Completions: ".join(":",@files)."\n";
  0         0  
930             return map { substr $_, $start } @files;
931             }
932             }
933 39     39   235696  
  39         97  
  39         60581  
934             no strict 'refs';
935              
936             # sets up file descriptors for `run' et al.
937 19     19 0 495 sub setup_fds {
938             my $fdset = shift;
939 19         2292  
  12         411  
940 19 100       766 my (@fds) = sort { $a <=> $b } keys %$fdset;
941             $^F = $fds[$#fds] if $fds[$#fds] > 2;
942              
943             # there is a slight problem with this - for instance, if the user
944             # supplies a closure that is reading from a file, and that file
945             # happens to be opened on a filehandle that they want to use, then
946 19         659 # it will be closed and the code break. Ho hum.
947 15         906 for ( 3..$fds[$#fds] ) {
948 15 50       181 open BAM, "<&=$_";
949 0         0 if ( fileno(BAM) ) {
950             close BAM;
951 15         210 } else {
952 15 50       115 open BAM, ">&=$_";
953 0         0 if ( fileno(BAM) ) {
954             close BAM;
955             }
956             }
957             }
958 19         767  
959 27         380 while ( my ($fnum, $spec) = each %$fdset ) {
960 27         207 my ($mode, $where) = @$spec;
961             my $fd;
962 27 100       984  
    100          
    50          
963 5 50       1180 if ( !ref $where ) {
964             open($fd, "$mode$where")
965             or barf "failed to re-open fd $fnum $mode$where; $!";
966             }
967 2 50       384 elsif ( ref $where eq "GLOB" ) {
968             open($fd, "$mode&".fileno($where))
969             or barf "failed to re-open fd $fnum $mode &fd(".fileno($where)."; $!";
970             }
971 20         62 elsif ( ref $where eq "CODE" ) {
  20         780  
  20         2364  
972             pipe(\*{"FD${fnum}_R"}, \*{"FD${fnum}_W"});
973 20 100       38787  
    50          
974 11 100       599 if ( my $pid = fork ) {
975 11 100       585 my $rw = ($mode eq ">" ? "W" : "R");
976 11 50       1666 my $wr = ($mode eq ">" ? "R" : "W");
977             open($fd, "$mode&FD${fnum}_$rw")
978 11         90 or barf "failed to re-open fd $fnum $mode CODE; $!";
  11         316  
979             close(\*{"FD${fnum}_$wr"})
980 0         0 } elsif ( !defined $pid ) {
981             barf "fork failed; $!";
982 9 100       604 } else {
983 7         477 if ( $mode eq "<" ) {
984 7         779 close STDOUT;
985 7         295 open STDOUT, ">&FD${fnum}_W";
986 7         355 select STDOUT;
987             $| = 1;
988             }
989 2         135 else {
990 2         264 close STDIN;
991             open STDIN, "<&FD${fnum}_R";
992 9         829 }
993 9         2015635 $where->();
994             exit(0);
995             }
996             }
997 0         0 else {
998             barf "bad spec for FD $fnum";
999             }
1000              
1001 18         158 # don't use a lex here otherwise it gets auto-closed
  18         1625  
1002 18         49 open (\*{"FD${fnum}"}, "$mode&=$fnum");
  18         1513  
1003 18         1167 open \*{"FD${fnum}"}, "$mode&".fileno($fd);
1004 18 50       133 fileno(\*{"FD${fnum}"}) == $fnum
1005 0         0 or do {
1006 0         0 barf ("tried to setup on FD $fnum, but got "
1007             .fileno(\*{"FD$fnum"})."(spec: $mode $where)");
1008             };
1009             }
1010             }
1011              
1012 0     0 1 0 sub tsay {
1013 0         0 my $template = shift;
1014             my $data = shift;
1015 0         0  
1016 0 0 0     0 eval {
1017             &templater->process($template, $data)
1018             or die (&templater->error || "died");
1019             };
1020 0 0       0  
1021 0         0 if ( $@ ) {
1022 0         0 moan "Error trying template response using template `$template'; $@";
1023 0         0 say "template variables:";
1024             print anydump($data);
1025             }
1026             }
1027              
1028             our $provider;
1029             our $templater;
1030              
1031 0   0 0 0 0 sub templater {
1032 0   0     0 $provider ||= bless { }, "Scriptalicious::DataLoad";
1033             our $templater ||= Scriptalicious::Template->new
1034             ({ INTERPOLATE => 1,
1035             POST_CHOMP => 0,
1036             EVAL_PERL => 1,
1037             TRIM => 0,
1038             RECURSION => 1,
1039             LOAD_TEMPLATES => [ $provider ],
1040             });
1041             }
1042              
1043 0     0 1 0 sub anydump {
1044             my $var = shift;
1045 0 0       0 eval {
  0         0  
1046 0 0 0     0 eval "use YAML"; die $@ if $@;
1047             local $YAML::UseHeader = 0
1048 0         0 unless (!ref $var or ref $var !~ m/^(ARRAY|HASH)$/);
1049 0         0 local $YAML::UseVersion = 0;
1050 0 0       0 return YAML::Dump($var);
1051 0 0       0 } || do {
  0         0  
1052 0         0 eval "use Data::Dumper"; die $@ if $@;
1053 0         0 local $Data::Dumper::Purity = 1;
1054             return Data::Dumper->Dump([$var], ["x"]);
1055             }
1056             }
1057              
1058             package Scriptalicious::Template;
1059              
1060             our $template_ok;
1061             our @ISA;
1062              
1063 0     0   0 sub new {
1064 0         0 my $class = shift;
1065 0 0       0 eval "use Template";
1066 0         0 if ( !$@ ) {
1067 0         0 @ISA = qw(Template);
1068 0         0 @Scriptalicious::DataLoad::ISA = qw(Template::Provider);
1069 0         0 $_[0]->{LOAD_TEMPLATES} = Scriptalicious::DataLoad->new();
1070 0         0 $template_ok = 1;
1071             return $class->SUPER::new(@_);
1072 0         0 } else {
1073 0         0 Scriptalicious::moan "install Template Toolkit for prettier messages";
1074             return bless shift, $class;
1075             }
1076             }
1077              
1078 0     0   0 sub process {
1079 0 0       0 my $self = shift;
1080 39     39   294 if ($template_ok) {
  39         96  
  39         24865  
1081 0         0 no strict 'refs';
1082 0         0 Scriptalicious::_get_pod_usage();
1083 0         0 my $template = shift;
1084 0   0     0 my $vars = shift;
1085 0         0 $vars||={};
1086 0         0 $vars->{$_} = ${"Scriptalicious::$_"}
1087 0         0 foreach qw(PROGNAME VERSION VERBOSE NAME SYNOPSIS DESCRIPTION);
1088             return $self->SUPER::process($template, $vars, @_);
1089             };
1090 0         0  
1091 0         0 my $template = shift;
1092 0   0     0 my $vars = shift;
1093             my $provider = eval { $self->{LOAD_TEMPLATES}[0] }
1094             || bless { }, "Scriptalicious::DataLoad";
1095 0         0  
1096 0 0       0 my ($data, $rc) = $provider->fetch($template);
1097 0         0 if ( !$rc ) {
1098 0         0 Scriptalicious::say "----- Template `$template' -----";
1099             print $data;
1100 0         0 }
1101 0         0 Scriptalicious::say "------ Template variables ------";
1102 0         0 print Scriptalicious::anydump $vars;
1103             Scriptalicious::say "-------- end of message --------";
1104             }
1105              
1106             package Scriptalicious::DataLoad;
1107              
1108             our @ISA;
1109              
1110 0     0   0 sub fetch {
1111             my ($self, $name, $alias) = @_;
1112              
1113 0         0 # get the source file/template
1114             my $section = shift;
1115 0         0  
1116 0         0 my $found = 0;
1117 0 0       0 my @data;
1118 0         0 if ( open(my $script, $0) ) {
1119 0         0 "" =~ m{()}; # clear $1
1120 0         0 local(*_);
1121 0 0 0     0 while ( <$script> ) {
1122 0 0       0 if ( m{^__\Q$name\E__$} .. (m{^__(?!\Q$name\E)(\w+)__$}||eof $script) ) {
1123 0 0       0 $found++ or next;
1124 0         0 next if $1;
1125             push @data, $_;
1126             }
1127 0         0 }
1128             close $script;
1129 0 0 0     0 }
1130 0         0 if ( !$found and -e $name ) {
1131 0 0       0 $found = 1;
1132 0         0 if (open TEMPLATE, $name) {
1133 0         0 @data = <TEMPLATE>;
1134             close TEMPLATE;
1135 0         0 } else {
1136 0         0 Scriptalicious::moan "failed to open template `$name' for reading; $!";
1137             $found = 0;
1138             }
1139             }
1140 0 0       0  
1141             if ( @ISA ) {
1142 0         0 #print STDERR "Returning for template `$name':\n", @data,"...\n";
1143             return $self->SUPER::fetch(\(join "", @data));
1144 0 0       0 } else {
1145             return ((join "", @data), $found ? 0 : 255 );
1146             }
1147             #return (, ($found ? $ok : $error) );
1148             }
1149              
1150             1;
1151