File Coverage

blib/lib/IPC/Open3/Callback/Command.pm
Criterion Covered Total %
statement 146 163 89.5
branch 77 94 81.9
condition 28 43 65.1
subroutine 27 27 100.0
pod 9 10 90.0
total 287 337 85.1


line stmt bran cond sub pod time code
1 1     1   53323 use strict;
  1         2  
  1         28  
2 1     1   5 use warnings;
  1         2  
  1         50  
3              
4             package IPC::Open3::Callback::Command;
5             $IPC::Open3::Callback::Command::VERSION = '1.19';
6             # ABSTRACT: A utility class that provides subroutines for building shell command strings.
7              
8 1     1   5 use Exporter qw(import);
  1         1  
  1         43  
9             our @EXPORT_OK =
10             qw(batch_command command command_options cp_command mkdir_command pipe_command rm_command sed_command write_command);
11              
12 1     1   5 use File::Spec;
  1         2  
  1         1864  
13              
14             sub batch_command {
15 13     13 1 24 my ( @commands, $batch_options, $command_options );
16 13         43 (@commands) = @_;
17 13 100       42 $command_options = pop(@commands)
18             if ( ref( $commands[$#commands] ) eq 'IPC::Open3::Callback::Command::CommandOptions' );
19 13 100       36 $batch_options = pop(@commands) if ( ref( $commands[$#commands] ) eq 'HASH' );
20              
21 13 100       26 push( @commands, $command_options ) if ($command_options);
22              
23             wrap(
24             $batch_options || {},
25             @commands,
26             sub {
27 13     13   29 return @_;
28             }
29 13   100     75 );
30             }
31              
32             sub command_options {
33 40     40 1 113 return IPC::Open3::Callback::Command::CommandOptions->new(@_);
34             }
35              
36             sub command {
37             wrap(
38             {},
39             @_,
40             sub {
41 31     31   57 return shift;
42             }
43 31     31 1 186 );
44             }
45              
46             sub cp_command {
47 13     13 1 21 my ( $source_path, $source_command_options, $destination_path, $destination_command_options,
48             %cp_options );
49 13         28 $source_path = _escape_path(shift);
50 13 100       35 $source_command_options = shift
51             if ( ref( $_[0] ) eq 'IPC::Open3::Callback::Command::CommandOptions' );
52 13         23 $destination_path = _escape_path(shift);
53 13 100       29 $destination_command_options = shift
54             if ( ref( $_[0] ) eq 'IPC::Open3::Callback::Command::CommandOptions' );
55 13         28 %cp_options = @_;
56              
57 13 100       38 $source_command_options = command_options() if ( !$source_command_options );
58 13 100       26 $destination_command_options = command_options() if ( !$destination_command_options );
59              
60 13         18 my $source_command;
61             my $destination_command;
62 13 100       29 if ( $cp_options{file} ) {
63              
64             # is a file, so use cat | dd
65 6 100       14 if ( $cp_options{compress} ) {
66 1         3 $source_command = command( "gzip -c $source_path", $source_command_options );
67 1         5 $destination_command =
68             pipe_command( "gunzip", "dd of=$destination_path", $destination_command_options );
69             }
70             else {
71 5         11 $source_command = command( "cat $source_path", $source_command_options );
72 5         19 $destination_command =
73             command( "dd of=$destination_path", $destination_command_options );
74             }
75             }
76             else {
77             # is a directory, so use tar or unzip
78 7 100 66     23 if ( $cp_options{archive} && $cp_options{archive} eq 'zip' ) {
79             my $temp_zip = File::Spec->catfile( $destination_path,
80 2   50     39 $cp_options{unzip_temp_file} || "temp_cp_command.zip" );
81 2         11 $source_command =
82             command(
83             batch_command( "cd $source_path", "zip -qr - .", { subshell => "bash -c " } ),
84             $source_command_options );
85 2         13 $destination_command = batch_command(
86             "dd of=$temp_zip", "unzip -qod $destination_path $temp_zip",
87             rm_command($temp_zip), $destination_command_options
88             );
89             }
90             else {
91             # default, use tar
92 5         12 my @parts = ("tar c -C $source_path .");
93 5         7 my @destination_parts = ();
94 5 100       11 if ( $cp_options{status} ) {
95 1   33     6 push(
96             @parts,
97             join(
98             '',
99             'pv -f -s `',
100             _sudo_command(
101             $source_command_options
102             && $source_command_options->get_sudo_username(),
103             pipe_command( "du -sb $source_path", 'cut -f1' )
104             ),
105             '`'
106             )
107             );
108             }
109 5 100       12 if ( $cp_options{compress} ) {
110 2         4 push( @parts, 'gzip' );
111 2         3 push( @destination_parts, 'gunzip' );
112             }
113             push(
114 5   66     19 @destination_parts,
115             _sudo_command(
116             $destination_command_options
117             && $destination_command_options->get_sudo_username(),
118             "tar x -C $destination_path"
119             )
120             );
121              
122 5         16 $source_command = command( pipe_command(@parts), $source_command_options );
123 5         20 $destination_command = command( pipe_command(@destination_parts),
124             $destination_command_options->clone( sudo_username => undef ) );
125             }
126             }
127              
128 13         44 return pipe_command( $source_command, $destination_command );
129             }
130              
131             sub mkdir_command {
132             wrap(
133             {},
134             @_,
135             sub {
136 1     1   5 return 'mkdir -p "' . join( '" "', @_ ) . '"';
137             }
138 1     1 1 8 );
139             }
140              
141             sub pipe_command {
142             wrap(
143             { command_separator => '|' },
144             @_,
145             sub {
146 32     32   55 return @_;
147             }
148 32     32 1 140 );
149             }
150              
151             sub _quote_command {
152 32     32   61 my ($command) = @_;
153 32         49 $command =~ s/\\/\\\\/g;
154 32         44 $command =~ s/`/\\`/g; # for `command`
155 32         54 $command =~ s/"/\\"/g;
156 32         72 return "\"$command\"";
157             }
158              
159             sub _escape_path {
160 26     26   39 my ($path) = @_;
161 26         63 $path =~ s/(['"`\s])/\\$1/g;
162 26         44 return $path;
163             }
164              
165             sub rm_command {
166             wrap(
167             {},
168             @_,
169             sub {
170 4     4   15 return 'rm -rf "' . join( '" "', @_ ) . '"';
171             }
172 4     4 1 22 );
173             }
174              
175             sub sed_command {
176             wrap(
177             {},
178             @_,
179             sub {
180 2     2   5 my @args = @_;
181 2         3 my $options = {};
182              
183 2 100       8 if ( ref( $args[$#args] ) eq 'HASH' ) {
184 1         2 $options = pop(@args);
185             }
186              
187 2         4 my $command = 'sed';
188 2 50       6 $command .= ' -i' if ( $options->{in_place} );
189 2 50       5 if ( defined( $options->{temp_script_file} ) ) {
190 0         0 my $temp_script_file_name = $options->{temp_script_file}->filename();
191 0 0       0 print( { $options->{temp_script_file} } join( ' ', '', map {"$_;"} @args ) )
  0         0  
  0         0  
192             if ( scalar(@args) );
193             print(
194 0         0 { $options->{temp_script_file} } join( ' ',
195             '',
196 0         0 map {"s/$_/$options->{replace_map}{$_}/g;"}
197 0         0 keys( %{ $options->{replace_map} } ) )
198 0 0       0 ) if ( defined( $options->{replace_map} ) );
199 0         0 $options->{temp_script_file}->flush();
200 0         0 $command .= " -f $temp_script_file_name";
201             }
202             else {
203 2 100       5 $command .= join( ' ', '', map {"-e '$_'"} @args ) if ( scalar(@args) );
  1         8  
204             $command .= join( ' ',
205             '',
206 1         6 map {"-e 's/$_/$options->{replace_map}{$_}/g'"}
207 1         3 keys( %{ $options->{replace_map} } ) )
208 2 100       5 if ( defined( $options->{replace_map} ) );
209             }
210 2 50       6 $command .= join( ' ', '', @{ $options->{files} } ) if ( $options->{files} );
  0         0  
211              
212 2         5 return $command;
213             }
214 2     2 1 12 );
215             }
216              
217             sub _sudo_command {
218 132     132   277 my ( $sudo_username, $command ) = @_;
219 132 100       189 if ( defined($sudo_username) ) {
220 24 100       62 $command = "sudo " . ( $sudo_username ? "-u $sudo_username " : '' ) . $command;
221             }
222 132         202 return $command;
223             }
224              
225             sub write_command {
226 5     5 1 10 my ( $filename, @lines, $write_options, $command_options );
227 5         9 $filename = shift;
228 5         11 @lines = @_;
229 5 100       18 $command_options = pop(@lines)
230             if ( ref( $lines[$#lines] ) eq 'IPC::Open3::Callback::Command::CommandOptions' );
231 5 100       15 $write_options = pop(@lines) if ( ref( $lines[$#lines] ) eq 'HASH' );
232              
233 5         12 my $remote_command = "dd of=$filename";
234 5 100 66     22 if ( defined($write_options) && defined( $write_options->{mode} ) ) {
    100          
235 3 50       7 if ( defined($command_options) ) {
    0          
236 3         11 $remote_command =
237             batch_command( $remote_command, "chmod $write_options->{mode} $filename",
238             $command_options );
239             }
240             elsif ( defined($command_options) ) {
241 0         0 $remote_command =
242             batch_command( $remote_command, "chmod $write_options->{mode} $filename" );
243             }
244             }
245             elsif ( defined($command_options) ) {
246 1         3 $remote_command = command( $remote_command, $command_options );
247             }
248              
249             my $line_separator =
250             ( defined($write_options) && defined( $write_options->{line_separator} ) )
251             ? $write_options->{line_separator}
252 5 100 100     23 : '\n';
253 5         21 return pipe_command( 'printf "' . join( $line_separator, @lines ) . '"', $remote_command );
254             }
255              
256             # Handles wrapping commands with possible ssh and command prefix
257             sub wrap {
258 83     83 0 117 my $wrap_options = shift;
259 83         105 my $builder = pop;
260 83         151 my @args = @_;
261 83         111 my ( $ssh, $username, $hostname, $sudo_username, $pretty );
262              
263 83 100       180 if ( ref( $args[$#args] ) eq 'IPC::Open3::Callback::Command::CommandOptions' ) {
264 40         62 my $options = pop(@args);
265 40   100     86 $ssh = $options->get_ssh() || 'ssh';
266 40         443 $username = $options->get_username();
267 40         331 $hostname = $options->get_hostname();
268 40         319 $sudo_username = $options->get_sudo_username();
269 40         299 $pretty = $options->get_pretty();
270             }
271              
272 83         350 my $destination_command = '';
273 83   100     194 my $command_separator = $wrap_options->{command_separator} || ';';
274 83         99 my $commands = 0;
275 83         135 foreach my $command ( &$builder(@args) ) {
276 126 50       245 if ( defined($command) ) {
277 126 100       201 if ( $commands++ > 0 ) {
278 43         60 $destination_command .= $command_separator;
279 43 50       67 if ($pretty) {
280 0         0 $destination_command .= "\n";
281             }
282             }
283              
284 126         201 $command =~ s/^(.*?[^\\]);$/$1/; # from find -exec
285              
286 126         181 $command = _sudo_command( $sudo_username, $command );
287              
288 126         240 $destination_command .= $command;
289             }
290             }
291              
292 83 100       158 if ( $wrap_options->{subshell} ) {
293 3         7 $destination_command = $wrap_options->{subshell} . _quote_command($destination_command);
294             }
295              
296 83 100 100     221 if ( !defined($username) && !defined($hostname) ) {
297              
298             # silly to ssh to localhost as current user, so dont
299 54         235 return $destination_command;
300             }
301              
302 29 100       67 my $userAt =
    100          
303             $username
304             ? ( ( $ssh =~ /plink(?:\.exe)?$/ ) ? "-l $username " : "$username\@" )
305             : '';
306              
307 29         52 $destination_command = _quote_command($destination_command);
308 29   50     131 return "$ssh $userAt" . ( $hostname || 'localhost' ) . " $destination_command";
309             }
310              
311             package IPC::Open3::Callback::Command::CommandOptions;
312             $IPC::Open3::Callback::Command::CommandOptions::VERSION = '1.19';
313 1     1   330 use parent qw(Class::Accessor);
  1         248  
  1         4  
314             __PACKAGE__->follow_best_practice;
315             __PACKAGE__->mk_ro_accessors(qw(hostname pretty ssh sudo_username username));
316              
317             sub new {
318 45     45   103 my ( $class, @args ) = @_;
319 45         106 return bless( {}, $class )->_init(@args);
320             }
321              
322             sub clone {
323 5     5   12 my ( $instance, %options ) = @_;
324 5 100 66     18 if ( exists( $instance->{hostname} ) && !exists( $options{hostname} ) ) {
325 3         6 $options{hostname} = $instance->{hostname};
326             }
327 5 50 33     10 if ( exists( $instance->{ssh} ) && !exists( $options{ssh} ) ) {
328 0         0 $options{ssh} = $instance->{ssh};
329             }
330 5 50 33     12 if ( exists( $instance->{username} ) && !exists( $options{username} ) ) {
331 0         0 $options{username} = $instance->{username};
332             }
333 5 50 66     15 if ( exists( $instance->{sudo_username} ) && !exists( $options{sudo_username} ) ) {
334 0         0 $options{sudo_username} = $instance->{sudo_username};
335             }
336 5 50 33     10 if ( exists( $instance->{pretty} ) && !exists( $options{pretty} ) ) {
337 0         0 $options{pretty} = $instance->{pretty};
338             }
339 5         17 return new( ref($instance), %options );
340             }
341              
342             sub _init {
343 45     45   101 my ( $self, %options ) = @_;
344              
345 45 100       119 $self->{hostname} = $options{hostname} if ( defined( $options{hostname} ) );
346 45 100       83 $self->{ssh} = $options{ssh} if ( defined( $options{ssh} ) );
347 45 100       85 $self->{username} = $options{username} if ( defined( $options{username} ) );
348 45 100       84 $self->{sudo_username} = $options{sudo_username} if ( defined( $options{sudo_username} ) );
349 45 50       75 $self->{pretty} = $options{pretty} if ( defined( $options{pretty} ) );
350              
351 45         155 return $self;
352             }
353              
354             1;
355              
356             __END__