File Coverage

blib/lib/App/RecordStream/Executor.pm
Criterion Covered Total %
statement 228 256 89.0
branch 8 12 66.6
condition n/a
subroutine 54 57 94.7
pod 0 17 0.0
total 290 342 84.8


line stmt bran cond sub pod time code
1             # This class handles the execution of "perl" code from the commandline on a
2             # record. Handles magic of variable hiding and also special syntax issues.
3              
4             package App::RecordStream::Executor;
5              
6             our $VERSION = "4.0.23";
7              
8 71     71   22401 use strict;
  71         153  
  71         1757  
9 71     70   392 use warnings;
  70         152  
  70         1541  
10              
11 70     70   8342 use App::RecordStream::Operation;
  70         169  
  70         30118  
12              
13             my $NEXT_ID = 0;
14             my $DEFAULT_METHOD_NAME = '__MY__DEFAULT';
15              
16             # snippets is of the form:
17             # name => {
18             # arg_names => ['a', 'b'],
19             #
20             # # one of these:
21             # code => 'code string',
22             # }
23             #
24             sub new {
25 56     56 0 132 my $class = shift;
26 56         102 my $snippets = shift;
27              
28 56 100       196 if ( ref($snippets) ne 'HASH' ) {
29 30         102 my $code = <
30             \$filename = App::RecordStream::Operation::get_current_filename();
31             \$line++;
32             $snippets;
33             CODE
34              
35 30         208 $snippets = {
36             $DEFAULT_METHOD_NAME => {
37             code => $code,
38             arg_names => ['r'],
39             },
40             };
41             }
42              
43 56         185 my $this = {
44             ID => $NEXT_ID,
45             SNIPPETS => $snippets,
46             };
47              
48 56         108 $NEXT_ID++;
49              
50 56         126 bless $this, $class;
51              
52 56         185 $this->init();
53              
54 56         250 return $this;
55             }
56              
57             sub init {
58 56     56 0 91 my $this = shift;
59 56         174 $this->create_safe_package();
60             }
61              
62             sub create_snippets {
63 56     56 0 102 my $this = shift;
64              
65 56         118 my $code = '';
66              
67 56         96 foreach my $name (keys %{$this->{'SNIPPETS'}} ) {
  56         232  
68 106         226 my $arg_names = $this->{'SNIPPETS'}->{$name}->{'arg_names'};
69 106         189 my $args_spec = '';
70              
71 106 100       263 if ( $arg_names ) {
72 56         107 $args_spec = 'my (';
73 56         124 $args_spec .= join(',', map { "\$$_"} @$arg_names);
  132         336  
74 56         138 $args_spec .= ') = @_;';
75             }
76              
77 106         251 my $method_name = $this->get_method_name($name);
78 106         314 my $snippet = $this->transform_code($this->{'SNIPPETS'}->{$name}->{'code'});
79              
80 106         376 $code .= <
81             sub $method_name {
82             $args_spec
83              
84             $snippet
85             }
86             CODE
87             }
88              
89 56         142 return $code;
90             }
91              
92             sub get_method_name {
93 212     212 0 376 my $this = shift;
94 212         333 my $name = shift;
95              
96 212         469 return '__MY__' . $name;
97             }
98              
99             sub get_safe_package_name {
100 106     106 0 184 my $this = shift;
101 106         313 return '__MY__SafeCompartment_' . $this->{'ID'};
102             }
103              
104             sub create_safe_package {
105 56     56 0 105 my $this = shift;
106 56         436 my $package_name = $this->get_safe_package_name();
107 56         178 my $snippets = $this->create_snippets();
108              
109 56         176 my $code = <
110             package $package_name;
111              
112             $snippets
113              
114             1;
115             CODE
116              
117 56         213 eval_safe_package($code);
118 56 50       167 if ( $@ ) {
119 0         0 die $@;
120             }
121              
122 56         105 foreach my $name (keys %{$this->{'SNIPPETS'}}) {
  56         427  
123 106         285 my $method_name = $this->get_method_name($name);
124 106         182 my $code_ref = \&{$package_name . '::' . $method_name};
  106         368  
125 106         358 $this->{'SNIPPETS'}->{$name}->{'CODE_REF'} = $code_ref;
126             }
127             }
128              
129             sub clear_vars {
130 0     0 0 0 my $this = shift;
131              
132 0         0 my $package_name = $this->get_safe_package_name();
133              
134 0         0 my %method_names = map { $this->get_method_name($_) => 1 } keys %{$this->{'SNIPPETS'}};
  0         0  
  0         0  
135              
136             {
137 70     70   500 no strict;
  70         164  
  70         1650  
  0         0  
138 70     70   366 no warnings;
  70         163  
  70         8246  
139              
140 0         0 foreach my $variable (keys %{$package_name . '::'}) {
  0         0  
141 0 0       0 next if ( exists $method_names{$variable} );
142 0         0 undef *{$package_name . '::' . $variable};
  0         0  
143 0         0 delete ${$package_name . '::'}{$variable};
  0         0  
144             }
145             }
146             }
147              
148             sub set_scalar {
149 0     0 0 0 my $this = shift;
150 0         0 my $name = shift;
151 0         0 my $val = shift;
152              
153 0         0 my $package_name = $this->get_safe_package_name();
154              
155             {
156 70     70   424 no strict;
  70         154  
  70         1349  
  0         0  
157 70     70   317 no warnings;
  70         161  
  70         4609  
158              
159 0         0 *{$package_name . '::' . $name} = \$val;
  0         0  
160             }
161             }
162              
163             sub get_scalar {
164 0     0 0 0 my $this = shift;
165 0         0 my $name = shift;
166              
167 0         0 my $package_name = $this->get_safe_package_name();
168              
169             {
170 70     70   396 no strict;
  70         168  
  70         1338  
  0         0  
171 70     70   315 no warnings;
  70         145  
  70         4705  
172              
173 0         0 return ${$package_name . '::' . $name};
  0         0  
174             }
175             }
176              
177             sub set_executor_method {
178 50     50 0 89 my $this = shift;
179 50         81 my $name = shift;
180 50         77 my $ref = shift;
181              
182 50         149 my $package_name = $this->get_safe_package_name();
183              
184             {
185 69     69   379 no strict;
  69         150  
  69         1284  
  50         82  
186 69     69   344 no warnings;
  69         149  
  69         21704  
187              
188 50         79 *{$package_name . "::" . $name} = $ref;
  50         244  
189             }
190             }
191              
192             sub get_code_ref {
193 166     166 0 288 my $this = shift;
194 166         270 my $name = shift;
195 166         3515 $this->{'SNIPPETS'}->{$name}->{'CODE_REF'};
196             }
197              
198             sub eval_safe_package {
199 56     56 0 112 my $__MY__code = shift;
200              
201 56         222 my $code = <
202             no strict;
203             no warnings;
204              
205             $__MY__code
206             CODE
207              
208 56 100   18   4046 eval $code;
  32     12   159  
  32     13   81  
  32     12   363  
  14     10   74  
  25     10   79  
  23     8   1116  
  22     11   113  
  22     8   79  
  19     7   292  
  19     6   92  
  17     6   39  
  17     21   1056  
  9     12   84  
  10     8   32  
  10     7   230  
  10     17   49  
  30     6   100  
  26     5   867  
  23     5   80  
  23     5   221  
  15     5   189  
  12     5   51  
  12     5   29  
  12     4   650  
  10     4   47  
  12     2   37  
  12     1   115  
  12     2   36  
  12         199  
  12         361  
  9         38  
  9         20  
  9         87  
  9         51  
  5         86  
  5         329  
  6         28  
  6         10  
  6         214  
  6         25  
  6         19  
  4         144  
  4         21  
  4         11  
  5         143  
  5         18  
  5         30  
  5         155  
  4         23  
  4         10  
  4         313  
  4         22  
  15         45  
  15         71  
  15         35  
  15         40  
  15         290  
  15         48  
  15         49  
  4         193  
  4         16  
  4         9  
  4         134  
  4         19  
  3         65  
  3         104  
  3         15  
  3         47  
  3         229  
  4         22  
  4         9  
  4         52  
  4         15  
  4         11  
  4         182  
  4         15  
  4         10  
  4         156  
  2         6  
  2         6  
  2         5  
  2         7  
  5         18  
  2         4  
  2         6  
  2         6  
  2         4  
  2         5  
  2         5  
  2         7  
  2         6  
  2         4  
  2         3  
  2         7  
  2         7  
  2         4  
  2         3  
  2         7  
  2         7  
  2         5  
  2         5  
  2         6  
  1         4  
  1         3  
  2         4  
  2         6  
  2         5  
  2         6  
  2         5  
  2         3  
  2         4  
  2         6  
  1         3  
  1         2  
  1         3  
  1         3  
  1         4  
  2         6  
  2         4  
  2         10  
  2         8  
  4         16  
  4         21  
  2         9  
209 56 50       246 if ($@) {
210 0         0 die $@;
211             }
212             }
213              
214             sub execute_code {
215 93     93 0 902 my ($this, @args) = @_;
216 91         241 return $this->execute_method($DEFAULT_METHOD_NAME, @args);
217             }
218              
219             sub execute_method {
220 164     164 0 429 my ($this, $name, @args) = @_;
221 164         442 return $this->get_code_ref($name)->(@args);
222             }
223              
224             sub transform_code {
225 957     956 0 1672 my $this = shift;
226 957         1691 my $code = shift;
227              
228 943         3443 while ( $code =~ m/\{\{(.*?)\}\}/ ) {
229 112         356 my $specifier = $1;
230 108         317 my $guessing_code = '${App::RecordStream::KeySpec::find_key($r, qq{\@' . $specifier . '})}';
231 106         632 $code =~ s/\{\{.*?\}\}/$guessing_code/;
232             }
233              
234 956         2599 return $code;
235             }
236              
237             sub usage {
238 8     22 0 27 return <
239             CODE SNIPPETS:
240             __FORMAT_TEXT__
241             Recs code snippets are perl code, with one exception. There a couple of
242             variables predefined for you, and one piece of special syntax to assist in
243             modifying hashes.
244             __FORMAT_TEXT__
245              
246             Special Variables:
247             __FORMAT_TEXT__
248             \$r - the current record object. This may be used exactly like a hash,
249             or you can use some of the special record functions, see App::RecordStream::Record for
250             more information
251              
252             \$line - This is the number of records run through the code snippet,
253             starting at 1. For most scripts this corresponds to the line number of the
254             input to the script.
255              
256             \$filename - The filename of the originating record. Note: This is only
257             useful if you're passing filenames directly to the recs script, piping from
258             other recs scripts or from cat, for instance, will not have a useful
259             filename.
260             __FORMAT_TEXT__
261              
262             Special Syntax
263             __FORMAT_TEXT__
264             Use {{search_string}} to look for a string in the keys of a record, use /
265             to nest keys. You can nest into arrays by using an index. If you are
266             vivifying arrays (if the array doesn't exist, prefix your key with # so
267             that an array rather than a hash will be created to put a / in your key,
268             escape it twice, i.e. \\/
269              
270             This is exactly the same as a key spec that is always prefaced with a @,
271             see 'man recs' for more info on key specs
272             __FORMAT_TEXT__
273              
274             For example: A record that looks like:
275             { "foo" : { "bar 1" : 1 }, "zoo" : 2}
276             Could be accessed like this:
277              
278             # value of zoo # value of \$r->{foo}->{bar 1}: (comma separate nested keys)
279             {{zoo}} {{foo/ar 1}}
280              
281             # Even assign to values (set the foo key to the value 1)
282             {{foo}} = 1
283              
284             # And auto, vivify
285             {{new_key/array_key/#0}} = 3 # creates an array within a hash within a hash
286              
287             # Index into an array
288             {{array_key/#3}} # The value of index 3 of the array ref under the
289             'array_key' hash key.
290              
291             __FORMAT_TEXT__
292             This matching is a fuzzy keyspec matching, see --help-keyspecs for more
293             details.
294             __FORMAT_TEXT__
295             USAGE
296             }
297              
298             sub options_help {
299             return (
300 26     13 0 73 ['e', 'a perl snippet to execute, optional'],
301             ['E', 'the name of a file to read as a perl snippet'],
302             ['M module[=...]', 'execute "use module..." before executing snippet; same behaviour as perl -M'],
303             ['m module[=...]', 'same as -M, but by default import nothing'],
304             );
305             }
306              
307             1;