File Coverage

blib/lib/Catmandu/Fix.pm
Criterion Covered Total %
statement 187 367 50.9
branch 45 124 36.2
condition 19 43 44.1
subroutine 34 50 68.0
pod 1 27 3.7
total 286 611 46.8


line stmt bran cond sub pod time code
1             package Catmandu::Fix;
2              
3 154     154   1291827 use Catmandu::Sane;
  154         370  
  154         1042  
4              
5             our $VERSION = '1.2020';
6              
7 154     154   1189 use Moo;
  154         386  
  154         1021  
8 154     154   135160 use Catmandu::Fix::Parser;
  154         528  
  154         5262  
9 154     154   83573 use Text::Hogan::Compiler;
  154         1576248  
  154         5465  
10 154     154   138796 use Path::Tiny ();
  154         1988041  
  154         4517  
11 154     154   1264 use File::Spec ();
  154         380  
  154         2437  
12 154     154   115275 use File::Temp ();
  154         1544854  
  154         6069  
13 154         13144 use Catmandu::Util qw(
14             is_string
15             is_array_ref
16             is_hash_ref
17             is_code_ref
18             is_glob_ref
19             is_instance
20             is_able
21             require_package
22 154     154   1483 );
  154         451  
23 154     154   1245 use namespace::clean;
  154         435  
  154         1755  
