File Coverage

blib/lib/YAML/Tidy.pm
Criterion Covered Total %
statement 675 735 91.8
branch 256 300 85.3
condition 132 149 88.5
subroutine 37 39 94.8
pod 4 5 80.0
total 1104 1228 89.9


line stmt bran cond sub pod time code
1             # ABSTRACT: Tidy YAML files
2 12     12   1538995 use strict;
  12         24  
  12         540  
3 12     12   68 use warnings;
  12         25  
  12         742  
4 12     12   66 use warnings FATAL => qw/ substr /;
  12         19  
  12         635  
5              
6 12     12   156 use v5.20;
  12         46  
7 12     12   5014 use experimental qw/ signatures /;
  12         43469  
  12         165  
8             package YAML::Tidy;
9              
10             our $VERSION = 'v0.11.0'; # VERSION
11              
12 12     12   9047 use YAML::Tidy::Node;
  12         39  
  12         548  
13 12     12   8445 use YAML::Tidy::Config;
  12         36  
  12         553  
14 12     12   8189 use YAML::LibYAML::API::XS;
  12         9011  
  12         768  
15 12         880 use YAML::PP::Common qw/
16             YAML_PLAIN_SCALAR_STYLE YAML_SINGLE_QUOTED_SCALAR_STYLE
17             YAML_DOUBLE_QUOTED_SCALAR_STYLE YAML_LITERAL_SCALAR_STYLE
18             YAML_FOLDED_SCALAR_STYLE
19             YAML_FLOW_SEQUENCE_STYLE YAML_FLOW_MAPPING_STYLE
20 12     12   90 /;
  12         20  
21 12     12   8326 use YAML::PP::Parser;
  12         422698  
  12         683  
22 12     12   8587 use YAML::PP::Highlight;
  12         729542  
  12         927  
23 12     12   150 use Data::Dumper;
  12         27  
  12         1012  
24              
25 12 50   12   89 use constant DEBUG => $ENV{YAML_TIDY_DEBUG} ? 1 : 0;
  12         28  
  12         191691  
26              
27 34     34 1 242712 sub new($class, %args) {
  34         113  
  34         143  
  34         66  
28 34   66     212 my $cfg = delete $args{cfg} || YAML::Tidy::Config->new();
29             my $self = bless {
30             partial => delete $args{partial},
31 34         228 cfg => $cfg,
32             }, $class;
33 34         208 return $self;
34             }
35              
36 67832     67832 1 108169 sub cfg($self) { $self->{cfg} }
  67832         96758  
  67832         95992  
  67832         254503  
37 21502     21502 0 33457 sub partial($self) { $self->{partial} }
  21502         35950  
  21502         28627  
  21502         70086  
