File Coverage

blib/lib/Perl/Tidy/Sweetened/Keyword/Block.pm
Criterion Covered Total %
statement 71 71 100.0
branch 6 10 60.0
condition 2 2 100.0
subroutine 15 15 100.0
pod 4 11 36.3
total 98 109 89.9


line stmt bran cond sub pod time code
1             package Perl::Tidy::Sweetened::Keyword::Block;
2              
3             # ABSTRACT: Perl::Tidy::Sweetened filter plugin to define new subroutine and class keywords
4              
5 15     15   370 use 5.010; # Needed for balanced parens matching with qr/(?-1)/
  15         56  
6 15     15   83 use strict;
  15         31  
  15         301  
7 15     15   65 use warnings;
  15         46  
  15         361  
8 15     15   95 use Carp;
  15         43  
  15         16026  
9             $|++;
10              
11             our $VERSION = '1.20';
12              
13             # Regex to match balanced params. Reproduced from Regexp::Common to avoid
14             # adding a non-core dependency.
15             # $RE{balanced}{-parens=>'()'};
16             # The (?-1) construct requires 5.010
17             our $Paren = '(?:((?:\((?:(?>[^\(\)]+)|(?-1))*\))))';
18              
19             sub new {
20 180     180 0 697 my ( $class, %args ) = @_;
21 180 50       440 croak 'keyword not specified' if not exists $args{keyword};
22 180 50       341 croak 'marker not specified' if not exists $args{marker};
23 180 50       342 croak 'replacement not specified' if not exists $args{replacement};
24 180 50       318 $args{clauses} = [] unless exists $args{clauses};
25 180         1110 return bless {%args}, $class;
26             }
27              
28 1459     1459 1 4140 sub keyword { return $_[0]->{keyword} }
29 1446     1446 1 3151 sub marker { return $_[0]->{marker} }
30 1446     1446 1 3209 sub replacement { return $_[0]->{replacement} }
31              
32             sub emit_placeholder {
33 138     138 0 824 my ( $self, $subname, $brace, $clauses ) = @_;
34              
35             # Store the signature and returns() for later use
36 138         736 my $id = sprintf "%03d", $self->{counter}++;
37 138         432 $self->{store_clause}->{$id} = $clauses;
38 138         340 $self->{store_sub}->{$id} = $subname;
39              
40             # Turns 'my_method_name' into 'SUB004hod_name'
41 138         337 my $marker = $self->marker . $id;
42 138         399 substr( $subname, 0, length($marker), $marker );
43              
44 138         327 return sprintf '%s %s %s', $self->replacement, $marker, $brace;
45             }
46              
47             sub emit_keyword {
48 138     138 0 640 my ( $self, $brace, $id ) = @_;
49              
50             # Get the signature and returns() from store
51 138         407 my $clauses = $self->{store_clause}->{$id};
52              
53 138         354 my $subname = $self->{store_sub}->{$id};
54              
55             # Combine clauses (parameter list, returns(), etc) into a string separate
56             # each with a space and lead with a space if there are any
57 138         380 my $clause = join ' ', grep { length $_ } @$clauses;
  651         1285  
58              
59             # FIXME: This forces space between sub/func/method name and clauses list
60             # (ignores perltidy settings)
61 138 100       674 $clause = ' ' . $clause if length $clause;
62              
63 138         387 return sprintf '%s %s%s%s', $self->keyword, $subname, $clause, $brace;
64             }
65              
66             sub emit_csc {
67 13     13 0 29 my ( $self, $id, $cscp ) = @_;
68 13         28 return sprintf "%s %s %s", $cscp, $self->keyword, $self->{store_sub}->{$_};
69             }
70              
71             sub clauses {
72 1308     1308 1 1916 my $self = shift;
73              
74             # Create a regex (as a string) for all the clauses (ie, parameter list,
75             # returns(), etc).
76 1308         2026 my $clause_re = '';
77 1308         1971 my $i = 0;
78 1308         1814 for my $clause ( @{ $self->{clauses} } ) {
  1308         2925  
79 2943         5443 $clause =~ s{PAREN}{$Paren}g;
80              
81 2943         5955 $clause_re .= "(?<clause_$i> $clause ) \\s* \n";
82 2943         5119 $i++;
83             }
84              
85 1308         76183 return $clause_re;
86             }
87              
88             sub identifier { # method or package identifier
89 2616     2616 0 4118 my $self = shift;
90              
91 2616         4525 return '[$@%]? \w+ (?: ::\w+ )*'; # words, possibly with twigil and/or separated by ::
92             }
93              
94             sub prefilter {
95 1308     1308 0 2792 my ( $self, $code ) = @_;
96 1308         2425 my $keyword = $self->keyword;
97 1308         2456 my $subname = $self->identifier;
98              
99 1308         2189 $code =~ s{
100             ^\s*\K # okay to have leading whitespace (preserve)
101             $keyword \s+ # the "func/method" keyword
102             (?<subname> $subname) # the function name or class name (needs ::)
103             (?!\w|\s*=>) \s* # check to make sure this isn't a sub call with params
104 1308         2384 @{[ $self->clauses ]} # any clauses defined (ie, a parameter list)
105             (?<brace> .*?) # anything else (ie, comments) including brace
106             $
107             }{
108 138         432 my $i = 0;
109 138         283 my $clauses = [];
110 138         1336 while( exists $+{"clause_$i"} ){
111             ## warn "# clause_$i: " . $+{"clause_$i"} . "\n";
112 651         2790 push @$clauses, $+{"clause_$i"};
113 651         2847 $i++;
114             }
115 138         726 $self->emit_placeholder( $+{subname}, $+{brace}, $clauses )
116             }egmx;
117              
118 1308         7210 return $code;
119             }
120              
121             sub postfilter {
122 1308     1308 0 2820 my ( $self, $code, $args ) = @_;
123 1308         2509 my $marker = $self->marker;
124 1308         2830 my $replacement = $self->replacement;
125 1308         2413 my $subname = $self->identifier;
126 1308         1955 my @ids;
127              
128             # Convert back to method
129 1308         64299 $code =~ s{
130             ^\s*\K # preserve leading whitespace
131             $replacement \s+ # keyword was converted to sub/package
132             $marker #
133             (?<id> \d\d\d) # the identifier
134             [\w:]* \b # the rest of the orignal sub/package name
135             (?<newline> \n? \s* ) # possible newline and indentation
136             (?<brace> .*? ) [ ]* # opening brace on followed orig comments
137             [ ]* # trailing spaces (not all whitespace)
138             }{
139 138         1216 push @ids, $+{id};
140 138         1581 $self->emit_keyword( $+{newline} . $+{brace}, $+{id} );
141             }egmx;
142              
143             # Restore the orig sub name when inserted via the -csc flag
144 1308   100     7375 my $cscp = $args->{'-cscp'} || '## end';
145             $code =~ s{
146             \Q${cscp}\E \s sub \s ${marker} $_
147             }{
148 13         46 $self->emit_csc( $_, $cscp );
149 1308         5075 }egx for @ids;
150              
151 1308         4989 return $code;
152             }
153              
154             1;
155              
156             __END__
157              
158             =pod
159              
160             =head1 NAME
161              
162             Perl::Tidy::Sweetened::Keyword::Block - Perl::Tidy::Sweetened filter plugin to define new subroutine and class keywords
163              
164             =head1 VERSION
165              
166             version 1.20
167              
168             =head1 SYNOPSIS
169              
170             our $plugins = Perl::Tidy::Sweetened::Pluggable->new();
171              
172             $plugins->add_filter(
173             Perl::Tidy::Sweetened::Keyword::Block->new(
174             keyword => 'method',
175             marker => 'METHOD',
176             replacement => 'sub',
177             clauses => [ 'PAREN?', '(returns \s* PAREN)?' ],
178             ) );
179              
180             =head1 DESCRIPTION
181              
182             This is a Perl::Tidy::Sweetened filter which enables the definition of
183             arbitrary keywords for subroutines with any number of potential signature
184             definitions. New accepts:
185              
186             =over 4
187              
188             =item keyword
189              
190             keyword => 'method'
191              
192             Declares a new keyword (in this example the "method" keyword).
193              
194             =item marker
195              
196             marker => 'METHOD'
197              
198             Provides a text marker to be used to flag the new keywords during
199             C<prefilter>. The source code will be filtered prior to formatting by
200             Perl::Tidy such that:
201              
202             method foo {
203             }
204              
205             is turned into:
206              
207             sub foo { # __METHOD 1
208             }
209              
210             =item replacement
211              
212             replacement => 'sub'
213              
214             Will convert the keyword to a C<sub> as shown above.
215              
216             =item clauses
217              
218             clauses => [ 'PAREN?' ]
219              
220             Provides a list of strings which will be turned into a regex to capture
221             additional clauses. The regex will include the 'xm' flags (so be sure to escape
222             spaces). The clause can be marked optional with '?'. The special text "PAREN"
223             can be used to capture a balanced parenthetical.
224              
225             This example will capture a parameter list enclosed by parenthesis, ie:
226              
227             method foo (Int $i) {
228             }
229              
230             No formatting is done on the clauses at this time. The order of declaration
231             is significant.
232              
233             =back
234              
235             =head1 AUTHOR
236              
237             Mark Grimes <mgrimes@cpan.org>
238              
239             =head1 SOURCE
240              
241             Source repository is at L<https://github.com/mvgrimes/Perl-Tidy-Sweetened>.
242              
243             =head1 BUGS
244              
245             Please report any bugs or feature requests on the bugtracker website L<https://github.com/mvgrimes/Perl-Tidy-Sweetened/issues>
246              
247             When submitting a bug or request, please include a test-file or a
248             patch to an existing test-file that illustrates the bug or desired
249             feature.
250              
251             =head1 COPYRIGHT AND LICENSE
252              
253             This software is copyright (c) 2023 by Mark Grimes <mgrimes@cpan.org>.
254              
255             This is free software; you can redistribute it and/or modify it under
256             the same terms as the Perl 5 programming language system itself.
257              
258             =cut