24              
25             with 'Catmandu::Logger';
26             with 'Catmandu::Emit';
27              
28             has parser => (is => 'lazy');
29             has fixer => (is => 'lazy', init_arg => undef);
30             has _captures =>
31             (is => 'ro', lazy => 1, init_arg => undef, default => sub {+{}});
32             has var =>
33             (is => 'ro', lazy => 1, init_arg => undef, builder => '_generate_var');
34             has _fixes => (is => 'ro', init_arg => 'fixes', default => sub {[]});
35             has fixes =>
36             (is => 'ro', lazy => 1, init_arg => undef, builder => '_build_fixes');
37             has _reject_var => (
38             is => 'ro',
39             lazy => 1,
40             init_arg => undef,
41             builder => '_build_reject_var'
42             );
43             has _fixes_var =>
44             (is => 'ro', lazy => 1, init_arg => undef, builder => '_build_fixes_var');
45             has preprocess => (is => 'ro');
46             has _hogan =>
47             (is => 'ro', lazy => 1, init_arg => undef, builder => '_build_hogan');
48             has _hogan_vars => (is => 'ro', init_arg => 'variables');
49              
50             sub _build_parser {
51 150     150   3770 Catmandu::Fix::Parser->new;
52             }
53              
54             sub _build_fixes {
55 479     479   4606 my ($self) = @_;
56 479         1609 my $fixes_arg = $self->_fixes;
57 479         987 my $fixes = [];
58              
59 479         1248 for my $fix (@$fixes_arg) {
60              
61 481 100 100     5461 if (is_code_ref($fix)) {
    100          
    100          
    100          
    50          
62 1         10 push @$fixes, require_package('Catmandu::Fix::code')->new($fix);
63             }
64             elsif (ref $fix && ref($fix) =~ /^IO::/) {
65 1         5 my $txt = Catmandu::Util::read_io($fix);
66 1         6 $txt = $self->_preprocess($txt);
67 1         5 push @$fixes, @{$self->parser->parse($txt)};
  1         25  
68             }
69             elsif (is_glob_ref($fix)) {
70 1         6 my $fh = Catmandu::Util::io($fix, binmode => ':encoding(UTF-8)');
71 1         5 my $txt = Catmandu::Util::read_io($fh);
72 1         10 $txt = $self->_preprocess($txt);
73 1         3 push @$fixes, @{$self->parser->parse($txt)};
  1         28  
74             }
75             elsif (ref $fix) {
76 327         1009 push @$fixes, $fix;
77             }
78             elsif (is_string($fix)) {
79 151 100 66     1310 if ($fix =~ /[^\s]/ && $fix !~ /\(/) {
80 10         66 $fix = Path::Tiny::path($fix)->slurp_utf8;
81             }
82 151         15437 $fix = $self->_preprocess($fix);
83 151         7286 push @$fixes, @{$self->parser->parse($fix)};
  151         3098  
84             }
85             }
86              
87 476         4548 $fixes;
88             }
89              
90             sub _build_fixer {
91 478     478   4835 my ($self) = @_;
92              
93 478         1911 my $reject = $self->_reject;
94 478         1581 my $sub = $self->_eval_sub(
95             $self->emit,
96             args => [$self->var],
97             captures => $self->_captures
98             );
99              
100             sub {
101 495     495   1193 my $data = $_[0];
102              
103 495 100       1892 if (is_hash_ref($data)) {
104 473         9516 my $d = $sub->($data);
105 469 100 66     3030 return if ref $d && $d == $reject;
106 460         5528 return $d;
107             }
108              
109 22 100       147 if (is_array_ref($data)) {
110 16   66     120 return [grep {!(ref $_ && $_ == $reject)}
111 9         29 map {$sub->($_)} @$data];
  16         305  
112             }
113              
114 13 100       60 if (is_code_ref($data)) {
115             return sub {
116 6         901 while (1) {
117 6   100     18 my $d = $sub->($data->() // return);
118 5 50 33     31 return if ref $d && $d == $reject;
119 5         53 return $d;
120             }
121 3         21 };
122             }
123              
124 10 100 66     53 if ( is_instance($data)
      66        
125             && is_able($data, 'does')
126             && $data->does('Catmandu::Iterable'))
127             {
128 24         633 return $data->map(sub {$sub->($_[0])})
129 9 50       659 ->reject(sub {ref $_[0] && $_[0] == $reject});
  23         191  
130             }
131              
132             Catmandu::BadArg->throw(
133 1         24 "must be hashref, arrayref, coderef or iterable object");
134 475         4568 };
135             }
136              
137             sub _build_reject_var {
138 479     479   4411 my ($self) = @_;
139 479         1412 $self->capture($self->_reject);
140             }
141              
142             sub _build_fixes_var {
143 0     0   0 my ($self) = @_;
144 0         0 $self->capture($self->fixes);
145             }
146              
147             sub _build_hogan {
148 1     1   23 Text::Hogan::Compiler->new;
149             }
150              
151             sub _preprocess {
152 153     153   400 my ($self, $text) = @_;
153 153 100 66     1071 return $text unless $self->preprocess || $self->_hogan_vars;
154 1   50     6 my $vars = $self->_hogan_vars || {};
155 1         31 $self->_hogan->compile($text, {numeric_string_as_string => 1})
156             ->render($vars);
157             }
158              
159             sub fix {
160 498     498 1 224137 my ($self, $data) = @_;
161 498         9953 $self->fixer->($data);
162             }
163              
164             sub generate_var {
165 116     116 0 353 $_[0]->_generate_var;
166             }
167              
168             sub generate_label {
169 0     0 0 0 $_[0]->_generate_label;
170             }
171              
172             sub capture {
173 1420     1420 0 4693 my ($self, $capture) = @_;
174 1420         3490 my $var = $self->_generate_var;
175 1420         25412 $self->_captures->{$var} = $capture;
176 1420         11117 $var;
177             }
178              
179             sub emit {
180 479     479 0 992 my ($self) = @_;
181 479         8135 my $var = $self->var;
182 479         1367 my $err = $self->_generate_var;
183 479         8420 my $reject_var = $self->_reject_var;
184 479         959 my $perl = "";
185              
186 479         1345 $perl .= "eval {";
187              
188             # Loop over all the fixes and emit their code
189 479         8177 $perl .= $self->emit_fixes($self->fixes);
190              
191 476         1356 $perl .= "return ${var};";
192 476         1560 $perl .= $self->_reject_label . ": return ${reject_var};";
193 476         967 $perl .= "} or do {";
194 476         1350 $perl .= $self->_emit_declare_vars($err, '$@');
195 476         1489 $perl .= "${err}->throw if is_instance(${err},'Throwable::Error');";
196 476         1382 $perl .= "Catmandu::FixError->throw(message => ${err}, data => ${var});";
197 476         881 $perl .= "};";
198              
199 476         9800 $self->log->debug($perl);
200              
201 476         208743 $perl;
202             }
203              
204             # Emit an array of fixes
205             sub emit_fixes {
206 742     742 0 1678 my ($self, $fixes) = @_;
207 742         1346 my $perl = '';
208              
209 742         1393 for (my $i = 0; $i < @{$fixes}; $i++) {
  1488         3908  
210 746         1356 my $fix = $fixes->[$i];
211 746         1862 $perl .= $self->emit_fix($fix);
212             }
213              
214 742         2341 $perl;
215             }
216              
217             sub emit_reject {
218 26     26 0 124 $_[0]->_emit_reject;
219             }
220              
221             sub emit_fix {
222 871     871 0 1672 my ($self, $fix) = @_;
223 871         1243 my $perl;
224              
225 871 100       3547 if ($fix->can('emit')) {
    50          
226             $perl = $self->emit_block(
227             sub {
228 860     860   1598 my ($label) = @_;
229 860         3022 $fix->emit($self, $label);
230             }
231 860         5188 );
232             }
233             elsif ($fix->can('fix')) {
234 11         225 my $var = $self->var;
235 11         126 my $ref = $self->_generate_var;
236 11         188 $self->_captures->{$ref} = $fix;
237 11         123 $perl = "${var} = ${ref}->fix(${var});";
238             }
239             else {
240 0         0 Catmandu::Error->throw('not a fix');
241             }
242              
243 871         4029 $perl;
244             }
245              
246             sub emit_block {
247 860     860 0 1819 my ($self, $cb) = @_;
248 860         2534 my $label = $self->_generate_label;
249 860         1782 my $perl = "${label}: {";
250 860         1816 $perl .= $cb->($label);
251 860         1620 $perl .= "};";
252 860         1819 $perl;
253             }
254              
255             sub emit_clear_hash_ref {
256 0     0 0 0 my ($self, $var) = @_;
257 0         0 "undef %{${var}} if is_hash_ref(${var});";
258             }
259              
260             sub emit_value {
261 0     0 0 0 shift->_emit_value(@_);
262             }
263              
264             sub emit_string {
265 8     8 0 22 shift->_emit_string(@_);
266             }
267              
268             sub emit_match {
269 0     0 0 0 my ($self, $pattern) = @_;
270 0         0 $pattern =~ s/\//\\\//g;
271 0         0 $pattern =~ s/\\$/\\\\/; # pattern can't end with an escape in m/.../
272 0         0 "m/$pattern/";
273             }
274              
275             sub emit_substitution {
276 0     0 0 0 my ($self, $pattern, $replace) = @_;
277 0         0 $pattern =~ s/\//\\\//g;
278 0         0 $pattern =~ s/\\$/\\\\/; # pattern can't end with an escape in m/.../
279 0         0 $replace =~ s/\//\\\//g;
280 0         0 $replace =~ s/\\$/\\\\/; # pattern can't end with an escape in m/.../
281 0         0 "s/$pattern/$replace/";
282             }
283              
284             sub emit_declare_vars {
285 4     4 0 15 shift->_emit_declare_vars(@_);
286             }
287              
288             sub emit_new_scope {
289 0     0 0 0 "{";
290             }
291              
292             sub emit_end_scope {
293 0     0 0 0 "};";
294             }
295              
296             sub emit_foreach {
297 0     0 0 0 my ($self, $var, $cb) = @_;
298 0         0 my $perl = "";
299 0         0 my $v = $self->_generate_var;
300 0         0 $perl .= "foreach (\@{${var}}) {";
301 0         0 $perl .= $self->emit_declare_vars($v, '$_');
302 0         0 $perl .= $cb->($v);
303 0         0 $perl .= "}";
304 0         0 $perl;
305             }
306              
307             sub emit_foreach_key {
308 4     4 0 12 my ($self, $var, $cb) = @_;
309 4         9 my $perl = "";
310 4         13 my $v = $self->_generate_var;
311 4         15 $perl .= "foreach (keys(\%{${var}})) {";
312 4         15 $perl .= $self->emit_declare_vars($v, '$_');
313 4         12 $perl .= $cb->($v);
314 4         11 $perl .= "}";
315 4         11 $perl;
316             }
317              
318             sub emit_walk_path {
319 8     8 0 137 my ($self, $var, $keys, $cb) = @_;
320              
321 8         17 $keys = [@$keys]; # protect keys
322              
323 8 100       23 if (@$keys) { # protect $var
324 6         16 my $v = $self->_generate_var;
325 6         17 $self->_emit_declare_vars($v, $var)
326             . $self->_emit_walk_path($v, $keys, $cb);
327             }
328             else {
329 2         6 $cb->($var);
330             }
331             }
332              
333             sub _emit_walk_path {
334 12     12   25 my ($self, $var, $keys, $cb) = @_;
335              
336 12 100       38 @$keys || return $cb->($var);
337              
338 6         12 my $key = shift @$keys;
339 6         15 my $str_key = $self->emit_string($key);
340 6         13 my $perl = "";
341              
342 6 50       27 if ($key =~ /^[0-9]+$/) {
    50          
343 0         0 $perl .= "if (is_hash_ref(${var})) {";
344 0         0 $perl .= "${var} = ${var}->{${str_key}};";
345 0         0 $perl .= $self->_emit_walk_path($var, [@$keys], $cb);
346 0         0 $perl .= "} elsif (is_array_ref(${var}) && \@{${var}} > ${key}) {";
347 0         0 $perl .= "${var} = ${var}->[${key}];";
348 0         0 $perl .= $self->_emit_walk_path($var, [@$keys], $cb);
349 0         0 $perl .= "}";
350             }
351             elsif ($key eq '*') {
352 0         0 my $v = $self->_generate_var;
353 0         0 $perl .= "if (is_array_ref(${var})) {";
354             $perl .= $self->emit_foreach(
355             $var,
356             sub {
357 0     0   0 return $self->_emit_walk_path(shift, $keys, $cb);
358             }
359 0         0 );
360 0         0 $perl .= "}";
361             }
362             else {
363 6 50       15 if ($key eq '$first') {
    50          
364 0         0 $perl .= "if (is_array_ref(${var}) && \@{${var}}) {";
365 0         0 $perl .= "${var} = ${var}->[0];";
366             }
367             elsif ($key eq '$last') {
368 0         0 $perl .= "if (is_array_ref(${var}) && \@{${var}}) {";
369 0         0 $perl .= "${var} = ${var}->[\@{${var}} - 1];";
370             }
371             else {
372 6         17 $perl .= "if (is_hash_ref(${var})) {";
373 6         16 $perl .= "${var} = ${var}->{${str_key}};";
374             }
375 6         30 $perl .= $self->_emit_walk_path($var, $keys, $cb);
376 6         10 $perl .= "}";
377             }
378              
379 6         43 $perl;
380             }
381              
382             sub emit_create_path {
383 0     0 0 0 my ($self, $var, $keys, $cb) = @_;
384 0         0 $self->_emit_create_path($var, [@$keys], $cb);
385             }
386              
387             sub _emit_create_path {
388 0     0   0 my ($self, $var, $keys, $cb) = @_;
389              
390 0 0       0 @$keys || return $cb->($var);
391              
392 0         0 my $key = shift @$keys;
393 0         0 my $str_key = $self->emit_string($key);
394 0         0 my $perl = "";
395              
396 0 0       0 if ($key =~ /^[0-9]+$/) {
    0          
397 0         0 my $v1 = $self->_generate_var;
398 0         0 my $v2 = $self->_generate_var;
399 0         0 $perl .= "if (is_hash_ref(${var})) {";
400 0         0 $perl .= "my ${v1} = ${var};";
401 0         0 $perl
402             .= $self->_emit_create_path("${v1}->{${str_key}}", [@$keys], $cb);
403 0         0 $perl .= "} elsif (is_maybe_array_ref(${var})) {";
404 0         0 $perl .= "my ${v2} = ${var} //= [];";
405 0         0 $perl .= $self->_emit_create_path("${v2}->[${key}]", [@$keys], $cb);
406 0         0 $perl .= "}";
407             }
408             elsif ($key eq '*') {
409 0         0 my $v1 = $self->_generate_var;
410 0         0 my $v2 = $self->_generate_var;
411 0         0 $perl .= "if (is_array_ref(${var})) {";
412 0         0 $perl .= "my ${v1} = ${var};";
413 0         0 $perl .= "for (my ${v2} = 0; ${v2} < \@{${v1}}; ${v2}++) {";
414 0         0 $perl .= $self->_emit_create_path("${v1}->[${v2}]", $keys, $cb);
415 0         0 $perl .= "}";
416 0         0 $perl .= "}";
417             }
418             else {
419 0         0 my $v = $self->_generate_var;
420 0 0 0     0 if ( $key eq '$first'
      0        
      0        
421             || $key eq '$last'
422             || $key eq '$prepend'
423             || $key eq '$append')
424             {
425 0         0 $perl .= "if (is_maybe_array_ref(${var})) {";
426 0         0 $perl .= "my ${v} = ${var} //= [];";
427 0 0       0 if ($key eq '$first') {
    0          
    0          
    0          
428 0         0 $perl .= $self->_emit_create_path("${v}->[0]", $keys, $cb);
429             }
430             elsif ($key eq '$last') {
431 0         0 $perl .= "if (\@${v}) {";
432 0         0 $perl .= $self->_emit_create_path("${v}->[\@${v} - 1]",
433             [@$keys], $cb);
434 0         0 $perl .= "} else {";
435 0         0 $perl .= $self->_emit_create_path("${v}->[0]", [@$keys], $cb);
436 0         0 $perl .= "}";
437             }
438             elsif ($key eq '$prepend') {
439 0         0 $perl .= "if (\@${v}) {";
440 0         0 $perl .= "unshift(\@${v}, undef);";
441 0         0 $perl .= "}";
442 0         0 $perl .= $self->_emit_create_path("${v}->[0]", $keys, $cb);
443             }
444             elsif ($key eq '$append') {
445 0         0 $perl
446             .= $self->_emit_create_path("${v}->[\@${v}]", $keys, $cb);
447             }
448 0         0 $perl .= "}";
449             }
450             else {
451 0         0 $perl .= "if (is_maybe_hash_ref(${var})) {";
452 0         0 $perl .= "my ${v} = ${var} //= {};";
453 0         0 $perl
454             .= $self->_emit_create_path("${v}->{${str_key}}", $keys, $cb);
455 0         0 $perl .= "}";
456             }
457             }
458              
459 0         0 $perl;
460             }
461              
462             sub emit_get_key {
463 0     0 0 0 my ($self, $var, $key, $cb) = @_;
464              
465 0 0       0 return $cb->($var) unless defined $key;
466              
467 0         0 my $str_key = $self->emit_string($key);
468 0         0 my $perl = "";
469              
470 0 0       0 if ($key =~ /^[0-9]+$/) {
    0          
    0          
    0          
471 0         0 $perl .= "if (is_hash_ref(${var}) && exists(${var}->{${str_key}})) {";
472 0         0 $perl .= $cb->("${var}->{${str_key}}");
473 0         0 $perl .= "} elsif (is_array_ref(${var}) && \@{${var}} > ${key}) {";
474 0         0 $perl .= $cb->("${var}->[${key}]");
475 0         0 $perl .= "}";
476             }
477             elsif ($key eq '$first') {
478 0         0 $perl .= "if (is_array_ref(${var}) && \@{${var}}) {";
479 0         0 $perl .= $cb->("${var}->[0]");
480 0         0 $perl .= "}";
481             }
482             elsif ($key eq '$last') {
483 0         0 $perl .= "if (is_array_ref(${var}) && \@{${var}}) {";
484 0         0 $perl .= $cb->("${var}->[\@{${var}} - 1]");
485 0         0 $perl .= "}";
486             }
487             elsif ($key eq '*') {
488 0         0 my $i = $self->_generate_var;
489 0         0 $perl .= "if (is_array_ref(${var})) {";
490 0         0 $perl .= "for (my ${i} = 0; ${i} < \@{${var}}; ${i}++) {";
491 0         0 $perl .= $cb->("${var}->[${i}]", $i);
492 0         0 $perl .= "}}";
493             }
494             else {
495 0         0 $perl .= "if (is_hash_ref(${var}) && exists(${var}->{${str_key}})) {";
496 0         0 $perl .= $cb->("${var}->{${str_key}}");
497 0         0 $perl .= "}";
498             }
499              
500 0         0 $perl;
501             }
502              
503             sub emit_set_key {
504 0     0 0 0 my ($self, $var, $key, $val) = @_;
505              
506 0 0       0 return "${var} = $val;" unless defined $key;
507              
508 0         0 my $perl = "";
509 0         0 my $str_key = $self->emit_string($key);
510              
511 0 0       0 if ($key =~ /^[0-9]+$/) {
    0          
    0          
    0          
    0          
    0          
512 0         0 $perl .= "if (is_hash_ref(${var})) {";
513 0         0 $perl .= "${var}->{${str_key}} = $val;";
514 0         0 $perl .= "} elsif (is_array_ref(${var})) {";
515 0         0 $perl .= "${var}->[${key}] = $val;";
516 0         0 $perl .= "}";
517             }
518             elsif ($key eq '$first') {
519 0         0 $perl .= "if (is_array_ref(${var})) {";
520 0         0 $perl .= "${var}->[0] = $val;";
521 0         0 $perl .= "}";
522             }
523             elsif ($key eq '$last') {
524 0         0 $perl .= "if (is_array_ref(${var})) {";
525 0         0 $perl .= "${var}->[\@{${var}} - 1] = $val;";
526 0         0 $perl .= "}";
527             }
528             elsif ($key eq '$prepend') {
529 0         0 $perl .= "if (is_array_ref(${var})) {";
530 0         0 $perl .= "unshift(\@{${var}}, $val);";
531 0         0 $perl .= "}";
532             }
533             elsif ($key eq '$append') {
534 0         0 $perl .= "if (is_array_ref(${var})) {";
535 0         0 $perl .= "push(\@{${var}}, $val);";
536 0         0 $perl .= "}";
537             }
538             elsif ($key eq '*') {
539 0         0 my $i = $self->_generate_var;
540 0         0 $perl .= "if (is_array_ref(${var})) {";
541 0         0 $perl .= "for (my ${i} = 0; ${i} < \@{${var}}; ${i}++) {";
542 0         0 $perl .= "${var}->[${i}] = $val;";
543 0         0 $perl .= "}}";
544             }
545             else {
546 0         0 $perl .= "if (is_hash_ref(${var})) {";
547 0         0 $perl .= "${var}->{${str_key}} = $val;";
548 0         0 $perl .= "}";
549             }
550              
551 0         0 $perl;
552             }
553              
554             sub emit_delete_key {
555 0     0 0 0 my ($self, $var, $key, $cb) = @_;
556              
557 0         0 my $str_key = $self->emit_string($key);
558 0         0 my $perl = "";
559 0         0 my $vals;
560 0 0       0 if ($cb) {
561 0         0 $vals = $self->_generate_var;
562 0         0 $perl = $self->emit_declare_vars($vals, '[]');
563             }
564              
565 0 0 0     0 if ($key =~ /^[0-9]+$/) {
    0 0        
566 0         0 $perl .= "if (is_hash_ref(${var}) && exists(${var}->{${str_key}})) {";
567 0 0       0 $perl .= "push(\@{${vals}}, " if $cb;
568 0         0 $perl .= "delete(${var}->{${str_key}})";
569 0 0       0 $perl .= ")" if $cb;
570 0         0 $perl .= ";";
571 0         0 $perl .= "} elsif (is_array_ref(${var}) && \@{${var}} > ${key}) {";
572 0 0       0 $perl .= "push(\@{${vals}}, " if $cb;
573 0         0 $perl .= "splice(\@{${var}}, ${key}, 1)";
574 0 0       0 $perl .= ")" if $cb;
575             }
576             elsif ($key eq '$first' || $key eq '$last' || $key eq '*') {
577 0         0 $perl .= "if (is_array_ref(${var}) && \@{${var}}) {";
578 0 0       0 $perl .= "push(\@{${vals}}, " if $cb;
579 0 0       0 $perl .= "splice(\@{${var}}, 0, 1)" if $key eq '$first';
580 0 0       0 $perl .= "splice(\@{${var}}, \@{${var}} - 1, 1)" if $key eq '$last';
581 0 0       0 $perl .= "splice(\@{${var}}, 0, \@{${var}})" if $key eq '*';
582 0 0       0 $perl .= ")" if $cb;
583             }
584             else {
585 0         0 $perl .= "if (is_hash_ref(${var}) && exists(${var}->{${str_key}})) {";
586 0 0       0 $perl .= "push(\@{${vals}}, " if $cb;
587 0         0 $perl .= "delete(${var}->{${str_key}})";
588 0 0       0 $perl .= ")" if $cb;
589             }
590 0         0 $perl .= ";";
591 0         0 $perl .= "}";
592 0 0       0 if ($cb) {
593 0         0 $perl .= $cb->($vals);
594             }
595              
596 0         0 $perl;
597             }
598              
599             sub emit_retain_key {
600 8     8 0 21 my ($self, $var, $key) = @_;
601              
602 8         20 my $perl = "";
603              
604 8 100       49 if ($key =~ /^[0-9]+$/) {
    100          
    100          
    100          
605 2         9 $perl .= "if (is_hash_ref(${var})) {";
606             $perl .= $self->emit_foreach_key(
607             $var,
608             sub {
609 2     2   5 my $v = shift;
610 2         9 "delete(${var}->{${v}}) if ${v} ne ${key};";
611             }
612 2         16 );
613 2         10 $perl .= "} elsif (is_array_ref(${var})) {";
614 2         7 $perl .= "if (\@{${var}} > ${key}) {";
615 2 50       13 $perl .= "splice(\@{${var}}, 0, ${key});" if $key > 0;
616 2         12 $perl .= "splice(\@{${var}}, 1, \@{${var}} - 1);";
617 2         3 $perl .= "} else {";
618 2         7 $perl .= "splice(\@{${var}}, 0, \@{${var}});";
619 2         4 $perl .= "}";
620 2         3 $perl .= "}";
621             }
622             elsif ($key eq '$first') {
623 1         6 $perl .= "if (is_array_ref(${var}) && \@{${var}} > 1) {";
624 1         4 $perl .= "splice(\@{${var}}, 1, \@{${var}} - 1);";
625 1         2 $perl .= "}";
626             }
627             elsif ($key eq '$last') {
628 1         5 $perl .= "if (is_array_ref(${var}) && \@{${var}} > 1) {";
629 1         5 $perl .= "splice(\@{${var}}, 0, \@{${var}} - 1);";
630 1         2 $perl .= "}";
631             }
632             elsif ($key eq '*') {
633              
634             # retain everything
635             }
636             else {
637 2         5 $key = $self->emit_string($key);
638 2         8 $perl .= "if (is_hash_ref(${var})) {";
639             $perl .= $self->emit_foreach_key(
640             $var,
641             sub {
642 2     2   4 my $v = shift;
643 2         10 "if ($v ne ${key}) {" . "delete(${var}->{${v}});" . "}";
644             }
645 2         21 );
646 2         11 $perl .= "}";
647             }
648              
649 8         29 $perl;
650             }
651              
652             sub emit_clone {
653 0     0 0 0 my ($self, $var) = @_;
654 0         0 "$var = clone($var);";
655             }
656              
657             # Split a path on '.' or '/', but not on '\.' or '\/'.
658             sub split_path {
659 8     8 0 18 my ($self, $path) = @_;
660 8         27 Catmandu::Util::split_path($path);
661             }
662              
663             1;
664              
665             __END__
666              
667             =pod
668              
669             =head1 NAME
670              
671             Catmandu::Fix - a Catmandu class used for data transformations
672              
673             =head1 SYNOPSIS
674              
675             # From the command line
676              
677             $ catmandu convert JSON --fix 'add(foo,bar)' < data.json
678             $ catmandu convert YAML --fix 'upcase(job) remove(test)' < data.yml
679             $ catmandu convert CSV --fix 'sort(tags)' < data.csv
680             $ catmandu run /tmp/myfixes.txt
681             $ catmandu convert OAI --url http://biblio.ugent.be/oai --fix /tmp/myfixes.txt
682              
683             # With preprocessing
684             $ catmandu convert JSON --var field=foo --fix 'add({{field}},bar)' < data.json
685              
686             # From Perl
687              
688             use Catmandu;
689              
690             my $fixer = Catmandu->fixer('upcase(job)','removed(test)');
691             my $fixer = Catmandu->fixer('/tmp/myfixes.txt');
692              
693             # Convert data
694             my $arr = $fixer->fix([ ... ]);
695             my $hash = $fixer->fix({ ... });
696             my $importer = Catmandu->importer('YAML', file => 'data.yml');
697             my $fixed_importer = $fixer->fix($importer);
698              
699             # With preprocessing
700             my $fixer = Catmandu::Fix->new(
701             variables => {x => 'foo', y => 'bar'},
702             fixes => ['add({{x}},{{y}})'],
703             );
704              
705             # Inline fixes
706             use Catmandu::Fix::upcase as => 'my_upcase';
707             use Catmandu::Fix::remove as => 'my_remove';
708              
709             my $hash = { 'job' => 'librarian' , deep => { nested => '1'} };
710              
711             my_upcase($hash,'job');
712             my_remove($hash,'deep.nested');
713              
714             =head1 DESCRIPTION
715              
716             A Catmandu::Fix is a Perl package that can transform data. These packages are used
717             for easy data manipulation by non programmers. The main intention is to use fixes
718             on the command line or in Fix scripts. A small DSL language is available to execute
719             many Fix command on a stream of data.
720              
721             When a C<fix> argument is given to a L<Catmandu::Importer>, L<Catmandu::Exporter> or
722             L<Catmandu::Store> then the transformations are executed on every item in the stream.
723              
724             =head1 FIX LANGUAGE
725              
726             A Fix script is a collection of one or more Fix commands. The fixes are executed
727             on every record in the dataset. If this command is executed on the command line:
728              
729             $ catmandu convert JSON --fix 'upcase(title); add(deep.nested.field,1)' < data.json
730              
731             then all the title fields will be upcased and a new deeply nested field will be added:
732              
733             { "title":"foo" }
734             { "title":"bar" }
735              
736             becomes:
737              
738             { "title":"FOO" , "deep":{"nested":{"field":1}} }
739             { "title":"BAR" , "deep":{"nested":{"field":1}} }
740              
741             Using the command line, Fix commands need a semicolon (;) as separator. All these commands can
742             also be written into a Fix script where semicolons are not required:
743              
744             $ catmandu convert JSON --fix script.fix < data.json
745              
746             where C<script.fix> contains:
747              
748             upcase(title)
749             add(deep.nested.field,1)
750              
751             Conditionals can be used to provide the logic when to execute fixes:
752              
753             if exists(error)
754             set(valid, 0)
755             end
756              
757             if exists(error)
758             set(is_valid, 0)
759             elsif exists(warning)
760             set(is_valid, 1)
761             log(...)
762             else
763             set(is_valid, 1)
764             end
765              
766             unless all_match(title, "PERL")
767             add(is_perl, "noooo")
768             end
769              
770             exists(error) and set(is_valid, 0)
771             exists(error) && set(is_valid, 0)
772              
773             exists(title) or log('title missing')
774             exists(title) || log('title missing')
775              
776             Binds are used to manipulate the context in which Fixes are executed. E.g.
777             execute a fix on every item in a list:
778              
779             # 'demo' is an array of hashes
780             bind list(path:demo)
781             add_field(foo,bar)
782             end
783             # do is an alias for bind
784             do list(path:demo)
785             add_field(foo,bar)
786             end
787              
788             To delete records from a stream of data the C<reject> Fix can be used:
789              
790             reject() # Reject all in the stream
791              
792             if exists(foo)
793             reject() # Reject records that contain a 'foo' field
794             end
795              
796             reject exists(foo) # Reject records that contain a 'foo' field
797              
798             The opposite of C<reject> is C<select>:
799              
800             select() # Keep all records in the stream
801              
802             select exists(foo) # Keep only the records that contain a 'foo' field
803              
804             Comments in Fix scripts are all lines (or parts of a line) that start with a hash (#):
805              
806             # This is ignored
807             add(test,123) # This is also a comment
808              
809             You can load fixes from another namespace with the C<use> statement:
810              
811             # this will look for fixes in the Foo::Bar namespace and make them
812             # available prefixed by fb
813             use(foo.bar, as: fb)
814             fb.baz()
815              
816             # this will look for Foo::Bar::Condition::is_baz
817             if fb.is_baz()
818             ...
819             fix()
820             ...
821             end
822              
823             =head1 FIX COMMANDS, ARGUMENTS AND OPTIONS
824              
825             Fix commands manipulate data or in some cases execute side effects. Fix
826             commands have zero or more arguments and zero or more options. Fix command
827             arguments are separated by commas ",". Fix options are name/value pairs
828             separated by a colon ":".
829              
830             # A command with zero arguments
831             my_command()
832              
833             # A command with multiple arguments
834             my_other_command(foo,bar,test)
835              
836             # A command with optional arguments
837             my_special_command(foo,bar,color:blue,size:12)
838              
839             All command arguments are treated as strings. These strings can be FIX PATHs
840             pointing to values or string literals. When command line arguments don't contain
841             special characters comma "," , equal "=" , great than ">" or colon ":", then
842             they can be written as-is. Otherwise, the arguments need to be quoted with single
843             or double quotes:
844              
845             # Both commands below have the same effect
846             my_other_command(foo,bar,test)
847             my_other_command("foo","bar","test")
848              
849             # Illegal syntax
850             my_special_command(foo,http://test.org,color:blue,size:12) # <- syntax error
851              
852             # Correct syntax
853             my_special_command(foo,"http://test.org",color:blue,size:12)
854            
855             # Or, alternative
856             my_special_command("foo","http://test.org",color:"blue",size:12)
857              
858             =head1 FIX PATHS
859              
860             Most of the Fix commands use paths to point to values
861             in a data record. E.g. 'foo.2.bar' is a key 'bar' which is the 3-rd value of the
862             key 'foo'.
863              
864             A special case is when you want to point to all items in an array. In this case
865             the wildcard '*' can be used. E.g. 'foo.*' points to all the items in the 'foo'
866             array.
867              
868             For array values there are special wildcards available:
869              
870             * $append - Add a new item at the end of an array
871             * $prepend - Add a new item at the start of an array
872             * $first - Syntactic sugar for index '0' (the head of the array)
873             * $last - Syntactic sugar for index '-1' (the tail of the array)
874              
875             E.g.
876              
877             # Create { mods => { titleInfo => [ { 'title' => 'a title' }] } };
878             add('mods.titleInfo.$append.title', 'a title');
879              
880             # Create { mods => { titleInfo => [ { 'title' => 'a title' } , { 'title' => 'another title' }] } };
881             add('mods.titleInfo.$append.title', 'another title');
882              
883             # Create { mods => { titleInfo => [ { 'title' => 'foo' } , { 'title' => 'another title' }] } };
884             add('mods.titleInfo.$first.title', 'foo');
885              
886             # Create { mods => { titleInfo => [ { 'title' => 'foo' } , { 'title' => 'bar' }] } };
887             add('mods.titleInfo.$last.title', 'bar');
888              
889             Some Fix commands can implement an alternatice path syntax to point to values.
890             See for example L<Catmandu::MARC>, L<Catmandu:PICA>:
891              
892             # Copy the MARC 245a field to the my.title field
893             marc_map(245a,my.title)
894              
895             =head1 OPTIONS
896              
897             =head2 fixes
898              
899             An array of fixes. L<Catmandu::Fix> which will execute every fix in consecutive
900             order. A fix can be the name of a Catmandu::Fix::* routine, or the path to a
901             plain text file containing all the fixes to be executed. Required.
902              
903             =head2 preprocess
904              
905             If set to C<1>, fix files or inline fixes will first be preprocessed as a
906             moustache template. See C<variables> below for an example. Default is C<0>, no
907             preprocessing.
908              
909             =head2 variables
910              
911             An optional hashref of variables that are used to preprocess the fix files or
912             inline fixes as a moustache template. Setting the C<variables> option also sets
913             C<preprocess> to 1.
914              
915             my $fixer = Catmandu::Fix->new(
916             variables => {x => 'foo', y => 'bar'},
917             fixes => ['add({{x}},{{y}})'],
918             );
919             my $data = {};
920             $fixer->fix($data);
921             # $data is now {foo => 'bar'}
922              
923             =head1 METHODS
924              
925             =head2 fix(HASH)
926              
927             Execute all the fixes on a HASH. Returns the fixed HASH.
928              
929             =head2 fix(ARRAY)
930              
931             Execute all the fixes on every element in the ARRAY. Returns an ARRAY of fixes.
932              
933             =head2 fix(Catmandu::Iterator)
934              
935             Execute all the fixes on every item in an L<Catmandu::Iterator>. Returns a
936             (lazy) iterator on all the fixes.
937              
938             =head2 fix(sub {})
939              
940             Executes all the fixes on a generator function. Returns a new generator with fixed data.
941              
942             =head2 log
943              
944             Return the current logger. See L<Catmandu> for activating the logger in your main code.
945              
946             =head1 CODING
947              
948             One can extend the Fix language by creating own custom-made fixes. Three methods are
949             available to create an new fix function:
950              
951             * Simplest: create a class that implements a C<fix> method.
952             * For most use cases: create a class that consumes the C<Catmandu::Fix::Builder> role and use C<Catmandu::Path> to build your fixer.
953             * Hardest: create a class that emits Perl code that will be evaled by the Fix module.
954              
955             Both methods will be explained shortly.
956              
957             =head2 Quick and easy
958              
959             A Fix function is a Perl class in the C<Catmandu::Fix> namespace that implements a C<fix> method.
960             The C<fix> methods accepts a Perl hash as input and returns a (fixed) Perl hash as output. As
961             an example, the code belows implements the C<meow> Fix which inserts a 'meow' field with value 'purrrrr'.
962              
963             package Catmandu::Fix::meow;
964              
965             use Moo;
966              
967             sub fix {
968             my ($self,$data) = @_;
969             $data->{meow} = 'purrrrr';
970             $data;
971             }
972              
973             1;
974              
975             Given this Perl class, the following fix statement can be used in your application:
976              
977             # Will add 'meow' = 'purrrrr' to the data
978             meow()
979              
980             Use the quick and easy method when your fixes are not dependent on reading or writing data
981             from/to a JSON path. Your Perl classes need to implement their own logic to read or write data
982             into the given Perl hash.
983              
984             Fix arguments are passed as arguments to the C<new> function of the Perl class. As in
985              
986             # In the fix file...
987             meow('test123', count: 4)
988              
989             # ...will be translated into this pseudo code
990             my $fix = Catmandu::Fix::meow->new('test123', count: 4);
991              
992             Using L<Moo> these arguments can be catched with L<Catmandu::Fix::Has> package:
993              
994             package Catmandu::Fix::meow;
995              
996             use Catmandu::Sane;
997             use Moo;
998             use Catmandu::Fix::Has;
999              
1000             has msg => (fix_arg => 1); # required parameter 1
1001             has count => (fix_opt => 1, default => sub { 4 }); # optional parameter 'count' with default value 4
1002              
1003             sub fix {
1004             my ($self,$data) = @_;
1005             $data->{meow} = $self->msg x $self->count;
1006             $data;
1007             }
1008              
1009             1;
1010              
1011             Using this code the fix statement can be used like:
1012              
1013             # Will add 'meow' = 'purrpurrpurrpurr'
1014             meow('purr', count: 4)
1015              
1016             =head1 SEE ALSO
1017              
1018             L<Catmandu::Fixable>,
1019             L<Catmandu::Importer>,
1020             L<Catmandu::Exporter>,
1021             L<Catmandu::Store>,
1022             L<Catmandu::Bag>
1023              
1024             =cut