File Coverage

blib/lib/App/Rad/Include.pm
Criterion Covered Total %
statement 9 71 12.6
branch 0 20 0.0
condition n/a
subroutine 3 11 27.2
pod 2 2 100.0
total 14 104 13.4


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