File Coverage

blib/lib/MojoX/CustomTemplateFileParser.pm
Criterion Covered Total %
statement 156 160 97.5
branch 41 46 89.1
condition 12 17 70.5
subroutine 13 14 92.8
pod 0 2 0.0
total 222 239 92.8


line stmt bran cond sub pod time code
1             package MojoX::CustomTemplateFileParser;
2              
3 4     4   272607 use strict;
  4         13  
  4         266  
4 4     4   25 use warnings;
  4         7  
  4         194  
5 4     4   62 use 5.10.1;
  4         21  
  4         242  
6             our $VERSION = '0.10';
7              
8 4     4   5917 use Moose;
  4         2325795  
  4         46  
9             with 'MooseX::Object::Pluggable';
10              
11 4     4   36381 use HTML::Entities;
  4         25642  
  4         491  
12 4     4   5230 use Path::Tiny();
  4         61931  
  4         153  
13 4     4   4187 use Storable qw/dclone/;
  4         16081  
  4         7488  
14              
15             has path => (
16             is => 'ro',
17             isa => 'Str',
18             required => 1,
19             );
20             has structure => (
21             is => 'rw',
22             isa => 'HashRef',
23             default => sub { { } },
24             );
25             has test_index => (
26             is => 'rw',
27             isa => 'HashRef',
28             default => sub { { } },
29             );
30             has output => (
31             is => 'ro',
32             isa => 'ArrayRef',
33             default => sub { [ ] },
34             );
35              
36             sub BUILD {
37 4     4 0 8920 my $self = shift;
38 4         21 $self->_parse;
39              
40 4         8 foreach my $plugin (@{ $self->output } ) {
  4         209  
41 3         89 $self->load_plugin("To::$plugin");
42             }
43             }
44              
45             sub _parse {
46 4     4   12 my $self = shift;
47 4         17 my $baseurl = $self->_get_baseurl;
48 4         186 my @lines = split /\n/ => Path::Tiny::path($self->path)->slurp;
49              
50             # matches ==test== ==no test== ==test loop(a thing or two)== ==test example ==test 1== ==test example 2==
51 4         1127 my $test_start = qr/==(?:(NO) )?TEST(?: loop\(([^)]+)\))?( EXAMPLE)?(?: (?:\d+))?==/i;
52 4         12 my $template_separator = '--t--';
53 4         10 my $expected_separator = '--e--';
54              
55 4         8 my $environment = 'head';
56              
57 4         26 my $info = {
58             head_lines => [],
59             tests => [],
60             indexed => {},
61             };
62 4         12 my $test = {};
63              
64 4         7 my $row = 0;
65 4         67 my $testcount = 0;
66              
67             LINE:
68 4         15 foreach my $line (@lines) {
69 156         169 ++$row;
70              
71 156 100       303 if($environment eq 'head') {
72 16 100       88 if($line =~ $test_start) {
73              
74 4         11 my $skipit = $1;
75 4 50       27 $test->{'loop'} = defined $2 ? [ split / / => $2 ] : [];
76 4         20 $test = $self->_reset_test();
77              
78 4 50 33     26 if(defined $skipit && $skipit eq lc 'no') {
79 0         0 $test->{'skip'} = $skipit;
80             }
81              
82 4         9 push @{ $info->{'head_lines'} } => '';
  4         10  
83 4         10 $test->{'test_number'} = ++$testcount;
84 4 50       18 $test->{'is_example'} = defined $3 ? 1 : 0;;
85 4         11 $test->{'test_start_line'} = $row;
86 4         6 $test->{'test_number'} = $testcount;
87 4         33 $test->{'test_name'} = sprintf '%s_%s' => $baseurl, $testcount;
88 4         7 $environment = 'beginning';
89              
90 4         11 next LINE;
91             }
92 12         18 push @{ $info->{'head_lines'} } => $line;
  12         31  
93 12         26 next LINE;
94             }
95 140 100       331 if($environment eq 'beginning') {
96 24 100       53 if($line eq $template_separator) {
97 16         23 $environment = 'template';
98 16         35 next LINE;
99             }
100 8         11 push @{ $test->{'lines_before'} } => $line;
  8         23  
101 8         16 next LINE;
102             }
103 116 100       225 if($environment eq 'template') {
104 32 100       68 if($line eq $template_separator) {
105 16 100       19 if(scalar @{ $test->{'lines_template'} }) {
  16         44  
106 12         16 unshift @{ $test->{'lines_template'} } => '';
  12         33  
107 12         17 push @{ $test->{'lines_template'} } => '';
  12         23  
108             }
109 16         31 $environment = 'between';
110 16         24 next LINE;
111             }
112             # If we have no template lines, don't push empty lines.
113             # This way we can avoid empty templates, meaning we can leave empty test blocks in the
114             # source files without messing up the tests.
115 16 100 66     21 push @{ $test->{'lines_template'} } => $line if scalar @{ $test->{'lines_template'} } || $line !~ m{^\s*$};
  12         30  
  16         130  
116 16         29 next LINE;
117             }
118 84 100       163 if($environment eq 'between') {
119 24 100       61 if($line eq $expected_separator) {
120 16         20 $environment = 'expected';
121 16         25 next LINE;
122             }
123 8         17 push @{ $test->{'lines_between'} } => $line;
  8         19  
124 8         15 next LINE;
125             }
126 60 100       112 if($environment eq 'expected') {
127 32 100       70 if($line eq $expected_separator) {
128 16         22 $environment = 'ending';
129 16 50       21 if(scalar @{ $test->{'lines_expected'} }) {
  16         49  
130 16         18 unshift @{ $test->{'lines_expected'} } => '';
  16         36  
131 16         20 push @{ $test->{'lines_expected'} } => '';
  16         33  
132             }
133 16         33 next LINE;
134             }
135 16         16 push @{ $test->{'lines_expected'} } => $line;
  16         37  
136 16         28 next LINE;
137             }
138 28 50       61 if($environment eq 'ending') {
139 28 100       158 if($line =~ $test_start) {
140 12         41 $self->_add_test($info, $test);
141              
142 12         31 $test = $self->_reset_test();
143 12         59 my $skipit = $1;
144 12 100 66     55 if(defined $skipit && $skipit eq lc 'no') {
145 4         12 $test->{'skip'} = 1;
146             }
147 12 100       73 $test->{'loop'} = defined $2 ? [ split / / => $2 ] : [];
148 12         20 $test->{'test_start_line'} = $row;
149 12         27 $test->{'test_number'} = ++$testcount;;
150 12   50     72 $test->{'is_example'} = $3 || 0;
151 12         38 $test->{'test_name'} = sprintf '%s_%s' => $baseurl, $testcount;
152 12         19 $environment = 'beginning';
153              
154 12         22 next LINE;
155             }
156 16 100 100     26 push @{ $test->{'lines_after'} } => $line if scalar @{ $test->{'lines_after'} } || $line !~ m{^\s*$};
  8         24  
  16         128  
157 16         82 next LINE;
158             }
159             }
160              
161 4         17 $self->_add_test($info, $test);
162              
163 4         214 $self->test_index(delete $info->{'indexed'});
164 4         204 $self->structure($info);
165              
166 4         44 return $self;
167             }
168              
169             sub test_count {
170 0     0 0 0 my $self = shift;
171 0         0 return keys %{ $self->{'test_index'} };
  0         0  
172             }
173              
174             sub _add_test {
175 16     16   23 my $self = shift;
176 16         19 my $info = shift;
177 16         22 my $test = shift;
178              
179             #* Nothing to test
180 16 100 100     21 return if !scalar @{ $test->{'lines_template'} } || $test->{'skip'};
  16         90  
181              
182             #* No loop, just add it
183 8 100       13 if(!scalar @{ $test->{'loop'} }) {
  8         28  
184 4         8 push @{ $info->{'tests'} } => $test;
  4         11  
185 4         16 $info->{'indexed'}{ $test->{'test_number'} } = [ $test ];
186 4         12 return;
187             }
188 4         19 $info->{'indexed'}{ $test->{'test_number'} } = [ ];
189              
190 4         10 foreach my $var (@{ $test->{'loop'} }) {
  4         11  
191 8         892 my $copy = dclone $test;
192              
193 8         19 map { $_ =~ s{\[var\]}{$var}g } @{ $copy->{'lines_template'} };
  24         66  
  8         19  
194 8         14 map { $_ =~ s{\[var\]}{$var}g } @{ $copy->{'lines_expected'} };
  24         56  
  8         18  
195 8         20 $copy->{'loop_variable'} = $var;
196 8         21 $copy->{'test_name'} .= "_$var";
197 8         11 push @{ $info->{'tests'} } => $copy;
  8         16  
198 8         12 push @{ $info->{'indexed'}{ $copy->{'test_number'} } } => $copy;
  8         30  
199             }
200 4         12 return;
201              
202             }
203              
204             sub _reset_test {
205 16     16   44 my $self = shift;
206             return {
207 16         180 is_example => 0,
208             lines_before => [],
209             lines_template => [],
210             lines_after => [],
211             lines_between => [],
212             lines_expected => [],
213             test_number => undef,
214             test_start_line => undef,
215             test_name => undef,
216             loop => [],
217             loop_variable => undef,
218             };
219             }
220              
221             sub _get_filename {
222 6     6   295 return Path::Tiny::path(shift->path)->basename;
223             }
224              
225             sub _get_baseurl {
226 5     5   12 my $self = shift;
227 5         28 my $filename = $self->_get_filename;
228 5         645 (my $baseurl = $filename) =~ s{^([^\.]+)\..*}{$1}; # remove suffix
229 5         20 $baseurl =~ s{-}{_};
230 5         18 return $baseurl;
231             }
232              
233             1;
234             __END__
235              
236             =encoding utf-8
237              
238             =head1 NAME
239              
240             MojoX::CustomTemplateFileParser - Parses a custom Mojo template file format
241              
242             =for html <p><a style="float: left;" href="https://travis-ci.org/Csson/p5-mojox-customtemplatefileparser"><img src="https://travis-ci.org/Csson/p5-mojox-customtemplatefileparser.svg?branch=master">&nbsp;</a>
243              
244             =head1 SYNOPSIS
245              
246             use MojoX::CustomTemplateFileParser;
247              
248             my $parser = MojoX::CustomTemplateFileParser->new(path => '/path/to/file.mojo', output => [qw/Html Pod Test]);
249              
250             print $parser->to_html;
251             print $parser->to_pod;
252             print $parser->to_test;
253              
254             =head1 STATUS
255              
256             Unstable.
257              
258             =head1 DESCRIPTION
259              
260             MojoX::CustomTemplateFileParser parses files containing L<Mojo::Templates|Mojo::Template> mixed with the expected rendering.
261              
262             The parsing creates a data structure that can be output in various formats using plugins.
263              
264             Its purpose is to facilitate development of tag helpers.
265              
266             =head2 Options
267              
268             B<C<path>>
269              
270             The path to the file that should be parsed. Parsing occurs at object creation.
271              
272             B<C<output>>
273              
274             An array reference to plugins in the C<::Plugin::To> namespace.
275              
276             =head2 Methods
277              
278             No public methods. See plugins for output options.
279              
280             =head1 PLUGINS
281              
282             Currently available plugins:
283              
284             =over 4
285              
286             =item * L<MojoX::CustomTemplateFileParser::To::Html>
287              
288             =item * L<MojoX::CustomTemplateFileParser::To::Pod>
289              
290             =item * L<MojoX::CustomTemplateFileParser::To::Test>
291              
292             =back
293              
294             =head1 SEE ALSO
295              
296             =over 4
297              
298             =item * L<Dist::Zilla::Plugin::Test::CreateFromMojoTemplates>
299              
300             =item * L<Dist::Zilla::Plugin::InsertExample::FromMojoTemplates>
301              
302             =back
303              
304             =head1 AUTHOR
305              
306             Erik Carlsson E<lt>info@code301.comE<gt>
307              
308             =head1 COPYRIGHT
309              
310             Copyright 2014- Erik Carlsson
311              
312             =head1 LICENSE
313              
314             This library is free software; you can redistribute it and/or modify
315             it under the same terms as Perl itself.
316              
317             =cut