File Coverage

blib/lib/App/Rad/Include.pm
Criterion Covered Total %
statement 9 77 11.6
branch 0 24 0.0
condition n/a
subroutine 3 12 25.0
pod 3 3 100.0
total 15 116 12.9


line stmt bran cond sub pod time code
1             package App::Rad::Include;
2 1     1   1324 use Carp qw/carp croak/;
  1         2  
  1         91  
3 1     1   7 use strict;
  1         2  
  1         34  
4 1     1   6 use warnings;
  1         1  
  1         1267  
5              
6             our $VERSION = '0.01';
7              
8             sub load {
9 0     0 1   my ($self, $c) = @_;
10 0           $c->register('include', \&include, 'include one-liner as a command');
11             }
12              
13             sub create_command_name {
14 0     0 1   my $c = shift;
15 0           my $id = 0;
16 0           foreach ( $c->commands() ) {
17 0 0         if (m/^cmd(\d+)$/) {
18 0 0         $id = $1 if ( $1 > $id );
19             }
20             }
21 0           return 'cmd' . ( $id + 1 );
22             }
23              
24              
25             # translates one-liner into
26             # a complete, readable code
27             sub _get_oneliner_code {
28 0     0     return _sanitize( _deparse($_[0]) );
29             }
30              
31              
32             #TODO: option to do it saving a backup file
33             # (behavior probably set via 'setup')
34             # inserts the string received
35             # (hopefully code) inside the
36             # user's program file as a 'sub'
37             sub _insert_code_in_file {
38 0     0     my ($command_name, $code_text) = @_;
39              
40 0           my $sub =<<"EOSUB";
41             sub $command_name {
42             $code_text
43             }
44             EOSUB
45              
46             # tidy up the code, if Perl::Tidy is available
47 0           eval "use Perl::Tidy ()";
48 0 0         if (! $@) {
49 0           my $new_code = '';
50 0           Perl::Tidy::perltidy( argv => '', source => \$sub, destination => \$new_code );
51 0           $sub = $new_code;
52             }
53              
54             #TODO: flock
55             # eval {
56             # use 'Fcntl qw(:flock)';
57             # }
58             # if ($@) {
59             # carp 'Could not load file locking module';
60             # }
61              
62             #TODO: I really should be using PPI
63             #if the user has it installed...
64             #or at least a decent parser
65 0 0         open my $fh, '+<', $0
66             or croak "error updating file $0: $!\n";
67              
68             # flock($fh, LOCK_EX) or carp "could not lock file $0: $!\n";
69              
70 0           my @file = <$fh>;
71 0           _insert_code_into_array(\@file, $sub);
72              
73             # TODO: only change the file if
74             # it's eval'd without errors
75 0 0         seek ($fh, 0, 0) or croak "error seeking file $0: $!\n";
76 0 0         print $fh @file or croak "error writing to file $0: $!\n";
77 0 0         truncate($fh, tell($fh)) or croak "error truncating file $0: $!\n";
78              
79 0           close $fh;
80             }
81              
82              
83             sub _insert_code_into_array {
84 0     0     my ($file_array_ref, $sub) = @_;
85 0           my $changed = 0;
86              
87 0           $sub = "\n\n" . $sub . "\n\n";
88              
89 0           my $line_id = 0;
90 0           while ( $file_array_ref->[$line_id] ) {
91              
92             # this is a very rudimentary parser. It assumes a simple
93             # vanilla application as shown in the main example, and
94             # tries to include the given subroutine just after the
95             # App::Rad->run(); call.
96 0 0         next unless $file_array_ref->[$line_id] =~ /App::Rad->run/;
97              
98             # now we add the sub (hopefully in the right place)
99 0           splice (@{$file_array_ref}, $line_id + 1, 0, $sub);
  0            
100 0           $changed = 1;
101 0           last;
102             }
103             continue {
104 0           $line_id++;
105             }
106 0 0         if ( not $changed ) {
107 0           croak "error finding 'App::Rad->run' call. $0 does not seem a valid App::Rad application.\n";
108             }
109             }
110              
111              
112             # deparses one-liner into a working subroutine code
113             sub _deparse {
114              
115 0     0     my $arg_ref = shift;
116              
117             # create array of perl command-line
118             # parameters passed to this one-liner
119 0           my @perl_args = ();
120 0           while ( $arg_ref->[0] =~ m/^-/o ) {
121 0           push @perl_args, (shift @{$arg_ref});
  0            
122             }
123              
124             #TODO: I don't know if "O" and
125             # "B::Deparse" can actually run the same way as
126             # a module as it does via -MO=Deparse.
127             # and while I can't figure out how to use B::Deparse
128             # to do exactly what it does via 'compile', I should
129             # at least catch the stderr buffer from qx via
130             # IPC::Cmd's run(), but that's another TODO
131 0           my $deparse = join ' ', @perl_args;
132 0           my $code = $arg_ref->[0];
133 0           my $body = qx{perl -MO=Deparse $deparse '$code'};
134 0           return $body;
135             }
136              
137              
138             # tries to adjust a subroutine into
139             # App::Rad's API for commands
140             sub _sanitize {
141 0     0     my $code = shift;
142              
143             # turns BEGIN variables into local() ones
144 0           $code =~ s{(?:local\s*\(?\s*)?(\$\^I|\$/|\$\\)}
145             {local ($1)}g;
146              
147             # and then we just strip any BEGIN blocks
148 0           $code =~ s{BEGIN\s*\{\s*(.+)\s*\}\s*$}
149             {$1}mg;
150              
151 0           my $codeprefix =<<'EOCODE';
152             my $c = shift;
153              
154             EOCODE
155 0           $code = $codeprefix . $code;
156              
157 0           return $code;
158             }
159              
160              
161             # includes a one-liner as a command.
162             # TODO: don't let the user include
163             # a control function!!!!
164             sub include {
165 0     0 1   my $c = shift;
166              
167 0           my @args = @ARGV;
168              
169 0 0         if( @args < 3 ) {
170 0           return "Sintax: $0 include [name] -perl_params 'code'.\n";
171             }
172              
173             # figure out the name of
174             # the command to insert.
175             # Either the user chose it already
176             # or we choose it for the user
177 0           my $command_name = '';
178 0 0         if ( $args[0] !~ m/^-/o ) {
179 0           $command_name = shift @args;
180              
181             # don't let the user add a command
182             # that already exists
183 0 0         if ( $c->is_command($command_name) ) {
184 0           return "Command '$command_name' already exists. Please remove it first with '$0 exclude $command_name";
185             }
186             }
187             else {
188 0           $command_name = create_command_name($c);
189             }
190 0           $c->debug("including command '$command_name'...");
191              
192 0           my $code_text = _get_oneliner_code(\@args);
193              
194 0           _insert_code_in_file($command_name, $code_text);
195              
196             # turns code string into coderef so we
197             # can register it (just in case the user
198             # needs to run it right away)
199 0     0     my $code_ref = sub { eval $code_text};
  0            
200 0           $c->register($command_name, $code_ref);
201              
202 0           return;
203             }
204              
205             42;
206             __END__