got rid of extra underscore in file marker
[deployable.git] / bundle / deployable
blob2fc81aaa45aabe51d683349a42e43213c6495fbd
1 #!/usr/bin/env perl
2 use strict;
3 use warnings;
4 use Carp;
5 use version; our $VERSION = qv('0.1.1');
6 use Fatal qw( close );
7 use Pod::Usage qw( pod2usage );
8 use Getopt::Long qw( :config gnu_getopt );
9 use English qw( -no_match_vars );
10 use File::Basename qw( basename dirname );
11 use File::Spec::Functions qw( file_name_is_absolute catfile );
12 use File::Temp qw( tempfile );
13 use POSIX qw( strftime );
14 use Cwd qw( cwd realpath );
15 use Archive::Tar;
16 use Data::Dumper;
17 use Encode;
19 # __MOBUNDLE_INCLUSION__
20 BEGIN {
21 my %file_for = (
23 'Text/Glob.pm' => <<'END_OF_FILE',
24 package Text::Glob;
25 use strict;
26 use Exporter;
27 use vars qw/$VERSION @ISA @EXPORT_OK
28 $strict_leading_dot $strict_wildcard_slash/;
29 $VERSION = '0.09';
30 @ISA = 'Exporter';
31 @EXPORT_OK = qw( glob_to_regex glob_to_regex_string match_glob );
33 $strict_leading_dot = 1;
34 $strict_wildcard_slash = 1;
36 use constant debug => 0;
38 sub glob_to_regex {
39 my $glob = shift;
40 my $regex = glob_to_regex_string($glob);
41 return qr/^$regex$/;
44 sub glob_to_regex_string
46 my $glob = shift;
47 my ($regex, $in_curlies, $escaping);
48 local $_;
49 my $first_byte = 1;
50 for ($glob =~ m/(.)/gs) {
51 if ($first_byte) {
52 if ($strict_leading_dot) {
53 $regex .= '(?=[^\.])' unless $_ eq '.';
55 $first_byte = 0;
57 if ($_ eq '/') {
58 $first_byte = 1;
60 if ($_ eq '.' || $_ eq '(' || $_ eq ')' || $_ eq '|' ||
61 $_ eq '+' || $_ eq '^' || $_ eq '$' || $_ eq '@' || $_ eq '%' ) {
62 $regex .= "\\$_";
64 elsif ($_ eq '*') {
65 $regex .= $escaping ? "\\*" :
66 $strict_wildcard_slash ? "[^/]*" : ".*";
68 elsif ($_ eq '?') {
69 $regex .= $escaping ? "\\?" :
70 $strict_wildcard_slash ? "[^/]" : ".";
72 elsif ($_ eq '{') {
73 $regex .= $escaping ? "\\{" : "(";
74 ++$in_curlies unless $escaping;
76 elsif ($_ eq '}' && $in_curlies) {
77 $regex .= $escaping ? "}" : ")";
78 --$in_curlies unless $escaping;
80 elsif ($_ eq ',' && $in_curlies) {
81 $regex .= $escaping ? "," : "|";
83 elsif ($_ eq "\\") {
84 if ($escaping) {
85 $regex .= "\\\\";
86 $escaping = 0;
88 else {
89 $escaping = 1;
91 next;
93 else {
94 $regex .= $_;
95 $escaping = 0;
97 $escaping = 0;
99 print "# $glob $regex\n" if debug;
101 return $regex;
104 sub match_glob {
105 print "# ", join(', ', map { "'$_'" } @_), "\n" if debug;
106 my $glob = shift;
107 my $regex = glob_to_regex $glob;
108 local $_;
109 grep { $_ =~ $regex } @_;
113 __END__
115 =head1 NAME
117 Text::Glob - match globbing patterns against text
119 =head1 SYNOPSIS
121 use Text::Glob qw( match_glob glob_to_regex );
123 print "matched\n" if match_glob( "foo.*", "foo.bar" );
125 # prints foo.bar and foo.baz
126 my $regex = glob_to_regex( "foo.*" );
127 for ( qw( foo.bar foo.baz foo bar ) ) {
128 print "matched: $_\n" if /$regex/;
131 =head1 DESCRIPTION
133 Text::Glob implements glob(3) style matching that can be used to match
134 against text, rather than fetching names from a filesystem. If you
135 want to do full file globbing use the File::Glob module instead.
137 =head2 Routines
139 =over
141 =item match_glob( $glob, @things_to_test )
143 Returns the list of things which match the glob from the source list.
145 =item glob_to_regex( $glob )
147 Returns a compiled regex which is the equivalent of the globbing
148 pattern.
150 =item glob_to_regex_string( $glob )
152 Returns a regex string which is the equivalent of the globbing
153 pattern.
155 =back
157 =head1 SYNTAX
159 The following metacharacters and rules are respected.
161 =over
163 =item C<*> - match zero or more characters
165 C<a*> matches C<a>, C<aa>, C<aaaa> and many many more.
167 =item C<?> - match exactly one character
169 C<a?> matches C<aa>, but not C<a>, or C<aaa>
171 =item Character sets/ranges
173 C<example.[ch]> matches C<example.c> and C<example.h>
175 C<demo.[a-c]> matches C<demo.a>, C<demo.b>, and C<demo.c>
177 =item alternation
179 C<example.{foo,bar,baz}> matches C<example.foo>, C<example.bar>, and
180 C<example.baz>
182 =item leading . must be explictly matched
184 C<*.foo> does not match C<.bar.foo>. For this you must either specify
185 the leading . in the glob pattern (C<.*.foo>), or set
186 C<$Text::Glob::strict_leading_dot> to a false value while compiling
187 the regex.
189 =item C<*> and C<?> do not match /
191 C<*.foo> does not match C<bar/baz.foo>. For this you must either
192 explicitly match the / in the glob (C<*/*.foo>), or set
193 C<$Text::Glob::strict_wildcard_slash> to a false value with compiling
194 the regex.
196 =back
198 =head1 BUGS
200 The code uses qr// to produce compiled regexes, therefore this module
201 requires perl version 5.005_03 or newer.
203 =head1 AUTHOR
205 Richard Clamp <richardc@unixbeard.net>
207 =head1 COPYRIGHT
209 Copyright (C) 2002, 2003, 2006, 2007 Richard Clamp. All Rights Reserved.
211 This module is free software; you can redistribute it and/or modify it
212 under the same terms as Perl itself.
214 =head1 SEE ALSO
216 L<File::Glob>, glob(3)
218 =cut
220 END_OF_FILE
222 'File/Find/Rule.pm' => <<'END_OF_FILE',
223 # $Id$
225 package File::Find::Rule;
226 use strict;
227 use File::Spec;
228 use Text::Glob 'glob_to_regex';
229 use Number::Compare;
230 use Carp qw/croak/;
231 use File::Find (); # we're only wrapping for now
233 our $VERSION = '0.33';
235 # we'd just inherit from Exporter, but I want the colon
236 sub import {
237 my $pkg = shift;
238 my $to = caller;
239 for my $sym ( qw( find rule ) ) {
240 no strict 'refs';
241 *{"$to\::$sym"} = \&{$sym};
243 for (grep /^:/, @_) {
244 my ($extension) = /^:(.*)/;
245 eval "require File::Find::Rule::$extension";
246 croak "couldn't bootstrap File::Find::Rule::$extension: $@" if $@;
250 =head1 NAME
252 File::Find::Rule - Alternative interface to File::Find
254 =head1 SYNOPSIS
256 use File::Find::Rule;
257 # find all the subdirectories of a given directory
258 my @subdirs = File::Find::Rule->directory->in( $directory );
260 # find all the .pm files in @INC
261 my @files = File::Find::Rule->file()
262 ->name( '*.pm' )
263 ->in( @INC );
265 # as above, but without method chaining
266 my $rule = File::Find::Rule->new;
267 $rule->file;
268 $rule->name( '*.pm' );
269 my @files = $rule->in( @INC );
271 =head1 DESCRIPTION
273 File::Find::Rule is a friendlier interface to File::Find. It allows
274 you to build rules which specify the desired files and directories.
276 =cut
278 # the procedural shim
280 *rule = \&find;
281 sub find {
282 my $object = __PACKAGE__->new();
283 my $not = 0;
285 while (@_) {
286 my $method = shift;
287 my @args;
289 if ($method =~ s/^\!//) {
290 # jinkies, we're really negating this
291 unshift @_, $method;
292 $not = 1;
293 next;
295 unless (defined prototype $method) {
296 my $args = shift;
297 @args = ref $args eq 'ARRAY' ? @$args : $args;
299 if ($not) {
300 $not = 0;
301 @args = $object->new->$method(@args);
302 $method = "not";
305 my @return = $object->$method(@args);
306 return @return if $method eq 'in';
308 $object;
312 =head1 METHODS
314 =over
316 =item C<new>
318 A constructor. You need not invoke C<new> manually unless you wish
319 to, as each of the rule-making methods will auto-create a suitable
320 object if called as class methods.
322 =cut
324 sub new {
325 my $referent = shift;
326 my $class = ref $referent || $referent;
327 bless {
328 rules => [],
329 subs => {},
330 iterator => [],
331 extras => {},
332 maxdepth => undef,
333 mindepth => undef,
334 }, $class;
337 sub _force_object {
338 my $object = shift;
339 $object = $object->new()
340 unless ref $object;
341 $object;
344 =back
346 =head2 Matching Rules
348 =over
350 =item C<name( @patterns )>
352 Specifies names that should match. May be globs or regular
353 expressions.
355 $set->name( '*.mp3', '*.ogg' ); # mp3s or oggs
356 $set->name( qr/\.(mp3|ogg)$/ ); # the same as a regex
357 $set->name( 'foo.bar' ); # just things named foo.bar
359 =cut
361 sub _flatten {
362 my @flat;
363 while (@_) {
364 my $item = shift;
365 ref $item eq 'ARRAY' ? push @_, @{ $item } : push @flat, $item;
367 return @flat;
370 sub name {
371 my $self = _force_object shift;
372 my @names = map { ref $_ eq "Regexp" ? $_ : glob_to_regex $_ } _flatten( @_ );
374 push @{ $self->{rules} }, {
375 rule => 'name',
376 code => join( ' || ', map { "m{$_}" } @names ),
377 args => \@_,
380 $self;
383 =item -X tests
385 Synonyms are provided for each of the -X tests. See L<perlfunc/-X> for
386 details. None of these methods take arguments.
388 Test | Method Test | Method
389 ------|------------- ------|----------------
390 -r | readable -R | r_readable
391 -w | writeable -W | r_writeable
392 -w | writable -W | r_writable
393 -x | executable -X | r_executable
394 -o | owned -O | r_owned
396 -e | exists -f | file
397 -z | empty -d | directory
398 -s | nonempty -l | symlink
399 | -p | fifo
400 -u | setuid -S | socket
401 -g | setgid -b | block
402 -k | sticky -c | character
403 | -t | tty
404 -M | modified |
405 -A | accessed -T | ascii
406 -C | changed -B | binary
408 Though some tests are fairly meaningless as binary flags (C<modified>,
409 C<accessed>, C<changed>), they have been included for completeness.
411 # find nonempty files
412 $rule->file,
413 ->nonempty;
415 =cut
417 use vars qw( %X_tests );
418 %X_tests = (
419 -r => readable => -R => r_readable =>
420 -w => writeable => -W => r_writeable =>
421 -w => writable => -W => r_writable =>
422 -x => executable => -X => r_executable =>
423 -o => owned => -O => r_owned =>
425 -e => exists => -f => file =>
426 -z => empty => -d => directory =>
427 -s => nonempty => -l => symlink =>
428 => -p => fifo =>
429 -u => setuid => -S => socket =>
430 -g => setgid => -b => block =>
431 -k => sticky => -c => character =>
432 => -t => tty =>
433 -M => modified =>
434 -A => accessed => -T => ascii =>
435 -C => changed => -B => binary =>
438 for my $test (keys %X_tests) {
439 my $sub = eval 'sub () {
440 my $self = _force_object shift;
441 push @{ $self->{rules} }, {
442 code => "' . $test . ' \$_",
443 rule => "'.$X_tests{$test}.'",
445 $self;
446 } ';
447 no strict 'refs';
448 *{ $X_tests{$test} } = $sub;
452 =item stat tests
454 The following C<stat> based methods are provided: C<dev>, C<ino>,
455 C<mode>, C<nlink>, C<uid>, C<gid>, C<rdev>, C<size>, C<atime>,
456 C<mtime>, C<ctime>, C<blksize>, and C<blocks>. See L<perlfunc/stat>
457 for details.
459 Each of these can take a number of targets, which will follow
460 L<Number::Compare> semantics.
462 $rule->size( 7 ); # exactly 7
463 $rule->size( ">7Ki" ); # larger than 7 * 1024 * 1024 bytes
464 $rule->size( ">=7" )
465 ->size( "<=90" ); # between 7 and 90, inclusive
466 $rule->size( 7, 9, 42 ); # 7, 9 or 42
468 =cut
470 use vars qw( @stat_tests );
471 @stat_tests = qw( dev ino mode nlink uid gid rdev
472 size atime mtime ctime blksize blocks );
474 my $i = 0;
475 for my $test (@stat_tests) {
476 my $index = $i++; # to close over
477 my $sub = sub {
478 my $self = _force_object shift;
480 my @tests = map { Number::Compare->parse_to_perl($_) } @_;
482 push @{ $self->{rules} }, {
483 rule => $test,
484 args => \@_,
485 code => 'do { my $val = (stat $_)['.$index.'] || 0;'.
486 join ('||', map { "(\$val $_)" } @tests ).' }',
488 $self;
490 no strict 'refs';
491 *$test = $sub;
495 =item C<any( @rules )>
497 =item C<or( @rules )>
499 Allows shortcircuiting boolean evaluation as an alternative to the
500 default and-like nature of combined rules. C<any> and C<or> are
501 interchangeable.
503 # find avis, movs, things over 200M and empty files
504 $rule->any( File::Find::Rule->name( '*.avi', '*.mov' ),
505 File::Find::Rule->size( '>200M' ),
506 File::Find::Rule->file->empty,
509 =cut
511 sub any {
512 my $self = _force_object shift;
513 # compile all the subrules to code fragments
514 push @{ $self->{rules} }, {
515 rule => "any",
516 code => '(' . join( ' || ', map '( ' . $_->_compile . ' )', @_ ). ')',
517 args => \@_,
520 # merge all the subs hashes of the kids into ourself
521 %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_;
522 $self;
525 *or = \&any;
527 =item C<none( @rules )>
529 =item C<not( @rules )>
531 Negates a rule. (The inverse of C<any>.) C<none> and C<not> are
532 interchangeable.
534 # files that aren't 8.3 safe
535 $rule->file
536 ->not( $rule->new->name( qr/^[^.]{1,8}(\.[^.]{0,3})?$/ ) );
538 =cut
540 sub not {
541 my $self = _force_object shift;
543 push @{ $self->{rules} }, {
544 rule => 'not',
545 args => \@_,
546 code => '(' . join ( ' && ', map { "!(". $_->_compile . ")" } @_ ) . ")",
549 # merge all the subs hashes into us
550 %{ $self->{subs} } = map { %{ $_->{subs} } } $self, @_;
551 $self;
554 *none = \&not;
556 =item C<prune>
558 Traverse no further. This rule always matches.
560 =cut
562 sub prune () {
563 my $self = _force_object shift;
565 push @{ $self->{rules} },
567 rule => 'prune',
568 code => '$File::Find::prune = 1'
570 $self;
573 =item C<discard>
575 Don't keep this file. This rule always matches.
577 =cut
579 sub discard () {
580 my $self = _force_object shift;
582 push @{ $self->{rules} }, {
583 rule => 'discard',
584 code => '$discarded = 1',
586 $self;
589 =item C<exec( \&subroutine( $shortname, $path, $fullname ) )>
591 Allows user-defined rules. Your subroutine will be invoked with C<$_>
592 set to the current short name, and with parameters of the name, the
593 path you're in, and the full relative filename.
595 Return a true value if your rule matched.
597 # get things with long names
598 $rules->exec( sub { length > 20 } );
600 =cut
602 sub exec {
603 my $self = _force_object shift;
604 my $code = shift;
606 push @{ $self->{rules} }, {
607 rule => 'exec',
608 code => $code,
610 $self;
613 =item C<grep( @specifiers )>
615 Opens a file and tests it each line at a time.
617 For each line it evaluates each of the specifiers, stopping at the
618 first successful match. A specifier may be a regular expression or a
619 subroutine. The subroutine will be invoked with the same parameters
620 as an ->exec subroutine.
622 It is possible to provide a set of negative specifiers by enclosing
623 them in anonymous arrays. Should a negative specifier match the
624 iteration is aborted and the clause is failed. For example:
626 $rule->grep( qr/^#!.*\bperl/, [ sub { 1 } ] );
628 Is a passing clause if the first line of a file looks like a perl
629 shebang line.
631 =cut
633 sub grep {
634 my $self = _force_object shift;
635 my @pattern = map {
636 ref $_
637 ? ref $_ eq 'ARRAY'
638 ? map { [ ( ref $_ ? $_ : qr/$_/ ) => 0 ] } @$_
639 : [ $_ => 1 ]
640 : [ qr/$_/ => 1 ]
641 } @_;
643 $self->exec( sub {
644 local *FILE;
645 open FILE, $_ or return;
646 local ($_, $.);
647 while (<FILE>) {
648 for my $p (@pattern) {
649 my ($rule, $ret) = @$p;
650 return $ret
651 if ref $rule eq 'Regexp'
652 ? /$rule/
653 : $rule->(@_);
656 return;
657 } );
660 =item C<maxdepth( $level )>
662 Descend at most C<$level> (a non-negative integer) levels of directories
663 below the starting point.
665 May be invoked many times per rule, but only the most recent value is
666 used.
668 =item C<mindepth( $level )>
670 Do not apply any tests at levels less than C<$level> (a non-negative
671 integer).
673 =item C<extras( \%extras )>
675 Specifies extra values to pass through to C<File::File::find> as part
676 of the options hash.
678 For example this allows you to specify following of symlinks like so:
680 my $rule = File::Find::Rule->extras({ follow => 1 });
682 May be invoked many times per rule, but only the most recent value is
683 used.
685 =cut
687 for my $setter (qw( maxdepth mindepth extras )) {
688 my $sub = sub {
689 my $self = _force_object shift;
690 $self->{$setter} = shift;
691 $self;
693 no strict 'refs';
694 *$setter = $sub;
698 =item C<relative>
700 Trim the leading portion of any path found
702 =cut
704 sub relative () {
705 my $self = _force_object shift;
706 $self->{relative} = 1;
707 $self;
710 =item C<not_*>
712 Negated version of the rule. An effective shortand related to ! in
713 the procedural interface.
715 $foo->not_name('*.pl');
717 $foo->not( $foo->new->name('*.pl' ) );
719 =cut
721 sub DESTROY {}
722 sub AUTOLOAD {
723 our $AUTOLOAD;
724 $AUTOLOAD =~ /::not_([^:]*)$/
725 or croak "Can't locate method $AUTOLOAD";
726 my $method = $1;
728 my $sub = sub {
729 my $self = _force_object shift;
730 $self->not( $self->new->$method(@_) );
733 no strict 'refs';
734 *$AUTOLOAD = $sub;
736 &$sub;
739 =back
741 =head2 Query Methods
743 =over
745 =item C<in( @directories )>
747 Evaluates the rule, returns a list of paths to matching files and
748 directories.
750 =cut
752 sub in {
753 my $self = _force_object shift;
755 my @found;
756 my $fragment = $self->_compile;
757 my %subs = %{ $self->{subs} };
759 warn "relative mode handed multiple paths - that's a bit silly\n"
760 if $self->{relative} && @_ > 1;
762 my $topdir;
763 my $code = 'sub {
764 (my $path = $File::Find::name) =~ s#^(?:\./+)+##;
765 my @args = ($_, $File::Find::dir, $path);
766 my $maxdepth = $self->{maxdepth};
767 my $mindepth = $self->{mindepth};
768 my $relative = $self->{relative};
770 # figure out the relative path and depth
771 my $relpath = $File::Find::name;
772 $relpath =~ s{^\Q$topdir\E/?}{};
773 my $depth = scalar File::Spec->splitdir($relpath);
774 #print "name: \'$File::Find::name\' ";
775 #print "relpath: \'$relpath\' depth: $depth relative: $relative\n";
777 defined $maxdepth && $depth >= $maxdepth
778 and $File::Find::prune = 1;
780 defined $mindepth && $depth < $mindepth
781 and return;
783 #print "Testing \'$_\'\n";
785 my $discarded;
786 return unless ' . $fragment . ';
787 return if $discarded;
788 if ($relative) {
789 push @found, $relpath if $relpath ne "";
791 else {
792 push @found, $path;
796 #use Data::Dumper;
797 #print Dumper \%subs;
798 #warn "Compiled sub: '$code'\n";
800 my $sub = eval "$code" or die "compile error '$code' $@";
801 for my $path (@_) {
802 # $topdir is used for relative and maxdepth
803 $topdir = $path;
804 # slice off the trailing slash if there is one (the
805 # maxdepth/mindepth code is fussy)
806 $topdir =~ s{/?$}{}
807 unless $topdir eq '/';
808 $self->_call_find( { %{ $self->{extras} }, wanted => $sub }, $path );
811 return @found;
814 sub _call_find {
815 my $self = shift;
816 File::Find::find( @_ );
819 sub _compile {
820 my $self = shift;
822 return '1' unless @{ $self->{rules} };
823 my $code = join " && ", map {
824 if (ref $_->{code}) {
825 my $key = "$_->{code}";
826 $self->{subs}{$key} = $_->{code};
827 "\$subs{'$key'}->(\@args) # $_->{rule}\n";
829 else {
830 "( $_->{code} ) # $_->{rule}\n";
832 } @{ $self->{rules} };
834 #warn $code;
835 return $code;
838 =item C<start( @directories )>
840 Starts a find across the specified directories. Matching items may
841 then be queried using L</match>. This allows you to use a rule as an
842 iterator.
844 my $rule = File::Find::Rule->file->name("*.jpeg")->start( "/web" );
845 while ( defined ( my $image = $rule->match ) ) {
849 =cut
851 sub start {
852 my $self = _force_object shift;
854 $self->{iterator} = [ $self->in( @_ ) ];
855 $self;
858 =item C<match>
860 Returns the next file which matches, false if there are no more.
862 =cut
864 sub match {
865 my $self = _force_object shift;
867 return shift @{ $self->{iterator} };
872 __END__
874 =back
876 =head2 Extensions
878 Extension modules are available from CPAN in the File::Find::Rule
879 namespace. In order to use these extensions either use them directly:
881 use File::Find::Rule::ImageSize;
882 use File::Find::Rule::MMagic;
884 # now your rules can use the clauses supplied by the ImageSize and
885 # MMagic extension
887 or, specify that File::Find::Rule should load them for you:
889 use File::Find::Rule qw( :ImageSize :MMagic );
891 For notes on implementing your own extensions, consult
892 L<File::Find::Rule::Extending>
894 =head2 Further examples
896 =over
898 =item Finding perl scripts
900 my $finder = File::Find::Rule->or
902 File::Find::Rule->name( '*.pl' ),
903 File::Find::Rule->exec(
904 sub {
905 if (open my $fh, $_) {
906 my $shebang = <$fh>;
907 close $fh;
908 return $shebang =~ /^#!.*\bperl/;
910 return 0;
911 } ),
914 Based upon this message http://use.perl.org/comments.pl?sid=7052&cid=10842
916 =item ignore CVS directories
918 my $rule = File::Find::Rule->new;
919 $rule->or($rule->new
920 ->directory
921 ->name('CVS')
922 ->prune
923 ->discard,
924 $rule->new);
926 Note here the use of a null rule. Null rules match anything they see,
927 so the effect is to match (and discard) directories called 'CVS' or to
928 match anything.
930 =back
932 =head1 TWO FOR THE PRICE OF ONE
934 File::Find::Rule also gives you a procedural interface. This is
935 documented in L<File::Find::Rule::Procedural>
937 =head1 EXPORTS
939 L</find>, L</rule>
941 =head1 TAINT MODE INTERACTION
943 As of 0.32 File::Find::Rule doesn't capture the current working directory in
944 a taint-unsafe manner. File::Find itself still does operations that the taint
945 system will flag as insecure but you can use the L</extras> feature to ask
946 L<File::Find> to internally C<untaint> file paths with a regex like so:
948 my $rule = File::Find::Rule->extras({ untaint => 1 });
950 Please consult L<File::Find>'s documentation for C<untaint>,
951 C<untaint_pattern>, and C<untaint_skip> for more information.
953 =head1 BUGS
955 The code makes use of the C<our> keyword and as such requires perl version
956 5.6.0 or newer.
958 Currently it isn't possible to remove a clause from a rule object. If
959 this becomes a significant issue it will be addressed.
961 =head1 AUTHOR
963 Richard Clamp <richardc@unixbeard.net> with input gained from this
964 use.perl discussion: http://use.perl.org/~richardc/journal/6467
966 Additional proofreading and input provided by Kake, Greg McCarroll,
967 and Andy Lester andy@petdance.com.
969 =head1 COPYRIGHT
971 Copyright (C) 2002, 2003, 2004, 2006, 2009, 2011 Richard Clamp. All Rights Reserved.
973 This module is free software; you can redistribute it and/or modify it
974 under the same terms as Perl itself.
976 =head1 SEE ALSO
978 L<File::Find>, L<Text::Glob>, L<Number::Compare>, find(1)
980 If you want to know about the procedural interface, see
981 L<File::Find::Rule::Procedural>, and if you have an idea for a neat
982 extension L<File::Find::Rule::Extending>
984 =cut
986 Implementation notes:
988 $self->rules is an array of hashrefs. it may be a code fragment or a call
989 to a subroutine.
991 Anonymous subroutines are stored in the $self->subs hashref keyed on the
992 stringfied version of the coderef.
994 When one File::Find::Rule object is combined with another, such as in the any
995 and not operations, this entire hash is merged.
997 The _compile method walks the rules element and simply glues the code
998 fragments together so they can be compiled into an anyonymous File::Find
999 match sub for speed
1002 [*] There's probably a win to be made with the current model in making
1003 stat calls use C<_>. For
1005 find( file => size => "> 20M" => size => "< 400M" );
1007 up to 3 stats will happen for each candidate. Adding a priming _
1008 would be a bit blind if the first operation was C< name => 'foo' >,
1009 since that can be tested by a single regex. Simply checking what the
1010 next type of operation doesn't work since any arbritary exec sub may
1011 or may not stat. Potentially worse, they could stat something else
1012 like so:
1014 # extract from the worlds stupidest make(1)
1015 find( exec => sub { my $f = $_; $f =~ s/\.c$/.o/ && !-e $f } );
1017 Maybe the best way is to treat C<_> as invalid after calling an exec,
1018 and doc that C<_> will only be meaningful after stat and -X tests if
1019 they're wanted in exec blocks.
1021 END_OF_FILE
1023 'Number/Compare.pm' => <<'END_OF_FILE',
1024 package Number::Compare;
1025 use strict;
1026 use Carp qw(croak);
1027 use vars qw/$VERSION/;
1028 $VERSION = '0.03';
1030 sub new {
1031 my $referent = shift;
1032 my $class = ref $referent || $referent;
1033 my $expr = $class->parse_to_perl( shift );
1035 bless eval "sub { \$_[0] $expr }", $class;
1038 sub parse_to_perl {
1039 shift;
1040 my $test = shift;
1042 $test =~ m{^
1043 ([<>]=?)? # comparison
1044 (.*?) # value
1045 ([kmg]i?)? # magnitude
1046 $}ix
1047 or croak "don't understand '$test' as a test";
1049 my $comparison = $1 || '==';
1050 my $target = $2;
1051 my $magnitude = $3 || '';
1052 $target *= 1000 if lc $magnitude eq 'k';
1053 $target *= 1024 if lc $magnitude eq 'ki';
1054 $target *= 1000000 if lc $magnitude eq 'm';
1055 $target *= 1024*1024 if lc $magnitude eq 'mi';
1056 $target *= 1000000000 if lc $magnitude eq 'g';
1057 $target *= 1024*1024*1024 if lc $magnitude eq 'gi';
1059 return "$comparison $target";
1062 sub test { $_[0]->( $_[1] ) }
1066 __END__
1068 =head1 NAME
1070 Number::Compare - numeric comparisons
1072 =head1 SYNOPSIS
1074 Number::Compare->new(">1Ki")->test(1025); # is 1025 > 1024
1076 my $c = Number::Compare->new(">1M");
1077 $c->(1_200_000); # slightly terser invocation
1079 =head1 DESCRIPTION
1081 Number::Compare compiles a simple comparison to an anonymous
1082 subroutine, which you can call with a value to be tested again.
1084 Now this would be very pointless, if Number::Compare didn't understand
1085 magnitudes.
1087 The target value may use magnitudes of kilobytes (C<k>, C<ki>),
1088 megabytes (C<m>, C<mi>), or gigabytes (C<g>, C<gi>). Those suffixed
1089 with an C<i> use the appropriate 2**n version in accordance with the
1090 IEC standard: http://physics.nist.gov/cuu/Units/binary.html
1092 =head1 METHODS
1094 =head2 ->new( $test )
1096 Returns a new object that compares the specified test.
1098 =head2 ->test( $value )
1100 A longhanded version of $compare->( $value ). Predates blessed
1101 subroutine reference implementation.
1103 =head2 ->parse_to_perl( $test )
1105 Returns a perl code fragment equivalent to the test.
1107 =head1 AUTHOR
1109 Richard Clamp <richardc@unixbeard.net>
1111 =head1 COPYRIGHT
1113 Copyright (C) 2002,2011 Richard Clamp. All Rights Reserved.
1115 This module is free software; you can redistribute it and/or modify it
1116 under the same terms as Perl itself.
1118 =head1 SEE ALSO
1120 http://physics.nist.gov/cuu/Units/binary.html
1122 =cut
1124 END_OF_FILE
1128 unshift @INC, sub {
1129 my ($me, $packfile) = @_;
1130 return unless exists $file_for{$packfile};
1131 (my $text = $file_for{$packfile}) =~ s/^\ //gmxs;
1132 chop($text); # added \n at the end
1133 open my $fh, '<', \$text or die "open(): $!\n";
1134 return $fh;
1136 } ## end BEGIN
1137 # __MOBUNDLE_INCLUSION__
1139 use File::Find::Rule;
1141 my %config = (
1142 output => '-',
1143 remote => catfile(dirname(realpath(__FILE__)), 'remote'),
1144 tarfile => [],
1145 heredir => [],
1146 rootdir => [],
1147 root => [],
1148 tarfile => [],
1149 deploy => [],
1150 passthrough => 0,
1152 GetOptions(
1153 \%config,
1155 usage! help! man! version!
1157 bundle|all-exec|X!
1158 bzip2|bz2|j!
1159 cleanup|c!
1160 deploy|exec|d=s@
1161 gzip|gz|z!
1162 heredir|H=s@
1163 include-archive-tar|T!
1164 no-tar!
1165 output|o=s
1166 passthrough|P!
1167 root|r=s@
1168 rootdir|in-root|R=s@
1169 tar|t=s
1170 tarfile|F=s@
1171 tempdir-mode|m=s
1172 workdir|work-directory|deploy-directory|w=s
1174 ) or pod2usage(message => "invalid command line", -verbose => 99, -sections => ' ');
1175 pod2usage(message => "$0 $VERSION", -verbose => 99, -sections => ' ')
1176 if $config{version};
1177 pod2usage(-verbose => 99, -sections => 'USAGE') if $config{usage};
1178 pod2usage(-verbose => 99, -sections => 'USAGE|EXAMPLES|OPTIONS')
1179 if $config{help};
1180 pod2usage(-verbose => 2) if $config{man};
1182 pod2usage(
1183 message => 'working directory must be an absolute path',
1184 -verbose => 99,
1185 -sections => ''
1186 ) if exists $config{workdir} && !file_name_is_absolute($config{workdir});
1188 if ($config{'include-archive-tar'}) {
1189 $config{remote} = catfile(dirname(realpath(__FILE__)), 'remote-at');
1190 if (!-e $config{remote}) { # "make" it
1191 print {*STDERR} "### Making remote-at...\n";
1192 my $startdir = cwd();
1193 chdir dirname realpath __FILE__;
1194 system {'make'} qw( make remote-at );
1195 chdir $startdir;
1196 } ## end if (!-e $config{remote...})
1197 } ## end if ($config{'include-archive-tar'...})
1199 # Establish output channel
1200 my $out_fh = \*STDOUT;
1201 if ($config{output} ne '-') {
1202 open my $fh, '>', $config{output} ## no critic
1203 or croak "open('$config{output}'): $OS_ERROR";
1204 $out_fh = $fh;
1206 binmode $out_fh;
1208 # Emit script code to be executed remotely. It is guaranteed to end
1209 # with __END__, so that all what comes next is data
1210 print {$out_fh} get_remote_script();
1212 # Where all the data will be kept
1213 print_configuration($out_fh, \%config);
1215 print_here_stuff($out_fh, \%config, @ARGV);
1216 print_root_stuff($out_fh, \%config);
1218 close $out_fh;
1220 # Set as executable
1221 if ($config{output} ne '-') {
1222 chmod oct(755), $config{output}
1223 or carp "chmod(0755, '$config{output}'): $OS_ERROR";
1226 sub header {
1227 my %params = @_;
1228 my $namesize = length $params{name};
1229 return "$namesize $params{size}\n$params{name}";
1232 sub print_configuration { # FIXME
1233 my ($fh, $config) = @_;
1234 my %general_configuration;
1235 for my $name (
1236 qw( workdir cleanup bundle deploy
1237 gzip bzip2 passthrough tempdir-mode )
1240 $general_configuration{$name} = $config->{$name}
1241 if exists $config->{$name};
1242 } ## end for my $name (qw( workdir cleanup bundle deploy...))
1243 my $configuration = Dumper \%general_configuration;
1244 print {$fh} header(name => 'config.pl', size => length($configuration)),
1245 "\n", $configuration, "\n\n";
1246 } ## end sub print_configuration
1248 # Process files and directories. All these will be reported in the
1249 # extraction directory, i.e. basename() will be applied to them. For
1250 # directories, they will be re-created
1251 sub print_here_stuff {
1252 my $fh = shift;
1253 my $config = shift;
1254 my @ARGV = @_;
1256 my $ai = Deployable::Tar->new($config);
1257 $ai->add(
1258 '.' => \@ARGV,
1259 map { $_ => ['.'] } @{$config->{heredir}}
1262 print {$fh} header(name => 'here', size => $ai->size()), "\n";
1263 $ai->copy_to($fh);
1264 print {$fh} "\n\n";
1266 return;
1267 } ## end sub print_here_stuff
1269 sub print_root_stuff {
1270 my ($fh, $config) = @_;
1272 my $ai = Deployable::Tar->new($config);
1273 $ai->add(
1274 '.' => $config->{rootdir},
1275 (undef, $config->{tarfile}),
1276 map { $_ => ['.'] } @{$config->{root}}
1279 print {$fh} header(name => 'root', size => $ai->size()), "\n";
1280 $ai->copy_to($fh);
1281 print {$fh} "\n\n";
1283 return;
1284 } ## end sub print_root_stuff
1286 sub get_remote_script {
1287 my $fh;
1288 if (-e $config{remote}) {
1289 open $fh, '<', $config{remote}
1290 or croak "open('$config{remote}'): $OS_ERROR";
1292 else {
1293 no warnings 'once';
1294 $fh = \*DATA;
1296 my @lines;
1297 while (<$fh>) {
1298 last if /\A __END__ \s*\z/mxs;
1299 push @lines, $_;
1301 close $fh;
1302 return join '', @lines, "__END__\n";
1303 } ## end sub get_remote_script
1305 package Deployable::Tar;
1307 sub new {
1308 my $package = shift;
1309 my $self = {ref $_[0] ? %{$_[0]} : @_};
1310 $package = 'Deployable::Tar::Internal';
1311 if (!$self->{'no-tar'}) {
1312 if ((exists $self->{tar}) || (open my $fh, '-|', 'tar', '--help')) {
1313 $package = 'Deployable::Tar::External';
1314 $self->{tar} ||= 'tar';
1316 } ## end if (!$self->{'no-tar'})
1317 bless $self, $package;
1318 $self->initialise();
1319 return $self;
1320 } ## end sub new
1322 package Deployable::Tar::External;
1323 use File::Temp qw( :seekable );
1324 use English qw( -no_match_vars );
1325 use Cwd ();
1326 use Carp;
1327 our @ISA = qw( Deployable::Tar );
1329 sub initialise {
1330 my $self = shift;
1331 $self->{_temp} = File::Temp->new();
1332 $self->{_filename} = Cwd::abs_path($self->{_temp}->filename());
1333 return $self;
1334 } ## end sub initialise
1336 sub add {
1337 my $self = shift;
1338 my $tar = $self->{tar};
1339 delete $self->{_compressed};
1340 while (@_) {
1341 my ($directory, $stuff) = splice @_, 0, 2;
1342 my @stuff = @$stuff;
1343 if (defined $directory) {
1344 while (@stuff) {
1345 my @chunk = splice @stuff, 0, 50;
1346 system {$tar} $tar, 'rvf', $self->{_filename},
1347 '-C', $directory, '--', @chunk;
1349 } ## end if (defined $directory)
1350 else { # it's another TAR file, concatenate
1351 while (@stuff) {
1352 my @chunk = splice @stuff, 0, 50;
1353 system {$tar} $tar, 'Avf', $self->{_filename}, '--', @chunk;
1355 } ## end else [ if (defined $directory)]
1356 } ## end while (@_)
1357 return $self;
1358 } ## end sub add
1360 sub _compress {
1361 my $self = shift;
1362 return if exists $self->{_compressed};
1364 $self->{_temp}->sysseek(0, SEEK_SET);
1365 if ($self->{bzip2}) {
1366 require IO::Compress::Bzip2;
1367 $self->{_compressed} = File::Temp->new();
1369 # double-quotes needed to force usage of filename
1370 # instead of filehandle
1371 IO::Compress::Bzip2::bzip2($self->{_temp}, "$self->{_compressed}");
1372 } ## end if ($self->{bzip2})
1373 elsif ($self->{gzip}) {
1374 require IO::Compress::Gzip;
1375 $self->{_compressed} = File::Temp->new();
1377 # double-quotes needed to force usage of filename
1378 # instead of filehandle
1379 IO::Compress::Gzip::gzip($self->{_temp}, "$self->{_compressed}");
1380 } ## end elsif ($self->{gzip})
1381 else {
1382 $self->{_compressed} = $self->{_temp};
1385 return $self;
1386 } ## end sub _compress
1388 sub size {
1389 my ($self) = @_;
1390 $self->_compress();
1391 return (stat $self->{_compressed})[7];
1394 sub copy_to {
1395 my ($self, $out_fh) = @_;
1396 $self->_compress();
1397 my $in_fh = $self->{_compressed};
1398 $in_fh->sysseek(0, SEEK_SET);
1399 while ('true') {
1400 my $nread = $in_fh->sysread(my $buffer, 4096);
1401 croak "sysread(): $OS_ERROR" unless defined $nread;
1402 last unless $nread;
1403 print {$out_fh} $buffer;
1404 } ## end while ('true')
1405 return $self;
1406 } ## end sub copy_to
1408 package Deployable::Tar::Internal;
1409 use Archive::Tar ();
1410 use Cwd ();
1411 use File::Find::Rule ();
1412 use Carp qw< croak >;
1413 our @ISA = qw( Deployable::Tar );
1415 sub initialise {
1416 my $self = shift;
1417 $self->{_tar} = Archive::Tar->new();
1418 return $self;
1421 sub add {
1422 my $self = shift;
1423 delete $self->{_string};
1424 my $tar = $self->{_tar};
1425 my $cwd = Cwd::getcwd();
1426 while (@_) {
1427 my ($directory, $stuff) = splice @_, 0, 2;
1428 if (defined $directory) {
1429 chdir $directory;
1430 for my $item (@$stuff) {
1431 $tar->add_files($_) for File::Find::Rule->in($item);
1433 chdir $cwd;
1434 } ## end if (defined $directory)
1435 else { # It's another TAR file to be concatenated
1436 for my $item (@$stuff) {
1437 my $iterator = Archive::Tar->iter($item);
1438 while (my $f = $iterator->()) {
1439 $tar->add_files($f);
1443 } ## end while (@_)
1444 return $self;
1445 } ## end sub add
1447 sub size {
1448 my ($self) = @_;
1449 $self->{_string} = $self->{_tar}->write()
1450 unless exists $self->{_string};
1451 return length $self->{_string};
1452 } ## end sub size
1454 sub copy_to {
1455 my ($self, $out_fh) = @_;
1456 $self->{_string} = $self->{_tar}->write()
1457 unless exists $self->{_string};
1458 print {$out_fh} $self->{_string};
1459 } ## end sub copy_to
1461 =head1 NAME
1463 deployable - create a deploy script for some files/scripts
1465 =head1 VERSION
1467 See version at beginning of script, variable $VERSION, or call
1469 shell$ deployable --version
1471 =head1 USAGE
1473 deployable [--usage] [--help] [--man] [--version]
1475 deployable [--bundle|--all-exec|-X] [--bzip2|--bz2|-j] [--cleanup|-c]
1476 [--deploy|--exec|d <program>] [--gzip|-gz|-z]
1477 [--heredir|-H <dirname>] [--include-archive-tar|-T]
1478 [--no-tar] [--output|-o <filename>] [--root|-r <dirname>]
1479 [--rootdir|--in-root|-R <dirname>] [--tar|-t <program-path>]
1480 [--tarfile|-F <filename>] [--tempdir-mode|-m <mode>]
1481 [--workdir|-w <path>] [ files or directories... ]
1483 =head1 EXAMPLES
1485 # pack some files and a deploy script together.
1486 shell$ deployable script.sh file.txt some/directory -d script.sh
1488 # Use a directory's contents as elements for the target root
1489 shell$ ls -1 /path/to/target/root
1494 # The above will be deployed as /etc, /opt, /usr and /var
1495 shell$ deployable -o dep.pl --root /path/to/target/root
1497 # Include sub-directory etc/ for inclusion and extraction
1498 # directly as /etc/
1499 shell$ deployable -o dep.pl --in-root etc/
1501 =head1 DESCRIPTION
1503 This is a meta-script to create deploy scripts. The latter ones are
1504 suitable to be distributed in order to deploy something.
1506 You basically have to provide two things: files to install and programs
1507 to be executed. Files can be put directly into the deployed script, or
1508 can be included in gzipped tar archives.
1510 When called, this script creates a deploy script for you. This script
1511 includes all the specified files, and when executed it will extract
1512 those files and execute the given programs. In this way, you can ship
1513 both files and logic needed to correctly install those files, but this
1514 is of course of of scope.
1516 All files and archives will be extracted under a configured path
1517 (see L<--workdir> below), which we'll call I<workdir> from now on. Under
1518 the I<workdir> a temporary directory will be created, and the files
1519 will be put in the temporary directory. You can specify if you want to
1520 clean up this temporary directory or keep it, at your choice. (You're able
1521 to both set a default for this cleanup when invoking deployable, or when
1522 invoking the deploy script itself). The temporary directory will be
1523 called I<tmpdir> in the following.
1525 There are several ways to embed files to be shipped:
1527 =over
1529 =item *
1531 pass the name of an already-prepared tar file via L</--tarfile>. The
1532 contents of this file will be assumed to be referred to the root
1533 directory;
1535 =item *
1537 specify the file name directly on the command line. A file given in this
1538 way will always be extracted into the I<tmpdir>, whatever its initial path
1539 was;
1541 =item *
1543 specify the name of a directory on the command line. In this case,
1544 C<tar> will be used to archive the directory, with the usual option to
1545 turn absolute paths into relative ones; this means that directories will
1546 be re-created under I<tmpdir> when extraction is performed;
1548 =item *
1550 give the name of a directory to be used as a "here directory", using
1551 the C<--heredir|-H> option. This is much the same as giving the directory
1552 name (see above), but in this case C<tar> will be told to change into the
1553 directory first, and archive '.'. This means that the contents of the
1554 "here-directory" will be extracted directly into I<tmpdir>.
1556 =back
1558 =head2 Extended Example
1560 Suppose you have a few server which have the same configuration, apart
1561 from some specific stuff (e.g. the hostname, the IP addresses, etc.).
1562 You'd like to perform changes to all with the minimum work possible...
1563 so you know you should script something.
1565 For example, suppose you want to update a few files in /etc, setting these
1566 files equal for all hosts. You would typically do the following:
1568 # In your computer
1569 shell$ mkdir -p /tmp/newfiles/etc
1570 shell$ cd /tmp/newfiles/etc
1571 # Craft the new files
1572 shell$ cd ..
1573 shell$ tar cvzf newetc.tar.gz etc
1575 # Now, for each server:
1576 shell$ scp newetc.tar.gz $server:/tmp
1577 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
1580 So far, so good. But what if you need to kick in a little more logic?
1581 For example, if you update some configuration files, you'll most likey
1582 want to restart some services. So you could do the following:
1584 shell$ mkdir -p /tmp/newfiles/tmp
1585 shell$ cd /tmp/newfiles/tmp
1586 # craft a shell script to be executed remotely and set the exec bit
1587 # Suppose it's called deploy.sh
1588 shell$ cd ..
1589 shell$ tar cvzf newetc.tar.gz etc tmp
1591 # Now, for each server:
1592 shell$ scp newetc.tar.gz $server:/tmp
1593 shell$ ssh $server tar xvzf /tmp/newetc.tar.gz -C /
1594 shell$ ssh $server /tmp/deploy.sh
1596 And what if you want to install files depending on the particular machine?
1597 Or you have a bundle of stuff to deploy and a bunch of scripts to execute?
1598 You can use deployable. In this case, you can do the following:
1600 shell$ mkdir -p /tmp/newfiles/etc
1601 shell$ cd /tmp/newfiles/etc
1602 # Craft the new files
1603 shell$ cd ..
1604 # craft a shell script to be executed remotely and set the exec bit
1605 # Suppose it's called deploy.sh
1606 shell$ deployable -o deploy.pl -R etc deploy.sh -d deploy.sh
1608 # Now, for each server
1609 shell$ scp deploy.pl $server:/tmp
1610 shell$ ssh $server /tmp/deploy.pl
1612 And you're done. This can be particularly useful if you have another
1613 layer of deployment, e.g. if you have to run a script to decide which
1614 of a group of archives should be deployed. For example, you could craft
1615 a different new "etc" for each server (which is particularly true if
1616 network configurations are in the package), and produce a simple script
1617 to choose which file to use based on the MAC address of the machine. In
1618 this case you could have:
1620 =over
1622 =item newetc.*.tar.gz
1624 a bunch of tar files with the configurations for each different server
1626 =item newetc.list
1628 a list file with the association between the MAC addresses and the
1629 real tar file to deploy from the bunch in the previous bullet
1631 =item deploy-the-right-stuff.sh
1633 a script to get the real MAC address of the machine, select the right
1634 tar file and do the deployment.
1636 =back
1638 So, you can do the following:
1640 shell$ deployable -o deploy.pl newetc.*.tar.gz newetc.list \
1641 deploy-the-right-stuff.sh --exec deploy-the-right-stuff.sh
1643 # Now, for each server:
1644 shell$ scp deploy.pl $server:/tmp
1645 shell$ ssh $server /tmp/deploy.pl
1647 So, once you have the deploy script on the target machine all you need
1648 to do is to execute it. This can come handy when you cannot access the
1649 machines from the network, but you have to go there physically: you
1650 can prepare all in advance, and just call the deploy script.
1653 =head1 OPTIONS
1655 Meta-options:
1657 =over
1659 =item B<--help>
1661 print a somewhat more verbose help, showing usage, this description of
1662 the options and some examples from the synopsis.
1664 =item B<--man>
1666 print out the full documentation for the script.
1668 =item B<--usage>
1670 print a concise usage line and exit.
1672 =item B<--version>
1674 print the version of the script.
1676 =back
1678 Real-world options:
1680 =over
1682 =item B<< --bundle | --all-exec | -X >>
1684 Set bundle flag in the produced script. If the bundle flag is set, the
1685 I<deploy script> will treat all executables in the main deployment
1686 directory as scripts to be executed.
1688 By default the flag is not set.
1690 =item B<< --bzip2 | --bz2 | -j >>
1692 Compress tar archives with bzip2.
1694 =item B<< --cleanup | -c >>
1696 Set cleanup flag in the produced script. If the cleanup flag is set, the
1697 I<deploy script> will clean up after having performed all operations.
1699 You can set this flag to C<0> by using C<--no-cleanup>.
1701 =item B<< --deploy | --exec | -d <filename> >>
1703 Set the name of a program to execute after extraction. You can provide
1704 multiple program names, they will be executed in the same order.
1706 =item B<< --gzip | --gz | -z >>
1708 Compress tar archives with gzip.
1710 =item B<< --heredir | -H <path> >>
1712 Set the name of a "here directory" (see L<DESCRIPTION>). You can use this
1713 option multiple times to provide multiple directories.
1715 =item B<< --include-archive-tar | -T >>
1717 Embed L<Archive::Tar> (with its dependencies L<Archive::Tar::Constant> and
1718 L<Archive::Tar::File>) inside the final script. Use this when you know (or
1719 aren't sure) that L<Archive::Tar> will not be available in the target
1720 machine.
1722 =item B<< --no-tar >>
1724 Don't use system C<tar>.
1726 =item B<< --output | -o <filename> >>
1728 Set the output file name. By default the I<deploy script> will be given
1729 out on the standard output; if you provide a filename (different from
1730 C<->, of course!) the script will be saved there and the permissions will
1731 be set to 0755.
1733 =item B<< --root | -r <dirname> >>
1735 Include C<dirname> contents for deployment under root directory. The
1736 actual production procedure is: hop into C<dirname> and grab a tarball
1737 of C<.>. During deployment, hop into C</> and extract the tarball.
1739 This is useful if you're already building up the absolute deployment
1740 layout under a given directory: just treat that directory as if it were
1741 the root of the target system.
1743 =item B<< --rootdir | --in-root | -R <filename> >>
1745 Include C<filename> as an item that will be extracted under root
1746 directory. The actual production procedure is: grab a tarball of
1747 C<filename>. During deployment, hop into C</> and extract the tarball.
1749 This is useful e.g. if you have a directory (or a group of directories)
1750 that you want to deploy directly under the root.
1752 Note that the C<--rootdir> alias is kept for backwards compatibility
1753 but is not 100% correct - you can specify both a dirname (like it was
1754 previously stated) or a single file with this option. This is why it's
1755 more readably to use C<--in-root> instead.
1757 =item B<< --tar | -t <program-path> >>
1759 Set the system C<tar> program to use.
1761 =item B<< --tempdir-mode | -m >>
1763 set default permissions for temporary directory of deployable script
1765 =item B<< --workdir | --deploy-directory | -w <path> >>
1767 Set the working directory for the deploy.
1769 =back
1771 =head1 ROOT OR ROOTDIR?
1773 There are two options that allow you to specify things to be deployed
1774 in C</>, so what should you use? Thing is... whatever you want!
1776 If you have a bunch of directories that have to appear under root, probably
1777 your best bet is to put them all inside a directory called C<myroot> and
1778 use option C<--root>:
1780 shell$ mkdir -p myroot/{etc,opt,var,lib,usr,whatever}
1781 # Now put stuff in the directories created above...
1782 shell$ deployable --root myroot ...
1784 On the other hand, if you just want to put stuff starting from one or
1785 two directories that have to show up in C</>, you can avoid creating
1786 the extra C<myroot> directory and use C<--in-root> instead:
1788 shell$ mkdir -p etc/whatever
1789 # Now put stuff in etc/whatever...
1790 shell$ deployable --in-root etc ...
1792 They are indeed somehow equivalent, the first avoiding you much typing
1793 when you have many directories to be deployed starting from root (just
1794 put them into the same subdirectory), the second allowing you to avoid
1795 putting an extra directory layer.
1797 There is indeed an additional catch that makes them quite different. When
1798 you use C<root>, the whole content of the directory specified will be
1799 used as a base, so you will end up with a listing like this:
1801 opt/
1802 opt/local/
1803 opt/local/application/
1804 opt/local/application/myfile.txt
1805 opt/local/application/otherfile.txt
1807 i.e. all intermediate directories will be saved. On the other hand, when
1808 you specify a directory with C<--in-root>, you're not limited to provide
1809 a "single-step" directory, so for example:
1811 shell$ deployable --in-root opt/local/application
1813 will result in the following list of files/directories to be stored:
1815 opt/local/application/
1816 opt/local/application/myfile.txt
1817 opt/local/application/otherfile.txt
1819 i.e. the upper level directories will not be included. What is better for
1820 you is for you to judge.
1822 =head1 THE DEPLOY SCRIPT
1824 The net result of calling this script is to produce another script,
1825 that we call the I<deploy script>. This script is made of two parts: the
1826 code, which is fixed, and the configurations/files, which is what is
1827 actually produced. The latter part is put after the C<__END__> marker,
1828 as usual.
1830 Stuff in the configuration part is always hexified in order to prevent
1831 strange tricks or errors. Comments will help you devise what's inside the
1832 configurations themselves.
1834 The I<deploy script> has options itself, even if they are quite minimal.
1835 In particular, it supports the same options C<--workdir|-w> and
1836 C<--cleanup> described above, allowing the final user to override the
1837 configured values. By default, the I<workdir> is set to C</tmp>
1838 and the script will clean up after itself.
1840 The following options are supported in the I<deploy script>:
1842 =over
1844 =item B<--usage | --man | --help>
1846 print a minimal help and exit
1848 =item B<--version>
1850 print script version and exit
1852 =item B<--bundle | --all-exec | -X>
1854 treat all executables in the main deployment directory as scripts
1855 to be executed
1857 =item B<--cleanup | --no-cleanup>
1859 perform / don't perform temporary directory cleanup after work done
1861 =item B<< --deploy | --no-deploy >>
1863 deploy scripts are executed by default (same as specifying '--deploy')
1864 but you can prevent it.
1866 =item B<--dryrun | --dry-run>
1868 print final options and exit
1870 =item B<< --filelist | --list | -l >>
1872 print a list of files that are shipped in the deploy script
1874 =item B<< --heretar | --here-tar | -H >>
1876 print out the tar file that contains all the files that would be
1877 extracted in the temporary directory, useful to redirect to file or
1878 pipe to the tar program
1880 =item B<< --inspect <dirname> >>
1882 just extract all the stuff into <dirname> for inspection. Implies
1883 C<--no-deploy>, C<--no-tempdir>, ignores C<--bundle> (as a consequence of
1884 C<--no-deploy>), disables C<--cleanup> and sets the working directory
1885 to C<dirname>
1887 =item B<< --no-tar >>
1889 don't use system C<tar>
1891 =item B<< --rootar | --root-tar | -R >>
1893 print out the tar file that contains all the files that would be
1894 extracted in the root directory, useful to redirect to file or
1895 pipe to the tar program
1897 =item B<--show | --show-options | -s>
1899 print configured options and exit
1901 =item B<< --tar | -t <program-path> >>
1903 set the system C<tar> program to use.
1905 =item B<< --tarfile | -F <filename> >>
1907 add the specified C<filename> (assumed to be an uncompressed
1908 TAR file) to the lot for root extraction. This can come handy
1909 when you already have all the files backed up in a TAR archive
1910 and you're not willing to expand them (e.g. because your
1911 filesystem is case-insensitive...).
1913 =item B<< --tempdir | --no-tempdir >>
1915 by default a temporary directory is created (same as specifying
1916 C<--tempdir>), but you can execute directly in the workdir (see below)
1917 without creating it.
1919 =item B<< --tempdir-mode | -m >>
1921 temporary directories (see C<--tempdir>) created by File::Temp have
1922 permission 600 that prevents group/others from even looking at the
1923 contents. You might want to invoke some of the internal scripts
1924 from another user (e.g. via C<su>), so you can pass a mode to be
1925 set on the temporary directory.
1927 Works only if C<--tempdir> is active.
1929 =item B<--workdir | --work-directory | --deploy-directory | -w>
1931 working base directory (a temporary subdirectory will be created
1932 there anyway)
1934 =back
1936 Note the difference between C<--show> and C<--dryrun>: the former will
1937 give you the options that are "embedded" in the I<deploy script> without
1938 taking into account other options given on the command line, while the
1939 latter will give you the final options that would be used if the script
1940 were called without C<--dryrun>.
1942 =head2 Deploy Script Example Usage
1944 In the following, we'll assume that the I<deploy script> is called
1945 C<deploy.pl>.
1947 To execute the script with the already configured options, you just have
1948 to call it:
1950 shell$ ./deploy.pl
1952 If you just want to see which configurations are in the I<deploy script>:
1954 shell$ ./deploy.pl --show
1956 To see which files are included, you have two options. One is asking the
1957 script:
1959 shell$ ./deploy.pl --filelist
1961 the other is piping to tar:
1963 shell$ ./deploy.pl --tar | tar tvf -
1965 Extract contents of the script in a temp directory and simply inspect
1966 what's inside:
1968 # extract stuff into subdirectory 'inspect' for... inspection
1969 shell$ ./deploy.pl --no-tempdir --no-deploy --workdir inspect
1971 =head2 Deploy Script Requirements
1973 You'll need a working Perl with version at least 5.6.2.
1975 If you specify L</--include-archive-tar>, the module L<Archive::Tar> will
1976 be included as well. This should ease your life and avoid you to have
1977 B<tar> on the target machine. On the other hand, if you already know
1978 that B<tar> will be available, you can avoid including C<Archive::Tar>
1979 and have the generated script use it (it could be rather slower anyway).
1981 =head1 DIAGNOSTICS
1983 Each error message should be enough explicit to be understood without the
1984 need for furter explainations. Which is another way to say that I'm way
1985 too lazy to list all possible ways that this script has to fail.
1988 =head1 CONFIGURATION AND ENVIRONMENT
1990 deployable requires no configuration files or environment variables.
1992 Please note that deployable B<needs> to find its master B<remote> file
1993 to produce the final script. This must be put in the same directory where
1994 deployable is put. You should be able to B<symlink> deployable where you
1995 think it's better, anyway - it will go search for the original file
1996 and look for B<remote> inside the same directory. This does not apply to
1997 hard links, of course.
2000 =head1 DEPENDENCIES
2002 All core modules, apart the following:
2004 =over
2006 =item B<< Archive::Tar >>
2008 =item B<< File::Find::Rule >>
2010 =back
2012 =head1 BUGS AND LIMITATIONS
2014 No bugs have been reported.
2016 Please report any bugs or feature requests to the AUTHOR below.
2018 Be sure to read L<CONFIGURATION AND ENVIRONMENT> for a slight limitation
2019 about the availability of the B<remote> script.
2021 =head1 AUTHOR
2023 Flavio Poletti C<flavio [AT] polettix.it>
2026 =head1 LICENSE AND COPYRIGHT
2028 Copyright (c) 2008, Flavio Poletti C<flavio [AT] polettix.it>. All rights reserved.
2030 This script is free software; you can redistribute it and/or
2031 modify it under the same terms as Perl itself. See L<perlartistic>
2032 and L<perlgpl>.
2034 =head1 DISCLAIMER OF WARRANTY
2036 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
2037 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
2038 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
2039 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
2040 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2041 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
2042 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
2043 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
2044 NECESSARY SERVICING, REPAIR, OR CORRECTION.
2046 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
2047 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
2048 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
2049 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
2050 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
2051 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
2052 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
2053 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
2054 SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
2055 SUCH DAMAGES.
2057 =cut
2059 package main; # ensure DATA is main::DATA
2060 __DATA__
2061 #!/usr/bin/env perl
2062 # *** NOTE *** LEAVE THIS MODULE LIST AS A PARAGRAPH
2063 use strict;
2064 use warnings;
2065 use 5.006_002;
2066 our $VERSION = '0.2.0';
2067 use English qw( -no_match_vars );
2068 use Fatal qw( close chdir opendir closedir );
2069 use File::Temp qw( tempdir );
2070 use File::Path qw( mkpath );
2071 use File::Spec::Functions qw( file_name_is_absolute catfile );
2072 use File::Basename qw( basename dirname );
2073 use POSIX qw( strftime );
2074 use Getopt::Long qw( :config gnu_getopt );
2075 use Cwd qw( getcwd );
2076 use Fcntl qw( :seek );
2078 # *** NOTE *** LEAVE EMPTY LINE ABOVE
2079 my %default_config = ( # default values
2080 workdir => '/tmp',
2081 cleanup => 1,
2082 'no-exec' => 0,
2083 tempdir => 1,
2084 passthrough => 0,
2085 verbose => 0,
2088 my $DATA_POSITION = tell DATA; # GLOBAL VARIABLE
2089 my %script_config = (%default_config, get_config());
2091 my %config = %script_config;
2092 if ($ENV{DEPLOYABLE_DISABLE_PASSTHROUGH} || (!$config{passthrough})) {
2093 my %cmdline_config;
2094 GetOptions(
2095 \%cmdline_config,
2097 usage|help|man!
2098 version!
2100 bundle|all-exec|X!
2101 cleanup|c!
2102 dryrun|dry-run|n!
2103 filelist|list|l!
2104 heretar|here-tar|H!
2105 inspect|i=s
2106 no-exec!
2107 no-tar!
2108 roottar|root-tar|R!
2109 show|show-options|s!
2110 tar|t=s
2111 tempdir!
2112 tempdir-mode|m=s
2113 verbose!
2114 workdir|work-directory|deploy-directory|w=s
2116 ) or short_usage();
2117 %config = (%config, %cmdline_config);
2118 } ## end if ($ENV{DEPLOYABLE_DISABLE_PASSTHROUGH...})
2120 usage() if $config{usage};
2121 version() if $config{version};
2123 if ($config{roottar}) {
2124 binmode STDOUT;
2125 my ($fh, $size) = locate_file('root');
2126 copy($fh, \*STDOUT, $size);
2127 exit 0;
2128 } ## end if ($config{roottar})
2130 if ($config{heretar}) {
2131 binmode STDOUT;
2132 my ($fh, $size) = locate_file('here');
2133 copy($fh, \*STDOUT, $size);
2134 exit 0;
2135 } ## end if ($config{heretar})
2137 if ($config{show}) {
2138 require Data::Dumper;
2139 print {*STDOUT} Data::Dumper::Dumper(\%script_config);
2140 exit 1;
2143 if ($config{inspect}) {
2144 $config{cleanup} = 0;
2145 $config{'no-exec'} = 1;
2146 $config{'tempdir'} = 0;
2147 $config{workdir} = $config{inspect};
2148 } ## end if ($config{inspect})
2150 if ($config{dryrun}) {
2151 require Data::Dumper;
2152 print {*STDOUT} Data::Dumper::Dumper(\%config);
2153 exit 1;
2156 if ($config{filelist}) {
2157 my $root_tar = get_sub_tar('root');
2158 print "root:\n";
2159 $root_tar->print_filelist();
2160 my $here_tar = get_sub_tar('here');
2161 print "here:\n";
2162 $here_tar->print_filelist();
2163 exit 0;
2164 } ## end if ($config{filelist})
2166 # here we have to do things for real... probably, so save the current
2167 # working directory for consumption by the scripts
2168 $ENV{OLD_PWD} = getcwd();
2170 # go into the working directory, creating any intermediate if needed
2171 mkpath($config{workdir});
2172 chdir($config{workdir});
2173 print {*STDERR} "### Got into working directory '$config{workdir}'\n\n"
2174 if $config{verbose};
2176 my $tempdir;
2177 if ($config{'tempdir'}) { # Only if allowed
2178 my $me = basename(__FILE__) || 'deploy';
2179 my $now = strftime('%Y-%m-%d_%H-%M-%S', localtime);
2180 $tempdir = tempdir(
2181 join('-', $me, $now, ('X' x 10)),
2182 DIR => '.',
2183 CLEANUP => $config{cleanup}
2186 if ($config{'tempdir-mode'}) {
2187 chmod oct($config{'tempdir-mode'}), $tempdir
2188 or die "chmod('$tempdir'): $OS_ERROR\n";
2191 chdir $tempdir
2192 or die "chdir('$tempdir'): $OS_ERROR\n";
2194 if ($config{verbose}) {
2195 print {*STDERR}
2196 "### Created and got into temporary directory '$tempdir'\n";
2197 print {*STDERR} "### (will clean it up later)\n" if $config{cleanup};
2198 print {*STDERR} "\n";
2199 } ## end if ($config{verbose})
2200 } ## end if ($config{'tempdir'})
2202 eval { # Not really needed, but you know...
2203 $ENV{PATH} = '/bin:/usr/bin:/sbin:/usr/sbin';
2204 save_files();
2205 execute_deploy_programs() unless $config{'no-exec'};
2207 warn "$EVAL_ERROR\n" if $EVAL_ERROR;
2209 # Get back so that cleanup can successfully happen, if requested
2210 chdir '..' if defined $tempdir;
2212 sub locate_file {
2213 my ($filename) = @_;
2214 my $fh = \*DATA;
2215 seek $fh, $DATA_POSITION, SEEK_SET;
2216 while (!eof $fh) {
2217 chomp(my $sizes = <$fh>);
2218 my ($name_size, $file_size) = split /\s+/, $sizes;
2219 my $name = full_read($fh, $name_size);
2220 full_read($fh, 1); # "\n"
2221 return ($fh, $file_size) if $name eq $filename;
2222 seek $fh, $file_size + 2, SEEK_CUR; # includes "\n\n"
2223 } ## end while (!eof $fh)
2224 die "could not find '$filename'";
2225 } ## end sub locate_file
2227 sub full_read {
2228 my ($fh, $size) = @_;
2229 my $retval = '';
2230 while ($size) {
2231 my $buffer;
2232 my $nread = read $fh, $buffer, $size;
2233 die "read(): $OS_ERROR" unless defined $nread;
2234 die "unexpected end of file" unless $nread;
2235 $retval .= $buffer;
2236 $size -= $nread;
2237 } ## end while ($size)
2238 return $retval;
2239 } ## end sub full_read
2241 sub copy {
2242 my ($ifh, $ofh, $size) = @_;
2243 while ($size) {
2244 my $buffer;
2245 my $nread = read $ifh, $buffer, ($size < 4096 ? $size : 4096);
2246 die "read(): $OS_ERROR" unless defined $nread;
2247 die "unexpected end of file" unless $nread;
2248 print {$ofh} $buffer;
2249 $size -= $nread;
2250 } ## end while ($size)
2251 return;
2252 } ## end sub copy
2254 sub get_sub_tar {
2255 my ($filename) = @_;
2256 my ($fh, $size) = locate_file($filename);
2257 return Deployable::Tar->new(%config, fh => $fh, size => $size);
2260 sub get_config {
2261 my ($fh, $size) = locate_file('config.pl');
2262 my $config_text = full_read($fh, $size);
2263 my $config = eval 'my ' . $config_text or return;
2264 return $config unless wantarray;
2265 return %$config;
2266 } ## end sub get_config
2268 sub save_files {
2269 my $here_tar = get_sub_tar('here');
2270 $here_tar->extract();
2272 my $root_dir = $config{inspect} ? 'root' : '/';
2273 mkpath $root_dir unless -d $root_dir;
2274 my $cwd = getcwd();
2275 chdir $root_dir;
2276 my $root_tar = get_sub_tar('root');
2277 $root_tar->extract();
2278 chdir $cwd;
2280 return;
2281 } ## end sub save_files
2283 sub execute_deploy_programs {
2284 my @deploy_programs = @{$config{deploy} || []};
2286 if ($config{bundle}) { # add all executable scripts in current directory
2287 print {*STDERR} "### Auto-deploying all executables in main dir\n\n"
2288 if $config{verbose};
2289 my %flag_for = map { $_ => 1 } @deploy_programs;
2290 opendir my $dh, '.';
2291 for my $item (sort readdir $dh) {
2292 next if $flag_for{$item};
2293 next unless ((-f $item) || (-l $item)) && (-x $item);
2294 $flag_for{$item} = 1;
2295 push @deploy_programs, $item;
2296 } ## end for my $item (sort readdir...)
2297 closedir $dh;
2298 } ## end if ($config{bundle})
2300 DEPLOY:
2301 for my $deploy (@deploy_programs) {
2302 $deploy = catfile('.', $deploy)
2303 unless file_name_is_absolute($deploy);
2304 if (!-x $deploy) {
2305 print {*STDERR} "### Skipping '$deploy', not executable\n\n"
2306 if $config{verbose};
2307 next DEPLOY;
2309 print {*STDERR} "### Executing '$deploy'...\n"
2310 if $config{verbose};
2311 system {$deploy} $deploy, @ARGV;
2312 print {*STDERR} "\n"
2313 if $config{verbose};
2314 } ## end DEPLOY: for my $deploy (@deploy_programs)
2316 return;
2317 } ## end sub execute_deploy_programs
2319 sub short_usage {
2320 my $progname = basename($0);
2321 print {*STDOUT} <<"END_OF_USAGE" ;
2323 $progname version $VERSION - for help on calling and options, run:
2325 $0 --usage
2326 END_OF_USAGE
2327 exit 1;
2328 } ## end sub short_usage
2330 sub usage {
2331 my $progname = basename($0);
2332 print {*STDOUT} <<"END_OF_USAGE" ;
2333 $progname version $VERSION
2335 More or less, this script is intended to be launched without parameters.
2336 Anyway, you can also set the following options, which will override any
2337 present configuration (except in "--show-options"):
2339 * --usage | --man | --help
2340 print these help lines and exit
2342 * --version
2343 print script version and exit
2345 * --bundle | --all-exec | -X
2346 treat all executables in the main deployment directory as scripts
2347 to be executed
2349 * --cleanup | -c | --no-cleanup
2350 perform / don't perform temporary directory cleanup after work done
2352 * --deploy | --no-deploy
2353 deploy scripts are executed by default (same as specifying '--deploy')
2354 but you can prevent it.
2356 * --dryrun | --dry-run
2357 print final options and exit
2359 * --filelist | --list | -l
2360 print a list of files that are shipped in the deploy script
2362 * --heretar | --here-tar | -H
2363 print out the tar file that contains all the files that would be
2364 extracted in the temporary directory, useful to redirect to file or
2365 pipe to the tar program
2367 * --inspect | -i <dirname>
2368 just extract all the stuff into <dirname> for inspection. Implies
2369 --no-deploy, --no-tempdir, ignores --bundle (as a consequence of
2370 --no-deploy), disables --cleanup and sets the working directory
2371 to <dirname>
2373 * --no-tar
2374 don't use system "tar"
2376 * --roottar | --root-tar | -R
2377 print out the tar file that contains all the files that would be
2378 extracted in the root directory, useful to redirect to file or
2379 pipe to the tar program
2381 * --show | --show-options | -s
2382 print configured options and exit
2384 * --tar | -t <program-path>
2385 set the system "tar" program to use.
2387 * --tempdir | --no-tempdir
2388 by default a temporary directory is created (same as specifying
2389 '--tempdir'), but you can execute directly in the workdir (see below)
2390 without creating it.
2392 * --tempdir-mode | -m
2393 set permissions of temporary directory (octal string)
2395 * --workdir | --work-directory | --deploy-directory | -w
2396 working base directory (a temporary subdirectory will be created
2397 there anyway)
2399 END_OF_USAGE
2400 exit 1;
2401 } ## end sub usage
2403 sub version {
2404 print "$0 version $VERSION\n";
2405 exit 1;
2408 package Deployable::Tar;
2410 sub new {
2411 my $package = shift;
2412 my $self = {ref $_[0] ? %{$_[0]} : @_};
2413 $package = 'Deployable::Tar::Internal';
2414 if (!$self->{'no-tar'}) {
2415 if ((exists $self->{tar}) || (open my $fh, '-|', 'tar', '--help')) {
2416 $package = 'Deployable::Tar::External';
2417 $self->{tar} ||= 'tar';
2419 } ## end if (!$self->{'no-tar'})
2420 bless $self, $package;
2421 $self->initialise() if $self->can('initialise');
2422 return $self;
2423 } ## end sub new
2425 package Deployable::Tar::External;
2426 use English qw( -no_match_vars );
2428 sub initialise {
2429 my $self = shift;
2430 my $compression =
2431 $self->{bzip2} ? 'j'
2432 : $self->{gzip} ? 'z'
2433 : '';
2434 $self->{_list_command} = 'tv' . $compression . 'f';
2435 $self->{_extract_command} = 'x' . $compression . 'f';
2436 } ## end sub initialise
2438 sub print_filelist {
2439 my $self = shift;
2440 if ($self->{size}) {
2441 open my $tfh, '|-', $self->{tar}, $self->{_list_command}, '-'
2442 or die "open() on pipe to tar: $OS_ERROR";
2443 main::copy($self->{fh}, $tfh, $self->{size});
2445 return $self;
2446 } ## end sub print_filelist
2448 sub extract {
2449 my $self = shift;
2450 if ($self->{size}) {
2451 open my $tfh, '|-', $self->{tar}, $self->{_extract_command}, '-'
2452 or die "open() on pipe to tar: $OS_ERROR";
2453 main::copy($self->{fh}, $tfh, $self->{size});
2455 return $self;
2456 } ## end sub extract
2458 package Deployable::Tar::Internal;
2459 use English qw( -no_match_vars );
2461 sub initialise {
2462 my $self = shift;
2464 if ($self->{size}) {
2465 my $data = main::full_read($self->{fh}, $self->{size});
2466 open my $fh, '<', \$data
2467 or die "open() on internal variable: $OS_ERROR";
2469 require Archive::Tar;
2470 $self->{_tar} = Archive::Tar->new();
2471 $self->{_tar}->read($fh);
2472 } ## end if ($self->{size})
2474 return $self;
2475 } ## end sub initialise
2477 sub print_filelist {
2478 my $self = shift;
2479 if ($self->{size}) {
2480 print {*STDOUT} " $_\n" for $self->{_tar}->list_files();
2482 return $self;
2483 } ## end sub print_filelist
2485 sub extract {
2486 my $self = shift;
2487 if ($self->{size}) {
2488 $self->{_tar}->extract();
2490 return $self;
2491 } ## end sub extract
2493 __END__