38              
39 2878     2878 1 7763608 sub tidy($self, $yaml) {
  2878         10598  
  2878         8652  
  2878         6235  
40 2878         10141 local $Data::Dumper::Sortkeys = 1;
41 2878         24695 my @lines = split /\n/, $yaml, -1;
42 2878         21765 my $tree = $self->_tree($yaml, \@lines);
43 2878         9659 $self->{tree} = $tree;
44 2878         17675 $self->{lines} = \@lines;
45             # warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\@lines], ['lines']);
46 2878 100       9931 if (@lines) {
47 2866         6603 my $from = 0;
48 2866 100       16161 $self->_trimspaces(\$from, $tree) if $self->cfg->trimtrailing;
49 2866         19696 $self->_process(undef, $tree);
50             }
51 2878         6896 $yaml = join "\n", @{ $self->{lines} };
  2878         16951  
52             # warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$yaml], ['yaml']);
53 2878         20466 return $yaml;
54             }
55              
56 21373     21373   37598 sub _process($self, $parent, $node) {
  21373         32210  
  21373         37643  
  21373         30504  
  21373         31151  
57 21373   100     83037 my $type = $node->{type} || '';
58             # warn __PACKAGE__.':'.__LINE__.": ======== _process($parent, $node) $type\n";
59 21373 100       54630 if ($node->{flow}) {
60 934         4503 $self->_process_flow($parent, $node);
61 934         3108 return;
62             }
63 20439         41712 my $level = $node->{level};
64 20439         46021 my $indent = $self->cfg->indent;
65 20439         49067 my $lines = $self->{lines};
66 20439 50       50741 return unless @$lines;
67              
68 20439 100 100     71296 if ($level == -1 and $type eq 'DOC') {
69 3171         15338 $self->_process_doc($parent, $node);
70             }
71 20439         70711 my $start = $node->start;
72              
73              
74 20439         38315 my $indenttoplevelscalar = 1;
75 20439         48349 my $trimtrailing = $self->cfg->trimtrailing;
76              
77 20439         57842 my $col = $node->indent;
78 20439 100       74796 my $lastcol = $parent ? $parent->indent : -99;
79 20439         52873 my $realindent = $col - $lastcol;
80 20439         77536 my $startline = $node->line;
81 20439         49947 my $line = $lines->[ $startline ];
82 20439 50       46075 unless (defined $line) {
83 0         0 die "Line $startline not found";
84             }
85 20439         74746 my $before = substr($line, 0, $col);
86              
87              
88 20439 100       55193 if ($node->is_collection) {
89 9821   100     31699 my $ignore_firstlevel = ($self->partial and $level == 0);
90 9821 100 100     41691 if ($level < 0 or $ignore_firstlevel) {
91 6038         9923 for my $c (@{ $node->{children} }) {
  6038         21927  
92 6344         20610 $self->_process($node, $c);
93             }
94 6038         25433 return;
95             }
96              
97 3783 100       10865 if ($level == 0) {
98 2165         4656 $indent = 0;
99             }
100 3783 100       16704 if ($type eq 'MAP') {
    50          
101 2379 100       8914 if ($before =~ tr/ //c) {
102 311 50       1406 if ($indent == 1) {
103 0         0 $indent = 2;
104             }
105             }
106             }
107             elsif ($type eq 'SEQ') {
108 1404 100       4424 if ($before =~ tr/ //c) {
109 235 50       794 if ($indent == 1) {
110 0         0 $indent = 2;
111             }
112             }
113             else {
114 1169 100 100     10362 if ($parent->{type} eq 'MAP' and not $node->{index} % 2) {
115             # zero indented sequence?
116 358         1183 $indent = $self->cfg->indent_seq_in_map;
117             }
118             }
119              
120             }
121 3783         8470 my $diff = $indent - $realindent;
122 3783 100       13094 if ($diff) {
123 544         2983 $self->_fix_indent($node, $diff, $col);
124 544         2307 $node->fix_node_indent($diff);
125             }
126 3783         6453 for my $c (@{ $node->{children} }) {
  3783         12393  
127 12163         33276 $self->_process($node, $c);
128             }
129 3783         18010 return;
130             }
131             else {
132 10618 100       35169 if (defined (my $anchor = $node->{anchor})) {
133 682         2955 $self->_rename_anchor($node);
134 682         1704 $line = $lines->[ $startline ];
135             }
136 10618   100     30123 my $ignore_firstlevel = ($self->partial and $level == 0);
137 10618 100       33815 if ($node->empty_leaf) {
138 266         1128 return;
139             }
140 10352 100       34170 if ($node->is_alias) {
141 261         1148 $self->_rename_alias($node);
142 261         1064 return;
143             }
144 10091 50       32143 if ($node->{name} eq 'alias_event') {
145 0         0 return;
146             }
147 10091 100 100     54914 if ($parent->{type} eq 'MAP' and ($node->{index} % 2 and not $node->multiline)) {
      100        
148 4206         15400 $self->_replace_quoting($node);
149 4206         19545 return;
150             }
151 5885         18426 my $new_indent = $parent->indent + $indent;
152 5885         18500 my $new_spaces = ' ' x $new_indent;
153              
154 5885         30237 my ($anchor, $tag, $comments, $scalar) = $self->_find_scalar_start($node);
155 5885         14154 my $explicit_indent = 0;
156 5885 100       26701 if ($scalar->[2] =~ m/[>|]/) {
157 847         3926 my $l = $lines->[ $scalar->[0] ];
158 847         6110 my ($ind) = substr($l, $scalar->[1]) =~ m/^[|>][+-]?([0-9]*)/;
159 847         2350 $explicit_indent = $ind;
160             }
161 5885         11838 my $skipfirst = 0;
162 5885         15465 my $before = substr($line, 0, $col);
163 5885 100       22937 if ($before =~ tr/ \t//c) {
164             # same line as key
165 5064         9799 my $remove = 0;
166 5064 50       35208 $before =~ s/([\t ]+)$/ / and $remove = -1 + length $1;
167 5064         17413 $node->open->{column} -= $remove;
168 5064 100       15357 unless ($node->multiline) {
169 3960         12918 $node->close->{column} -= $remove;
170             }
171 5064         19569 $line = $before . substr($line, $col);
172 5064         12412 $lines->[ $startline ] = $line;
173 5064         8516 $skipfirst = 1;
174             }
175 5885         14159 my $realstart = $scalar->[0];
176 5885 100       19264 unless ($ignore_firstlevel) {
177 5883         16807 for my $i ($startline .. $realstart) {
178 6025         16131 my $line = $lines->[ $i ];
179 6025 100 100     30442 if ($i == $startline and $col > 0) {
180 5331         13783 my $before = substr($line, 0, $col);
181 5331 100       13602 if ($before =~ tr/ //c) {
182 5064         13421 next;
183             }
184             }
185 961 50       3554 unless ($line =~ tr/ //c) {
186 0         0 next;
187             }
188 961         1831 my $remove = 0;
189 961 50       8608 $line =~ s/^( *)/$new_spaces/ and $remove = length($1) - length($new_spaces);
190 961 100       3701 if ($i == $startline) {
191 819         2983 $node->open->{column} -= $remove;
192 819 100       3031 unless ($node->multiline) {
193 387         1064 $node->close->{column} -= $remove;
194             }
195             }
196 961         3465 $lines->[ $i] = $line;
197             }
198             }
199             # leave alone explicitly indented block scalars
200 5885 100       15606 return if $explicit_indent;
201              
202 5739         10231 $startline = $realstart;
203 5739         15016 my $endline = $node->realendline;
204             # warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$startline], ['startline']);
205             # warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$endline], ['endline']);
206              
207 5739         29412 my $line = $lines->[ $startline ];
208 5739         10278 my $realcol = $scalar->[1];
209 5739         9298 $col = $realcol;
210              
211 5739         10980 my $nextline = $node->{nextline};
212              
213             my $block = ($node->{style} eq YAML_LITERAL_SCALAR_STYLE
214 5739   100     25638 or $node->{style} eq YAML_FOLDED_SCALAR_STYLE);
215 5739 100 100     31487 if ($block) {
    50 66        
216              
217 701         1587 $startline++;
218 701   100     4756 while ($startline < $endline and $lines->[ $startline ] !~ tr/ //c) {
219 68 100       236 if ($trimtrailing) {
220 63         182 $self->_trim($startline, $startline);
221             }
222 68         271 $startline++;
223             }
224 701 100       2550 if ($nextline > $endline + 1) {
225 63         180 $endline = $nextline - 1;
226             }
227 701         3350 my @slice = @$lines[$startline .. $endline ];
228 701         4624 my ($sp) = $lines->[ $startline ] =~ m/^( *)/;
229 701 100 100     6065 if (not $ignore_firstlevel and length($sp) != $new_indent) {
    100          
230 344         906 for my $line (@slice) {
231 1099 100       2652 unless (length $line) {
232 262         597 next;
233             }
234 837 100 100     2813 if ($line !~ tr/ //c and length($line) <= length($sp)) {
235 122 100       345 if ($trimtrailing) {
236 114         254 $line = '';
237             }
238 122         254 next;
239             }
240 715 100       2203 if ($line =~ m/^( *)\#/) {
241 31         89 my $cindent = length $1;
242 31         74 my $diff = $new_indent - length $sp;
243 31         57 $cindent += $diff;
244 31 50       85 if ($diff > 0) {
    0          
245 31         136 $line = (' ' x $diff) . $line;
246             }
247             elsif ($diff < 0) {
248 0 0       0 if ($cindent < 0) {
249 0         0 $cindent = 0;
250             }
251 0         0 $new_spaces = ' ' x $cindent;
252 0         0 $line =~ s/^ */$new_spaces/;
253             }
254             }
255             else {
256 684         7356 $line =~ s/^$sp/$new_spaces/;
257             }
258             }
259 344         3344 @$lines[$startline .. $endline ] = @slice;
260             }
261             elsif ($trimtrailing) {
262 332         912 for my $line (@slice) {
263 852 100 100     3291 if ($line !~ tr/ //c and length($line) <= length($sp)) {
264 204         509 $line = '';
265             }
266             }
267 332         3030 @$lines[$startline .. $endline ] = @slice;
268             }
269             }
270             elsif ($node->{style} == YAML_PLAIN_SCALAR_STYLE or
271             $node->{style} == YAML_SINGLE_QUOTED_SCALAR_STYLE or
272             $node->{style} == YAML_DOUBLE_QUOTED_SCALAR_STYLE) {
273 5038 50       14548 if ($node->empty_leaf) {
274 0         0 return;
275             }
276 5038         8783 my $remove = 0;
277 5038 100 100     29313 if (not $skipfirst or $node->multiline) {
278 1078         2195 my $startline = $startline;
279 1078 100       3427 $startline++ if $skipfirst;
280 1078         2768 $endline = $node->close->{line};
281 1078 50       3830 return if $startline >= @$lines;
282 1078         2514 my $line = $lines->[ $startline ];
283 1078         6579 my ($sp) = $line =~ m/^( *)/;
284 1078 100       3576 if ($ignore_firstlevel) {
285 1         2 $new_indent = length $sp;
286 1         4 $new_spaces = ' ' x $new_indent;
287             }
288 1078         4453 my @slice = @$lines[$startline .. $endline ];
289 1078 50 66     18503 if ($level == 0 and not $indenttoplevelscalar) {
290 0         0 $new_spaces = ' ' x ($new_indent - $indent);
291             }
292 1078         2752 for my $line (@slice) {
293 1933 100       5468 if ($line =~ tr/ //c) {
294 1650 50       11351 $line =~ s/^([\t ]*)/$new_spaces/
295             and $remove = length($1) - length($new_spaces);
296             }
297             }
298 1078         3344 $node->close->{column} -= $remove;
299 1078         5847 @$lines[$startline .. $endline ] = @slice;
300             }
301 5038 100       13475 if (not $node->multiline) {
302 4347         16621 $self->_replace_quoting($node);
303             }
304             }
305             }
306             }
307              
308             my $RE_INT_CORE = qr{^([+-]?(?:[0-9]+))$};
309             my $RE_FLOAT_CORE = qr{^([+-]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][+-]?[0-9]+)?)$};
310             my $RE_INT_OCTAL = qr{^0o([0-7]+)$};
311             my $RE_INT_HEX = qr{^0x([0-9a-fA-F]+)$};
312             my @null = (qw/ null NULL Null ~ /, '');
313             my @true = qw/ true TRUE True /;
314             my @false = qw/ false FALSE False /;
315             my @inf = qw/ .inf .Inf .INF +.inf +.Inf +.INF -.inf -.Inf -.INF /;
316             my @nan = qw/ .nan .NaN .NAN /;
317             my @re = ($RE_INT_CORE, $RE_INT_OCTAL, $RE_INT_HEX, $RE_FLOAT_CORE);
318             my $re = join '|', @re;
319             my @all = (@null, @true, @false, @inf, @nan);
320              
321 10813     10813   16637 sub _replace_quoting($self, $node) {
  10813         17362  
  10813         15621  
  10813         17566  
322 10813 100       36816 return if $node->{tag};
323 9986         24677 my $default_style = $self->cfg->default_scalar_style;
324             # single line flow scalars
325 9986 100 100     61374 if (defined $default_style and $node->{style} != $default_style) {
326 1539         7964 my ($changed, $new_string, $new_style) = $self->_change_style($node, $default_style);
327 1539 100       6782 if ($changed) {
328 1360         3443 my $lines = $self->{lines};
329 1360         78635 my $line = $lines->[ $node->open->{line} ];
330 1360         3327 my ($from, $to) = ($node->open->{column}, $node->close->{column});
331 1360 100 66     8021 if (defined $node->{anchor} or $node->{tag}) {
332 59         286 my ($anchor, $tag, $comments, $scalar) = $self->_find_scalar_start($node);
333 59         226 $from = $scalar->[1];
334             }
335 1360         5303 substr($line, $from, $to - $from, $new_string);
336 1360         3290 my $diff = length($new_string) - ($to - $from);
337 1360 100       3326 if ($diff) {
338 1333         4584 $self->{tree}->_move_columns($node->open->{line}, $node->close->{column} + 1, $diff);
339             }
340 1360         4606 $node->{style} = $new_style;
341 1360         3533 $node->close->{column} += $diff;
342 1360         3935 $lines->[ $node->open->{line} ] = $line;
343             }
344             }
345             }
346              
347 1539     1539   3635 sub _change_style($self, $node, $style) {
  1539         2903  
  1539         2931  
  1539         2808  
  1539         2236  
348 1539         4174 my $value = $node->{value};
349 1539 100 100     4277 if (grep { $_ eq $value } @all or $value =~ m/($re)/) {
  35397         94165  
350             # leave me alone
351 152 100 100     986 if ($node->{style} eq YAML_PLAIN_SCALAR_STYLE or $style eq YAML_PLAIN_SCALAR_STYLE) {
352 151         670 return (0);
353             }
354             }
355              
356 1388         6058 my $emit = $self->_emit_value($value, $style, $node->{flow});
357 1388         3638 chomp $emit;
358 1388 50       4493 return (0) if $emit =~ tr/\n//;
359 1388         4592 my $first = substr($emit, 0, 1);
360 1388 100       5592 my $new_style =
    100          
361             $first eq "'" ? YAML_SINGLE_QUOTED_SCALAR_STYLE
362             : $first eq '"' ? YAML_DOUBLE_QUOTED_SCALAR_STYLE
363             : YAML_PLAIN_SCALAR_STYLE;
364 1388 100       4117 if ($new_style eq $style) {
365 1360         8651 return (1, $emit, $new_style);
366             }
367              
368 28         110 return (0);
369             }
370              
371 1388     1388   3043 sub _emit_value($self, $value, $style, $inflow) {
  1388         2283  
  1388         2883  
  1388         2596  
  1388         2471  
  1388         2099  
372 1388         4779 my $options = { unicode => 0 };
373 1388         8273 my @events = (
374             { name => 'stream_start_event' },
375             { name => 'document_start_event', implicit => 1 },
376             );
377 1388 100       3916 if ($inflow) {
378 325         1224 push @events, { name => 'sequence_start_event', style => YAML_FLOW_SEQUENCE_STYLE };
379             }
380 1388         8389 push @events, (
381             { name => 'scalar_event', style => $style, value => $value }
382             );
383 1388 100       3942 if ($inflow) {
384 325         1029 push @events, { name => 'sequence_end_event' }
385             }
386 1388         6840 push @events, (
387             { name => 'document_end_event', implicit => 1 },
388             { name => 'stream_end_event' },
389             );
390 1388         41484 my $emit = YAML::LibYAML::API::XS::emit_string_events(\@events, $options);
391 1388 100       4804 if ($inflow) {
392 325         3157 $emit =~ s/^ *\[ *//;
393 325         2145 $emit =~ s/ *\] *$//;
394             }
395 1388         11082 return $emit;
396             }
397              
398 49     49   58 sub _collect_aliases($self, $node) {
  49         57  
  49         56  
  49         50  
399 49 100       118 if ($node->is_alias) {
400 15   50     36 my $alias = $node->{value} // '???';
401 15         42 $alias =~ s/_[0-9]+$//;
402 15         43 $self->{doc}->{aliases}->{ $alias }++;
403             }
404             else {
405 34 100       78 if (defined(my $anchor = $node->{anchor})) {
406 7         25 $anchor =~ s/_[0-9]+$//;
407 7         35 $self->{doc}->{anchors}->{ $anchor }++;
408             }
409 34 100       65 if ($node->is_collection) {
410 11         13 for my $c (@{ $node->{children} }) {
  11         22  
411 47         100 $self->_collect_aliases($c);
412             }
413             }
414             }
415             }
416              
417 2     2   3 sub _serialize_aliases($self, $node) {
  2         3  
  2         3  
  2         4  
418 2         7 $self->_collect_aliases($node);
419 2         4 my $doc = $self->{doc};
420 2         4 my $anchors = $doc->{anchors};
421 2         8 for my $name (sort keys %$anchors) {
422 2 50       8 delete $anchors->{ $name }, next if $anchors->{ $name } < 2;
423             $anchors->{ $name } = ['', map {
424 7         29 $name ."_$_"
425 2         7 } 1 .. $anchors->{ $name } ];
426              
427             }
428             }
429              
430 17     17   28 sub _rename($self, $type, $node, $anchor, $new_anchor) {
  17         23  
  17         33  
  17         26  
  17         21  
  17         28  
  17         24  
431 17         29 my $lines = $self->{lines};
432 17         44 my $startline = $node->line;
433 17         36 my $line = $lines->[ $startline ];
434 17         52 my $col = $node->indent;
435              
436 17 100       41 if ($anchor ne $new_anchor) {
437 15         39 $self->{doc}->{rename}->{ $anchor } = $new_anchor;
438             }
439 17         55 my $end_column = $node->end->{column};
440 17         105 substr $line, $col, 1+length($anchor), {anchor=>'&',alias=>'*'}->{$type} . $new_anchor;
441 17         60 $lines->[ $startline ] = $line;
442 17         33 my $diff = length($new_anchor) - length($anchor);
443 17         63 $self->{tree}->_move_columns($node->start->{line}, $node->start->{column} + 1, $diff);
444             }
445              
446 713     713   1276 sub _rename_anchor($self, $node) {
  713         1265  
  713         1273  
  713         1928  
447 713         1537 my $anchor = $node->{anchor};
448 713         2104 my $group = $anchor =~ s/_[0-9]+$//r;
449 713 100       3477 my $usage = $self->{doc}->{anchors}->{ $group } or return;
450 7         15 shift @$usage;
451 7 50       17 return unless @$usage;
452 7         13 my $new_anchor = $usage->[0];
453 7 50       19 if ($new_anchor ne $group) {
454 7         19 $self->_rename(anchor => $node, $anchor, $new_anchor);
455             }
456             }
457              
458 273     273   460 sub _rename_alias($self, $node) {
  273         536  
  273         408  
  273         472  
459 273         813 my $anchor = $node->{value};
460 273 100       1410 my $new_anchor = $self->{doc}->{rename}->{ $anchor } or return;
461 10         40 my $group = $anchor =~ s/_[0-9]+$//r;
462 10 50       23 if ($new_anchor ne $group) {
463 10         27 $self->_rename(alias => $node, $anchor, $new_anchor);
464             }
465             }
466              
467 3171     3171   13307 sub _process_doc($self, $parent, $node) {
  3171         6666  
  3171         5958  
  3171         5893  
  3171         5848  
468 3171         5042 DEBUG and say STDERR "_process_doc($node)";
469 3171         13949 $self->{doc} = {};
470 3171 100       9633 if ($self->cfg->serialize_aliases) {
471 2         7 $self->_serialize_aliases($node);
472             }
473 3171         8350 my $lines = $self->{lines};
474 3171         9744 my $open = $node->open;
475 3171         8743 my $close = $node->close;
476 3171 100 100     8703 if ($node->open->{implicit} and $self->cfg->addheader and not $self->partial) {
    100 66        
      100        
      100        
      66        
477             # add ---
478 346         1876 splice @$lines, $open->{start}->{line}, 0, '---';
479 346         2616 $self->{tree}->fix_lines($open->{start}->{line}, +1);
480 346         1033 $open->{start}->{line}--;
481 346         895 $open->{end}->{line}--;
482 346         960 $open->{end}->{column} = 3;
483 346         971 $open->{implicit} = 0;
484 346         732 DEBUG and say STDERR "$node";
485             }
486             elsif ($node->{index} == 1 and not $open->{implicit} and $self->cfg->removeheader and not $self->partial) {
487             # remove first ---
488 159         638 my $child = $node->{children}->[0];
489 159 100 100     1917 if ($open->{version_directive} or $open->{tag_directives} or not $child->is_collection and $child->empty_leaf) {
      100        
      100        
490             }
491             else {
492 138         506 my $startline = $open->{start}->{line};
493 138         384 my $line = $lines->[ $startline ];
494 138 100       1258 if ($line =~ m/^---[ \t]*$/) {
    50          
495 99         410 splice @$lines, $startline, 1;
496 99         729 $self->{tree}->fix_lines($open->{start}->{line}+1, -1);
497 99         230 DEBUG and say STDERR "$node";
498 99         489 $open->{implicit} = 1;
499             }
500             elsif ($line =~ s/^---[ \t]+(?=#)//) {
501 0         0 $lines->[ $startline ] = $line;
502 0         0 DEBUG and say STDERR "$node";
503 0         0 $open->{implicit} = 1;
504             }
505             }
506             }
507 3171 100 100     21284 if ($close->{implicit} and $self->cfg->addfooter and not $self->partial) {
    100 66        
      100        
      66        
508             # add ...
509 531         3087 splice @$lines, $close->{start}->{line}, 0, '...';
510 531         2908 $self->{tree}->fix_lines($close->{start}->{line}, +1);
511 531         1546 $close->{end}->{column} = 3;
512 531         1361 $close->{implicit} = 0;
513 531         1541 DEBUG and say STDERR "$node";
514             }
515             elsif (not $close->{implicit} and $self->cfg->removefooter and not $self->partial) {
516             # remove ...
517 27         123 my $next = $parent->{children}->[ $node->{index} ];
518 27 100 66     203 if ($next and ($next->open->{version_directive} or $next->open->{tag_directives})) {
      66        
519             }
520             else {
521 12         43 my $startline = $close->{start}->{line};
522 12         38 my $line = $lines->[ $startline ];
523 12 50       108 if ($line =~ m/^\.\.\.[ \t]*$/) {
    0          
524 12         45 splice @$lines, $startline, 1;
525 12         85 $self->{tree}->fix_lines($close->{start}->{line}+1, -1);
526 12         41 $close->{implicit} = 1;
527             }
528             elsif ($line =~ s/^\.\.\.[ \t]+(?=#)//) {
529 0         0 $lines->[ $startline ] = $line;
530 0         0 $close->{implicit} = 1;
531             }
532 12         39 DEBUG and say STDERR "$node";
533             }
534             }
535             }
536              
537 23160     23160   31952 sub _trimspaces($self, $from, $node) {
  23160         40536  
  23160         42517  
  23160         32549  
  23160         31779  
538 23160 100       63027 if ($node->is_collection) {
    100          
539 10576         30728 my $level = $node->{level};
540 10576         16017 for my $c (@{ $node->{children} }) {
  10576         31213  
541 20465         49450 $self->_trimspaces($from, $c);
542             }
543 10576 100       30323 if ($level == -1) {
544 5677         21023 $self->_trim($$from, $node->end->{line});
545             }
546             }
547             elsif (defined $node->{style}) {
548             # Only spaces in block scalars must be left alone
549 12340 100 100     80852 if ($node->{style} eq YAML_LITERAL_SCALAR_STYLE
550             or $node->{style} eq YAML_FOLDED_SCALAR_STYLE) {
551 793         3868 my ($anchor, $tag, $comments, $scalar) = $self->_find_scalar_start($node);
552 793         4373 $self->_trim($$from, $scalar->[0]);
553 793         2936 $$from = $node->end->{line};
554             }
555             }
556             }
557              
558 3832     3832   6579 sub _process_flow($self, $parent, $node, $block_indent = undef) {
  3832         6438  
  3832         6066  
  3832         6201  
  3832         6855  
  3832         6582  
559 3832 50       13169 return unless $parent;
560 3832         10347 my $level = $node->{level};
561 3832   50     12375 my $flow = $node->{flow} || 0;
562 3832   66     12773 $block_indent //= $parent->indent + $self->cfg->indent;
563 3832 100       10213 $block_indent = 0 if $level == 0;
564              
565 3832 100       11078 if (defined (my $anchor = $node->{anchor})) {
566 31         156 $self->_rename_anchor($node);
567             }
568 3832 100       14604 if ($node->is_alias) {
569 12         61 $self->_rename_alias($node);
570             }
571              
572 3832 100       10824 unless ($node->is_collection) {
573 2491         9699 $self->_process_flow_scalar($parent, $node, $block_indent);
574 2491         13262 return;
575             }
576 1341 100 100     7007 if ($parent->{type} eq 'MAP' and $node->{index} % 2) {
577 92         326 return;
578             }
579 1249         3026 my $lines = $self->{lines};
580 1249         3651 my $startline = $node->start->{line};
581 1249         3737 my $end = $node->end;
582 1249         2824 my $endline = $end->{line};
583              
584 1249         3965 my $before = substr($lines->[ $startline ], 0, $node->start->{column});
585 1249 100       4989 if ($before =~ tr/ \t//c) {
586 882         1837 $startline++;
587             }
588 1249         3826 my @lines = ($startline .. $node->open->{end}->{line});
589 1249         5065 my $before_end = substr($lines->[ $endline ], 0, $end->{column} - 1);
590 1249 100       3994 unless ($before_end =~ tr/ \t//c) {
591 279         625 push @lines, $endline;
592             }
593 1249         3645 for my $i (@lines) {
594 661         2013 my $new_spaces = ' ' x $block_indent;
595 661         5373 $lines->[ $i ] =~ s/^([ \t]*)/$new_spaces/;
596 661         2548 my $old = length $1;
597 661         3948 $node->_fix_flow_indent(line => $i, diff => $block_indent - $old);
598             }
599              
600 1249         2305 for my $c (@{ $node->{children} }) {
  1249         3753  
601 2898         8669 $self->_process_flow($node, $c, $block_indent + $self->cfg->indent);
602             }
603             }
604              
605 2491     2491   3773 sub _process_flow_scalar($self, $parent, $node, $block_indent) {
  2491         4369  
  2491         3962  
  2491         3578  
  2491         3785  
  2491         3445  
606 2491 100       7140 if ($node->empty_leaf) {
607 121         288 return;
608             }
609 2370         7508 my $startline = $node->line;
610 2370         5694 my $lines = $self->{lines};
611 2370         6284 my $line = $lines->[ $startline ];
612 2370         5424 my $col = $node->start->{column};
613 2370         8168 my $before = substr($line, 0, $col);
614 2370 100       7409 if ($before =~ tr/ \t//c) {
615 2028         3259 $startline++;
616             }
617 2370         9959 my $endline = $node->end->{line};
618 2370         8826 for my $i ($startline .. $endline) {
619 440         1241 my $line = $lines->[ $i ];
620 440         1461 my $new_spaces = ' ' x $block_indent;
621 440         3506 $line =~ s/^([ \t]*)/$new_spaces/;
622 440         4448 my $old = length $1;
623 440 100       1666 if ($block_indent != $old) {
624 398         1954 $self->{tree}->_fix_flow_indent(line => $i, diff => $block_indent - $old);
625             }
626 440         1993 $lines->[ $i ] = $line;
627             }
628 2370 100       6794 if (not $node->multiline) {
629 2272         8295 $self->_check_adjacency($node, $parent);
630 2272 100       7701 if ($node->{name} eq 'scalar_event') {
631 2260         9503 $self->_replace_quoting($node);
632             }
633             }
634             }
635              
636 2272     2272   3366 sub _check_adjacency($self, $node, $parent) {
  2272         7528  
  2272         4427  
  2272         4012  
  2272         5596  
637 2272 50       6145 return unless $node->{flow};
638 2272 100       7885 return unless $node->is_mapping_value($parent);
639             # allowed: "foo":bar, "foo":*alias
640             # not allowed: foo:bar, foo:*alias
641 727         2799 my $prev = $parent->sibling($node, -1);
642 727         2675 my $tidy_adjacency = $self->cfg->adjacency;
643 727 100 100     2342 if (not $prev->is_collection and not $prev->is_quoted) {
644 507         1011 $tidy_adjacency = 0; # adjacency would be invalid here
645             }
646 727         2003 my $start = $node->open;
647 727         1229 my $adjacent = 0;
648 727         3107 my $line = $self->{lines}->[ $start->{line} ];
649 727 100 66     5561 if ($start->{column} > 0 and substr($line, $start->{column} - 1, 1) eq ':') {
650 60         137 $adjacent = 1;
651             }
652 727 100       2392 return unless defined $tidy_adjacency; # keep as is
653 513 50       1465 if ($tidy_adjacency) {
654 0         0 die "Not implemented yet: enforce adjacency";
655             }
656 513 100       3052 return unless $adjacent;
657 9         38 substr($line, $start->{column}, 0, ' ');
658 9         57 $self->{tree}->_move_columns($start->{line}, $start->{column} + 1, +1);
659 9         60 $self->{lines}->[ $start->{line} ] = $line;
660             }
661              
662 6737     6737   14626 sub _find_scalar_start($self, $node) {
  6737         10712  
  6737         10671  
  6737         10948  
663             # warn __PACKAGE__.':'.__LINE__.": ========= _find_scalar_start $node\n";
664 6737         15530 my $lines = $self->{lines};
665 6737         19119 my $from = $node->line;
666 6737         21143 my $to = $node->realendline;
667 6737         17098 my $col = $node->indent;
668 6737         20081 my $end = $node->end;
669 6737         13771 my $endcol = $end->{column};
670 6737         32682 my @slice = @$lines[ $from .. $to ];
671 6737         32366 my $anchor;
672             my $tag;
673 6737         0 my @comments;
674 6737         0 my $start;
675 6737         0 my $scalar;
676 6737         25196 for my $i (0 .. $#slice) {
677 6893         19237 my $line = $slice[ $i ];
678 6893 100       16625 my $f = $i == 0 ? $col : 0;
679 6893 100 33     23339 my $t = $i == $#slice ? ($endcol || length($line)) : length($line);
680 6893         29162 my $part = substr($line, $f, $t - $f);
681 6893 50       27961 if ($part =~ m/^ *(\#.*)$/g) {
682 0         0 my $comment = $1;
683 0         0 my $pos1 = length($line) - length($comment);
684 0         0 push @comments, [$i + $from, $pos1, $comment];
685 0         0 next;
686             }
687 6893         11158 my $cur;
688 6893         38866 while ($part =~ m/\G\s*([&!])(\S*)/g) {
689 1291         4473 my $type = $1;
690 1291         3298 my $name = $2;
691 1291         2631 $cur = pos $part;
692 1291         2738 my $pos = $cur - 1;
693 1291         2640 my $pos1 = $pos - length $name;
694 1291         3362 my $prop = substr($part, $pos1, 1+ length $name);
695 1291 100       4935 if ($type eq '&') {
    50          
696 485         3238 $anchor = [$i + $from, $pos1 + $f, $prop];
697             }
698             elsif ($type eq '!') {
699 806         5625 $tag = [$i + $from, $pos1 + $f, $prop];
700             }
701             }
702 6893         25300 pos($part) = $cur;
703 6893 50       25974 if ($part =~ m/\G *(\#.*)$/g) {
704 0         0 my $comment = $1;
705 0         0 $cur = pos $part;
706 0         0 my $pos1 = length($line) - length($comment);
707 0         0 push @comments, [$i + $from, $pos1, $comment];
708 0         0 next;
709             }
710 6893         14904 pos($part) = $cur;
711 6893 100       33484 if ($part =~ m/\G *(\S)/g) {
712 6570         20409 $scalar = $1;
713 6570         13622 my $pos1 = (pos $part) - 1;
714 6570         34632 $scalar = [$i + $from, $pos1 + $f, $scalar];
715 6570         20966 last;
716             }
717             }
718 6737   100     20758 $scalar ||= [$to, length($slice[ -1 ]), ''];
719             # warn __PACKAGE__.':'.__LINE__.$".Data::Dumper->Dump([\$scalar], ['scalar']);
720 6737         37256 return ($anchor, $tag, \@comments, $scalar);
721             }
722              
723 6533     6533   10388 sub _trim($self, $from, $to) {
  6533         10897  
  6533         13497  
  6533         9931  
  6533         10567  
724 6533         13907 my $lines = $self->{lines};
725 6533         22856 for my $line (@$lines[ $from .. $to ]) {
726 30651         86899 $line =~ s/[\t ]+$//;
727             }
728             }
729              
730 544     544   1092 sub _fix_indent($self, $node, $fix, $offset) {
  544         1091  
  544         1070  
  544         1151  
  544         1097  
  544         992  
731 544   100     2141 $offset ||= 0;
732 544         1888 my $startline = $node->line;
733 544         1506 my $lines = $self->{lines};
734 544         1730 my $endline = $node->realendline;
735 544         2973 my @slice = @$lines[$startline .. $endline];
736 544         1658 for my $line (@slice) {
737 1380 100       8108 next unless length $line;
738 1360 100       6202 if ($fix < 0) {
739 875         1430 my $offset = $offset;
740 875         1572 my $fix = -$fix;
741 875 50       3315 if ($offset > length $line) {
742 0         0 $offset = -1 + length $line;
743             }
744 875 50       2174 if ($line =~ tr/ //c) {
745 875 100       2989 if ($line =~ m/^ *\#/) {
746 22         343 $line =~ s/ {1,$fix}//;
747 22         116 next;
748             }
749             }
750             else {
751 0         0 $line =~ s/ {1,$fix}//;
752 0         0 next;
753             }
754 853         1767 my $before = substr($line, 0, $offset);
755 853         9051 $before =~ s/ {$fix,$fix}$//;
756 853         2982 $line = $before . substr($line, $offset);
757             }
758             else {
759 485 50       1455 unless ($line =~ tr/ //c) {
760 0         0 next;
761             }
762 485         964 my $offset1 = $offset - 1;
763             # lines with comments should only be indented if the comment starts
764             # at the original offset or after
765 485 100 100     5297 if ($offset > 0 and $line =~ m/^ {0,$offset1}\#/) {
766 2         3 next;
767             }
768 483         2225 substr($line, $offset, 0, ' ' x $fix);
769             }
770             }
771 544         3395 @$lines[$startline .. $endline] = @slice;
772             }
773              
774 2878     2878   9323 sub _tree($self, $yaml, $lines) {
  2878         7080  
  2878         7222  
  2878         6986  
  2878         5865  
775 2878         17472 my $events = $self->_parse($yaml);
776 2878         40113 $self->{events} = $events;
777 2878         8324 my $first = shift @$events;
778 2878         8316 my $end = pop @$events;
779 2878         18308 $_->{level} = -1 for ($first, $end);
780 2878         10637 $first->{id} = -1;
781 2878         5770 _pp($first) if DEBUG;
782 2878         5492 my @stack;
783              
784 2878         6493 my $level = -1;
785 2878         50142 my $docs = YAML::Tidy::Node::Collection->new(
786             type => 'STR',
787             children => [],
788             indent => -1,
789             line => 0,
790             level => $level,
791             start => YAML::Tidy::Node::Collection->new(%$first),
792             end => YAML::Tidy::Node::Collection->new(%$end),
793             );
794 2878         9875 my $ref = $docs;
795 2878         6615 my $id = 0;
796 2878         6428 my $flow = 0;
797 2878         15050 for my $i (0 .. $#$events) {
798 30056         48893 my $event = $events->[ $i ];
799 30056         60172 my $name = $event->{name};
800 30056         44369 $id++;
801              
802 30056         41821 my $type;
803 30056 100       136972 if ($name =~ m/document_start/) {
    100          
    100          
    100          
804 3171         14227 $type = 'DOC';
805             }
806             elsif ($name =~ m/sequence_start/) {
807 2104         4174 $type = 'SEQ';
808             }
809             elsif ($name =~ m/mapping_start/) {
810 3081         6748 $type = 'MAP';
811             }
812             elsif ($name eq 'alias_event') {
813 273         628 $type = 'ALI';
814             }
815              
816 30056         63103 $event->{id} = $id;
817 30056 100       109193 if ($name =~ m/_start_event/) {
    100          
    100          
818 8356         20843 $event->{level} = $level;
819 8356 100       25054 if ($name eq 'sequence_start_event') {
820             # inconsistency in libyaml events?
821 2104         4563 my $col = $event->{end}->{column};
822 2104 100       6258 if ($col > 0) {
823 1402         4543 my $line = $lines->[ $event->{end}->{line} ];
824 1402         5844 my $chr = substr($line, $col - 1, 1);
825 1402 100       7669 if ($chr eq '-') {
826 117         440 $event->{end}->{column}--;
827             }
828             }
829             }
830 8356 100 100     101984 if ($flow or ($event->{style} // -1) == YAML_FLOW_SEQUENCE_STYLE
      100        
      100        
      66        
831             or ($event->{style} // -1) == YAML_FLOW_MAPPING_STYLE) {
832 1401         2547 $flow++;
833             }
834 8356         37251 my $node = YAML::Tidy::Node::Collection->new(
835             children => [],
836             type => $type,
837             level => $level,
838             start => $event,
839             flow => $flow,
840             );
841 8356         15603 push @{ $ref->{children} }, $node;
  8356         25409  
842 8356         20333 $ref->{elements}++;
843 8356         35772 $node->{index} = $ref->{elements};
844 8356         15739 push @stack, $ref;
845 8356         18605 $ref = $node;
846 8356         15122 $level++;
847             }
848             elsif ($name =~ m/_end_event/) {
849 8356         18909 my $last = pop @stack;
850              
851 8356         25107 $ref->{end} = $event;
852              
853 8356         13239 $ref = $last;
854              
855 8356         13796 $level--;
856 8356         18101 $event->{level} = $level;
857 8356 100       26067 $flow-- if $flow;
858             }
859             elsif ($name eq 'alias_event') {
860 273         2261 $event = YAML::Tidy::Node::Alias->new(%$event, flow => $flow);
861 273         727 $ref->{elements}++;
862 273         1000 $event->{index} = $ref->{elements};
863 273         626 $event->{level} = $level;
864 273         482 push @{ $ref->{children} }, $event;
  273         660  
865             }
866             else {
867 13071         76060 $event = YAML::Tidy::Node::Scalar->new(%$event, flow => $flow);
868 13071         35492 $ref->{elements}++;
869 13071         30740 $event->{index} = $ref->{elements};
870 13071         25396 $event->{level} = $level;
871 13071         19131 push @{ $ref->{children} }, $event;
  13071         31855  
872             }
873 30056         64599 $event->{nextline} = -1;
874 30056 100       80445 if ($i < $#$events) {
875 27214         49674 my $next = $events->[ $i + 1 ];
876 27214         62774 my $nextline = $next->{start}->{line};
877 27214         51802 $event->{nextline} = $nextline;
878             }
879 30056         58195 _pp($event) if DEBUG;
880             }
881 2878         9209 $end->{id} = $id + 1;
882 2878         6239 _pp($end) if DEBUG;
883 2878         137143 $self->{tree} = $docs;
884 2878         17505 return $docs;
885             }
886              
887 5743     5743   9506624 sub _parse($self, $yaml) {
  5743         15242  
  5743         12978  
  5743         16425  
888 5743         11518 my @events;
889 5743         44039 YAML::LibYAML::API::XS::parse_events($yaml, \@events);
890 5743         570928 return \@events;
891             }
892              
893 0     0   0 sub _pp($event) {
  0         0  
  0         0  
894 0         0 my $name = $event->{name};
895 0         0 my $level = $event->{level};
896 0         0 $name =~ s/_event$//;
897 0         0 my $fmt = '%2d %-10s) %-14s';
898 0         0 my $indent = $level*2+2;
899 0         0 my $lstr = (' ' x $indent) . $level;
900             my @args = (
901             $event->{id}, $lstr,
902             $event->{start}->{line}, $event->{start}->{column},
903             $event->{end}->{line}, $event->{end}->{column},
904 0         0 $name,
905             );
906 0 0       0 if ($name =~ m/scalar|alias/) {
    0          
907 0         0 local $Data::Dumper::Useqq = 1;
908 0         0 my $str = Data::Dumper->Dump([$event->{value}], ['value']);
909 0         0 chomp $str;
910 0         0 $str =~ s/^\$value = //;
911 0         0 $fmt .= " %s";
912 0         0 push @args, $str;
913             }
914             elsif ($name =~ m/end/) {
915             }
916             else {
917             }
918 0         0 $fmt .= "\n";
919 0         0 printf $fmt, @args;
920             }
921              
922 0     0   0 sub _debug_lines($self) {
  0         0  
  0         0  
923 0         0 say "====================================";
924 0         0 say for @{ $self->{lines} };
  0         0  
925 0         0 say "====================================";
926             }
927              
928 5730     5730 1 49598297 sub highlight($self, $yaml, $type = 'ansi') {
  5730         16260  
  5730         17451  
  5730         17880  
  5730         14697  
929 5730         71835 my ($error, $tokens) = YAML::PP::Parser->yaml_to_tokens(string => $yaml);
930 5730 100       26414104 if ($error) {
931 200         626 $tokens = [];
932 200         5597 my @lines = split m/(?<=\n)/, $yaml;
933 200         765 for my $line (@lines) {
934 600 50       1959 if ($line =~ s/( +\n)//) {
935 0         0 push @$tokens, { value => $line, name => 'PLAIN' };
936 0         0 push @$tokens, { value => $1, name => 'TRAILING_SPACE' };
937 0         0 next;
938             }
939 600         2821 push @$tokens, { value => $line, name => 'PLAIN' };
940             }
941             }
942 5730 50       36470 if ($type eq 'html') {
943 0         0 return YAML::PP::Highlight->htmlcolored($tokens);
944             }
945 5730         68000 return YAML::PP::Highlight->ansicolored($tokens);
946             }
947              
948             1;
949              
950             __END__