File Coverage

blib/lib/Mason/Tidy.pm
Criterion Covered Total %
statement 260 267 97.3
branch 53 56 94.6
condition 20 28 71.4
subroutine 55 56 98.2
pod n/a
total 388 407 95.3


line stmt bran cond sub pod time code
1             package Mason::Tidy;
2             BEGIN {
3 3     3   1185 $Mason::Tidy::VERSION = '2.57';
4             }
5 3     3   3704 use File::Slurp;
  3         35323  
  3         233  
6 3     3   3136 use Method::Signatures::Simple;
  3         197398  
  3         49  
7 3     3   16145 use Moo;
  3         56383  
  3         22  
8 3     3   28028 use Perl::Tidy qw();
  3         794353  
  3         126  
9 3     3   29 use strict;
  3         15  
  3         99  
10 3     3   15 use warnings;
  3         8  
  3         801  
11              
12             my $marker_count = 0;
13              
14             # Public
15             has 'indent_block' => ( is => 'ro', default => sub { 0 } );
16             has 'indent_perl_block' => ( is => 'ro', default => sub { 2 } );
17             has 'mason_version' => ( is => 'ro', required => 1, isa => \&validate_mason_version );
18             has 'perltidy_argv' => ( is => 'ro', default => sub { '' } );
19             has 'perltidy_block_argv' => ( is => 'ro', default => sub { '' } );
20             has 'perltidy_line_argv' => ( is => 'ro', default => sub { '' } );
21             has 'perltidy_tag_argv' => ( is => 'ro', default => sub { '' } );
22              
23             # Private
24             has '_is_code_block' => ( is => 'lazy' );
25             has '_is_mixed_block' => ( is => 'lazy' );
26             has '_marker_prefix' => ( is => 'ro', default => sub { '__masontidy__' } );
27             has '_open_block_regex' => ( is => 'lazy' );
28             has '_subst_tag_regex' => ( is => 'lazy' );
29              
30 3     3   1551 func validate_mason_version () {
  67     67   1212  
31 67 50       2027 die "must be 1 or 2" unless $_[0] =~ /^[12]$/;
32             }
33              
34 3     3   1239 method _build__is_mixed_block () {
  10     10   466  
  10         20  
35 10         41 return { map { ( $_, 1 ) } $self->mixed_block_names };
  70         280  
36             }
37              
38 3     3   1051 method _build__is_code_block () {
  21     21   885  
  21         63  
39 21         119 return { map { ( $_, 1 ) } $self->code_block_names };
  84         647  
40             }
41              
42 3     3   1069 method _build__open_block_regex () {
  65     65   1555  
  65         110  
43 65         297 my $re = '<%(' . join( '|', $self->block_names ) . ')(\s+[\w\._-]+)?>';
44 65         751 return qr/$re/;
45             }
46              
47 3     3   1186 method _build__subst_tag_regex () {
  65     65   1518  
  65         110  
48 65         181 my $re = '<%(?!' . join( '|', $self->block_names, 'perl' ) . ')(.*?)%>';
49 65         635 return qr/$re/;
50             }
51              
52 3     3   1184 method block_names () {
  130     130   194  
  130         177  
53             return
54 130         1469 qw(after args around attr augment before class cleanup def doc filter flags init method once override shared text);
55             }
56              
57 3     3   1071 method code_block_names () {
  21     21   33  
  21         45  
58 21         100 return qw(class init once shared);
59             }
60              
61 3     3   1095 method mixed_block_names () {
  10     10   21  
  10         14  
62 10         50 return qw(after augment around before def method override);
63             }
64              
65 3     3   1101 method tidy ($source) {
  68     68   112  
  68         147  
  68         111  
66 68         249 my $final = $self->tidy_method($source);
67 66         428 return $final;
68             }
69              
70 3     3   1106 method tidy_method ($source) {
  78     78   189  
  78         138  
  78         117  
71 78 100       391 return $source if $source !~ /\S/;
72 74         275 my $final_newline = ( $source =~ /\n$/ );
73              
74 74         1869 my $open_block_regex = $self->_open_block_regex;
75 74         375 my $marker_prefix = $self->_marker_prefix;
76              
77             # Hide blocks other than <%perl>
78             #
79 74         1005 while ( $source =~ s/($open_block_regex.*?<\/%\2>)/$self->replace_with_marker($1)/se ) { }
  23         77  
80              
81             # Tidy Perl in <% %>
82             #
83 74         2176 my $subst_tag_regex = $self->_subst_tag_regex;
84 74         476 $source =~ s/$subst_tag_regex/"<% " . $self->tidy_subst_expr($1) . " %>"/ge;
  12         50  
85              
86             # Tidy Perl in <& &> and <&| &>
87             #
88 74         253 $source =~ s/(<&\|?)(.*?)&>/"$1 " . $self->tidy_compcall_expr($2) . " &>"/ge;
  7         337  
89              
90             # Hide <% %> and <& &>
91             #
92 74         910 while ( $source =~ s/($open_block_regex.*?<\/%\2>)/$self->replace_with_marker($1)/se ) { }
  0         0  
93 74         532 while ( $source =~ s/(<(%|&\|?)(?![A-Za-z]+>).*?\2>)/$self->replace_with_marker($1)/se ) { }
  21         75  
94              
95 74         412 my @lines = split( /\n/, $source, -1 );
96 74 100 66     595 pop(@lines) if @lines && $lines[-1] eq '';
97 74         170 my @elements = ();
98 74     242   434 my $add_element = sub { push( @elements, [@_] ) };
  242         941  
99              
100 74         165 my $last_line = scalar(@lines) - 1;
101 74         329 my $mason1 = $self->mason_version == 1;
102 74         178 my $mason2 = $self->mason_version == 2;
103              
104 74         322 for ( my $cur_line = 0 ; $cur_line <= $last_line ; $cur_line++ ) {
105 191         287 my $line = $lines[$cur_line];
106              
107             # Begin Mason 2 filter invocation
108             #
109 191 100 100     969 if ( $mason2 && $line =~ /^%\s*(.*)\{\{\s*/ ) {
110 4         18 $add_element->( 'perl_line', "given (__filter($1)) {" );
111 4         11 next;
112             }
113              
114             # End Mason 2 filter invocation
115             #
116 187 100 100     843 if ( $mason2 && $line =~ /^%\s*\}\}\s*/ ) {
117 4         11 $add_element->( 'perl_line', "} # __end filter" );
118 4         12 next;
119             }
120              
121             # %-line
122             #
123 183 100       535 if ( $line =~ /^%/ ) {
124 71         209 $add_element->( 'perl_line', substr( $line, 1 ) );
125 71         241 next;
126             }
127              
128             # <%perl> block, with both <%perl> and </%perl> on their own lines
129             #
130 112 100       341 if ( $line =~ /^\s*<%perl>\s*$/ ) {
131 80         190 my ($end_line) =
132 13         51 grep { $lines[$_] =~ /^\s*<\/%perl>\s*$/ } ( $cur_line + 1 .. $last_line );
133 13 100       50 if ($end_line) {
134 12         32 $add_element->( 'begin_perl_block', '<%perl>' );
135 12         58 foreach my $line ( @lines[ $cur_line + 1 .. $end_line - 1 ] ) {
136 39         77 $add_element->( 'perl_line', $line );
137             }
138 12         32 $add_element->( 'end_perl_block', '</%perl>' );
139 12         21 $cur_line = $end_line;
140 12         43 next;
141             }
142             }
143              
144             # Single line of text untouched
145             #
146 100         331 $add_element->( 'text', $line );
147             }
148              
149             # Create content from elements with non-perl lines as comments; perltidy;
150             # reassemble list of elements from tidied perl blocks and replaced elements
151             #
152 242 100       902 my $untidied_perl = "{\n"
153             . join( "\n",
154 74         209 map { $_->[0] eq 'perl_line' ? trim( $_->[1] ) : $self->replace_with_perl_comment($_) }
155             @elements )
156             . "\n}\n";
157 74         197 $DB::single = 1;
158 74         454 $self->perltidy(
159             source => \$untidied_perl,
160             destination => \my $tidied_perl,
161             argv => $self->perltidy_line_argv . " -fnl -fbl",
162             );
163 73         538 $tidied_perl =~ s/^{\n//;
164 73         386 $tidied_perl =~ s/}\n$//;
165              
166 73         474 my @tidied_lines = split( /\n/, substr( $tidied_perl, 0, -1 ), -1 );
167 73 100       262 @tidied_lines = ('') if !@tidied_lines;
168 73         142 my @final_lines = ();
169 73         130 my $perl_block_mode = 0;
170 73         267 my $standard_indent = $self->standard_line_indent();
171 73         222 foreach my $line (@tidied_lines) {
172 241 100       691 if ( my $marker = $self->marker_in_line($line) ) {
173 124         176 my ( $type, $contents ) = @{ $self->restore($marker) };
  124         384  
174 124         250 push( @final_lines, $contents );
175 124 100       585 if ( $type eq 'begin_perl_block' ) {
    100          
176 12         39 $perl_block_mode = 1;
177             }
178             elsif ( $type eq 'end_perl_block' ) {
179 12         73 $perl_block_mode = 0;
180             }
181             }
182             else {
183             # Convert back filter invocation
184             #
185 117 100       308 if ($mason2) {
186 114         334 $line =~ s/given\s*\(\s*__filter\s*\(\s*(.*?)\s*\)\s*\)\s*\{/$1 \{\{/;
187 114         194 $line =~ s/\}\s*\#\s*__end filter/\}\}/;
188             }
189              
190 117         178 $line =~ s/^\}\}/$standard_indent\}\}/;
191 117 100       238 if ($perl_block_mode) {
192 39 100       189 my $spacer = ( $line =~ /\S/ ? scalar( ' ' x $self->indent_perl_block ) : '' );
193 39         223 $line =~ s/^$standard_indent/$spacer/;
194 39         113 push( @final_lines, $line );
195             }
196             else {
197 78 100       324 my $spacer = ( $line =~ /\S/ ? ' ' : '' );
198 78         508 $line =~ s/^$standard_indent/$spacer/;
199 78         309 push( @final_lines, "%$line" );
200             }
201             }
202             }
203 73 100       444 my $final = join( "\n", @final_lines ) . ( $final_newline ? "\n" : "" );
204              
205             # Restore <% %> and <& &> and blocks
206             #
207 73         612 while ( $final =~ s/(${marker_prefix}_\d+)/$self->restore($1)/e ) { }
  44         134  
208              
209             # Tidy content in blocks other than <%perl>
210             #
211 73         114 my @replacements;
212 73         232 undef pos($final);
213 73         1179 while ( $final =~ /^(.*)$open_block_regex[\t ]*\n?/mg ) {
214 25         118 my ( $preceding, $block_type, $block_args ) = ( $1, $2, $3 );
215 25 100 66     134 next if length($preceding) > 0 && substr( $preceding, 0, 1 ) eq '%';
216 24         62 my $start_pos = pos($final) + length($preceding);
217 24 100       621 if ( $final =~ /(\n?[\t ]*<\/%$block_type>)/g ) {
218 23         76 my $length = pos($final) - $start_pos - length($1);
219 23         66 my $untidied_block_contents = substr( $final, $start_pos, $length );
220 23         133 my $tidied_block_contents =
221             $self->handle_block( $block_type, $block_args, $untidied_block_contents );
222 23         353 push( @replacements,
223             [ $start_pos, $length, $untidied_block_contents, $tidied_block_contents ] );
224             }
225             else {
226 1   50     40 die sprintf( "no matching end tag for '<%%%s%s>' at char %d",
227             $block_type, $block_args || '', $start_pos );
228             }
229             }
230 72         165 my $offset = 0;
231 72         182 foreach my $replacement (@replacements) {
232 23         79 my ( $start_pos, $length, $untidied_block_contents, $tidied_block_contents ) =
233             @$replacement;
234 23         51 my $adjusted_start_pos = $start_pos + $offset;
235 23         92 my $actual = substr( $final, $adjusted_start_pos, $length );
236 23 50       75 unless ( $actual eq $untidied_block_contents ) {
237 0         0 die sprintf( "assert failure: start pos %s, length %s - '%s' ne '%s'",
238             $adjusted_start_pos, $length, $actual, $untidied_block_contents );
239             }
240 23         72 substr( $final, $adjusted_start_pos, $length ) = $tidied_block_contents;
241 23         73 $offset += length($tidied_block_contents) - length($untidied_block_contents);
242             }
243              
244 72         1078 return $final;
245             }
246              
247 3     3   6474 method tidy_subst_expr ($expr) {
  12     12   28  
  12         31  
  12         19  
248 12         97 $self->perltidy(
249             source => \$expr,
250             destination => \my $tidied_expr,
251             argv => $self->perltidy_tag_argv . " -fnl -fbl",
252             );
253 12         54 return trim($tidied_expr);
254             }
255              
256 3     3   1126 method tidy_compcall_expr ($expr) {
  7     7   16  
  7         23  
  7         13  
257 7         11 my $path;
258 7 100       143 if ( ($path) = ( $expr =~ /^(\s*[\w\/\.][^,]+)/ ) ) {
259 4         21 substr( $expr, 0, length($path) ) = "'$path'";
260             }
261             $self->perltidy(
262 7         58 source => \$expr,
263             destination => \my $tidied_expr,
264             argv => $self->perltidy_tag_argv . " -fnl -fbl",
265             );
266 7 100       33 if ($path) {
267 4         18 substr( $tidied_expr, 0, length($path) + 2 ) = $path;
268             }
269 7         24 return trim($tidied_expr);
270             }
271              
272 3     3   2363 method handle_block ($block_type, $block_args, $block_contents) {
  23     23   47  
  23         52  
  23         46  
273 23 100 66     919 if ( $self->_is_code_block->{$block_type}
    100 66        
      33        
      66        
274             || ( $block_type eq 'filter' && !defined($block_args) ) )
275             {
276 11         37 $block_contents = trim_lines($block_contents);
277 11         68 $self->perltidy(
278             source => \$block_contents,
279             destination => \my $tidied_block_contents,
280             argv => $self->perltidy_block_argv
281             );
282 11         48 $block_contents = trim($tidied_block_contents);
283 11         83 my $spacer = scalar( ' ' x $self->indent_block );
284 11         74 $block_contents =~ s/^/$spacer/mg;
285             }
286             elsif ( $self->_is_mixed_block->{$block_type}
287             || ( $block_type eq 'filter' && defined($block_args) ) )
288             {
289 10         186 $block_contents = $self->tidy_method($block_contents);
290             }
291 23         166 return $block_contents;
292             }
293              
294 3     3   1509 method replace_with_perl_comment ($obj) {
  124     124   184  
  124         159  
  124         158  
295 124         278 return "# _LINE_" . $self->replace_with_marker($obj);
296             }
297              
298 3     3   1132 method replace_with_marker ($obj) {
  168     168   256  
  168         242  
  168         181  
299 168         564 my $marker = join( "_", $self->_marker_prefix, $marker_count++ );
300 168         560 $self->{markers}->{$marker} = $obj;
301 168         1051 return $marker;
302             }
303              
304 3     3   1197 method marker_in_line ($line) {
  241     241   358  
  241         358  
  241         276  
305 241         664 my $marker_prefix = $self->_marker_prefix;
306 241 100       1949 if ( my ($marker) = ( $line =~ /\s*_LINE_(${marker_prefix}_\d+)/ ) ) {
307 124         497 return $marker;
308             }
309 117         393 return undef;
310             }
311              
312 3     3   1186 method restore ($marker) {
  168     168   240  
  168         327  
  168         197  
313 168         431 my $retval = $self->{markers}->{$marker};
314 168         795 return $retval;
315             }
316              
317 3     3   1102 method perltidy (%params) {
  177     177   327  
  177         961  
  177         292  
318 177   100     726 $params{argv} ||= '';
319 177         810 $params{argv} .= ' ' . $self->perltidy_argv;
320 177         231 my $errorfile;
321 177         1381 Perl::Tidy::perltidy(
322             prefilter => \&perltidy_prefilter,
323             postfilter => \&perltidy_postfilter,
324             errorfile => \$errorfile,
325             %params
326             );
327 177 100       49173 die $errorfile if $errorfile;
328             }
329              
330 3     3   1382 method standard_line_indent () {
  73     73   112  
  73         142  
331 73         173 my $source = "{\nfoo();\n}\n";
332 73         643 $self->perltidy(
333             source => \$source,
334             destination => \my $destination,
335             argv => $self->perltidy_line_argv . " -fnl -fbl"
336             );
337 73 50       868 my ($indent) = ( $destination =~ /^(\s*)foo/m )
338             or die "cannot determine standard indent";
339 73         330 return $indent;
340             }
341              
342 3     3   1345 func perltidy_prefilter ($buf) {
  177     177   1731797  
  177         319  
343 177         511 $buf =~ s/\$\./\$__SELF__->/g;
344 177         540 return $buf;
345             }
346              
347 3     3   1206 func perltidy_postfilter ($buf) {
  177     177   1285897  
  177         505  
348 177         626 $buf =~ s/\$__SELF__->/\$\./g;
349 177         1176 $buf =~ s/ *\{ *\{/ \{\{/g;
350 177         911 $buf =~ s/ *\} *\}/\}\}/g;
351 177         843 return $buf;
352             }
353              
354 3     3   1288 func trim ($str) {
  148     148   249  
  148         195  
355 148         303 for ($str) { s/^\s+//; s/\s+$// }
  148         369  
  148         592  
356 148         563 return $str;
357             }
358              
359 3     3   1248 func rtrim ($str) {
  0     0   0  
  0         0  
360 0         0 for ($str) { s/\s+$// }
  0         0  
361 0         0 return $str;
362             }
363              
364 3     3   1147 func trim_lines ($str) {
  11     11   28  
  11         17  
365 11         28 for ($str) { s/^\s+//m; s/\s+$//m }
  11         47  
  11         64  
366 11         35 return $str;
367             }
368              
369             1;
370              
371              
372              
373             =pod
374              
375             =head1 NAME
376              
377             Mason::Tidy - Engine for masontidy
378              
379             =head1 VERSION
380              
381             version 2.57
382              
383             =head1 SYNOPSIS
384              
385             use Mason::Tidy;
386              
387             my $mc = Mason::Tidy->new(mason_version => 2);
388             my $dest = $mc->tidy($source);
389              
390             =head1 DESCRIPTION
391              
392             This is the engine used by L<masontidy|masontidy> - read that first to get an
393             overview.
394              
395             You can call this API from your own program instead of executing C<masontidy>.
396              
397             =head1 CONSTRUCTOR PARAMETERS
398              
399             =over
400              
401             =item indent_block
402              
403             =item indent_perl_block
404              
405             =item mason_version (required)
406              
407             =item perltidy_argv
408              
409             =item perltidy_block_argv
410              
411             =item perltidy_line_argv
412              
413             =item perltidy_tag_argv
414              
415             These options are the same as the equivalent C<masontidy> command-line options,
416             replacing dashes with underscore (e.g. the C<--indent-per-block> option becomes
417             C<indent_perl_block> here).
418              
419             =back
420              
421             =head1 METHODS
422              
423             =over
424              
425             =item tidy ($source)
426              
427             Tidy component source I<$source> and return the tidied result. Throw fatal
428             error if source cannot be tidied (e.g. invalid syntax).
429              
430             =back
431              
432             =head1 AUTHOR
433              
434             Jonathan Swartz <swartz@pobox.com>
435              
436             =head1 COPYRIGHT AND LICENSE
437              
438             This software is copyright (c) 2011 by Jonathan Swartz.
439              
440             This is free software; you can redistribute it and/or modify it under
441             the same terms as the Perl 5 programming language system itself.
442              
443             =cut
444              
445              
446             __END__
447