File Coverage

blib/lib/Markdent/Parser.pm
Criterion Covered Total %
statement 111 111 100.0
branch 19 22 86.3
condition 4 6 66.6
subroutine 23 23 100.0
pod 1 2 50.0
total 158 164 96.3


line stmt bran cond sub pod time code
1             package Markdent::Parser;
2              
3 34     34   17302 use strict;
  34         85  
  34         1202  
4 34     34   215 use warnings;
  34         76  
  34         1323  
5 34     34   206 use namespace::autoclean 0.09;
  34         1113  
  34         292  
6              
7             our $VERSION = '0.38';
8              
9 34     34   19947 use Markdent::Parser::BlockParser;
  34         150  
  34         1654  
10 34     34   18702 use Markdent::Parser::SpanParser;
  34         172  
  34         1678  
11 34     34   322 use Markdent::Types;
  34         86  
  34         326  
12 34     34   864645 use Module::Runtime qw( require_module );
  34         92  
  34         331  
13 34     34   2169 use Moose::Meta::Class;
  34         106  
  34         1596  
14 34     34   600 use Params::ValidationCompiler 0.14 qw( validation_for );
  34         1193  
  34         2429  
15 34     34   259 use Specio::Declare;
  34         86  
  34         402  
16 34     34   7093 use Try::Tiny;
  34         307  
  34         2454  
17              
18 34     34   264 use Moose 0.92;
  34         609  
  34         375  
19 34     34   246072 use MooseX::SemiAffordanceAccessor 0.05;
  34         795  
  34         282  
20 34     34   141227 use MooseX::StrictConstructor 0.08;
  34         754  
  34         282  
21              
22             with 'Markdent::Role::AnyParser';
23              
24             has _block_parser_class => (
25             is => 'rw',
26             isa => t('BlockParserClass'),
27             init_arg => 'block_parser_class',
28             default => 'Markdent::Parser::BlockParser',
29             );
30              
31             has _block_parser => (
32             is => 'rw',
33             does => object_does_type('Markdent::Role::BlockParser'),
34             lazy => 1,
35             init_arg => undef,
36             builder => '_build_block_parser',
37             );
38              
39             has _block_parser_args => (
40             is => 'rw',
41             isa => t('HashRef'),
42             init_arg => undef,
43             );
44              
45             has _span_parser_class => (
46             is => 'rw',
47             isa => t('SpanParserClass'),
48             init_arg => 'span_parser_class',
49             default => 'Markdent::Parser::SpanParser',
50             );
51              
52             has _span_parser => (
53             is => 'ro',
54             does => object_does_type('Markdent::Role::SpanParser'),
55             lazy => 1,
56             init_arg => undef,
57             builder => '_build_span_parser',
58             );
59              
60             has _span_parser_args => (
61             is => 'rw',
62             isa => t('HashRef'),
63             init_arg => undef,
64             );
65              
66             override BUILDARGS => sub {
67             my $class = shift;
68              
69             my $args = super();
70              
71             if ( exists $args->{dialect} ) {
72              
73             # XXX - deprecation warning
74             $args->{dialects} = [ delete $args->{dialect} ];
75             }
76             elsif ( exists $args->{dialects} ) {
77             $args->{dialects} = [ $args->{dialects} ]
78             unless ref $args->{dialects};
79             }
80              
81             return $args;
82             };
83              
84             sub BUILD {
85 181     181 0 398 my $self = shift;
86 181         350 my $args = shift;
87              
88 181         740 $self->_set_classes_for_dialects($args);
89              
90 181         384 my %sp_args;
91 181         4934 for my $key (
92 1629         2644 grep {defined}
93 1629         17683 map { $_->init_arg() }
94             $self->_span_parser_class()->meta()->get_all_attributes()
95             ) {
96              
97             $sp_args{$key} = $args->{$key}
98 543 100       1602 if exists $args->{$key};
99             }
100              
101 181         5462 $sp_args{handler} = $self->handler();
102              
103 181         5753 $self->_set_span_parser_args( \%sp_args );
104              
105 181         371 my %bp_args;
106 181         4621 for my $key (
107 1112         1957 grep {defined}
108 1112         12666 map { $_->init_arg() }
109             $self->_block_parser_class()->meta()->get_all_attributes()
110             ) {
111              
112             $bp_args{$key} = $args->{$key}
113 543 100       1501 if exists $args->{$key};
114             }
115              
116 181         5724 $bp_args{handler} = $self->handler();
117 181         4588 $bp_args{span_parser} = $self->_span_parser();
118              
119 181         5846 $self->_set_block_parser_args( \%bp_args );
120             }
121              
122             sub _set_classes_for_dialects {
123 181     181   411 my $self = shift;
124 181         347 my $args = shift;
125              
126 181         434 my $dialects = delete $args->{dialects};
127              
128 181 100       324 return unless @{ $dialects || [] };
  181 100       1036  
129              
130 47         149 for my $thing (qw( block_parser span_parser )) {
131 94         201 my @roles;
132              
133 94         177 for my $dialect ( @{$dialects} ) {
  94         259  
134 96 50       285 next if $dialect eq 'Standard';
135              
136 96         357 my $role = $self->_role_name_for_dialect( $dialect, $thing );
137              
138             my $found = try {
139 96     96   4498 require_module($role);
140             }
141             catch {
142 1 50   1   324 die $_ unless $_ =~ /Can't locate/;
143 1         4 0;
144 96         756 };
145 96 100       27692 next unless $found;
146              
147 95         431 my $specified_class = $args->{ $thing . '_class' };
148              
149             next
150 95 50 66     341 if $specified_class
      66        
151             && $specified_class->can('meta')
152             && $specified_class->meta()->does_role($role);
153              
154 95         314 push @roles, $role;
155             }
156              
157 94 100       302 next unless @roles;
158              
159 93         231 my $class_meth = q{_} . $thing . '_class';
160              
161 93         3082 my $class = Moose::Meta::Class->create_anon_class(
162             superclasses => [ $self->$class_meth() ],
163             roles => \@roles,
164             cache => 1,
165             )->name();
166              
167 93         148636 my $set_meth = '_set' . $class_meth;
168 93         3652 $self->$set_meth($class);
169             }
170             }
171              
172             sub _role_name_for_dialect {
173 96     96   178 my $self = shift;
174 96         169 my $dialect = shift;
175 96         174 my $type = shift;
176              
177 96         408 my $suffix = join q{}, map {ucfirst} split /_/, $type;
  192         629  
178              
179 96 100       394 if ( $dialect =~ /::/ ) {
180 4         16 return join '::', $dialect, $suffix;
181             }
182             else {
183 92         361 return join '::', 'Markdent::Dialect', $dialect, $suffix;
184             }
185             }
186              
187             sub _build_block_parser {
188 179     179   359 my $self = shift;
189              
190 179         4419 return $self->_block_parser_class()->new( $self->_block_parser_args() );
191             }
192              
193             sub _build_span_parser {
194 181     181   368 my $self = shift;
195              
196 181         4504 return $self->_span_parser_class()->new( $self->_span_parser_args() );
197             }
198              
199             {
200             my $validator = validation_for(
201             params => [ markdown => { type => t('Str') } ],
202             named_to_list => 1,
203             );
204              
205             sub parse {
206 175     175 1 964 my $self = shift;
207 175         3315 my ($text) = $validator->(@_);
208              
209 175         5179 $self->_clean_text( \$text );
210              
211 175         797 $self->_send_event('StartDocument');
212              
213 175         5525 $self->_block_parser()->parse_document( \$text );
214              
215 173         790 $self->_send_event('EndDocument');
216              
217 173         4912 return;
218             }
219             }
220              
221             sub _clean_text {
222 175     175   382 my $self = shift;
223 175         307 my $text = shift;
224              
225 175         306 ${$text} =~ s/\r\n?/\n/g;
  175         872  
226 4         13 ${$text} .= "\n"
227 175 100       309 unless substr( ${$text}, -1, 1 ) eq "\n";
  175         1133  
228              
229 175         355 return;
230             }
231              
232             __PACKAGE__->meta()->make_immutable();
233              
234             1;
235              
236             # ABSTRACT: A markdown parser
237              
238             __END__
239              
240             =pod
241              
242             =encoding UTF-8
243              
244             =head1 NAME
245              
246             Markdent::Parser - A markdown parser
247              
248             =head1 VERSION
249              
250             version 0.38
251              
252             =head1 SYNOPSIS
253              
254             my $handler = Markdent::Handler::HTMLStream->new( ... );
255              
256             my $parser = Markdent::Parser->new(
257             dialect => ...,
258             handler => $handler,
259             );
260              
261             $parser->parse( markdown => $markdown );
262              
263             =head1 DESCRIPTION
264              
265             This class provides the primary interface for creating a parser. It ties a
266             block and span parser together with a handler.
267              
268             By default, it will parse the standard Markdown dialect, but you can provide
269             alternate block or span parser classes.
270              
271             =head1 METHODS
272              
273             This class provides the following methods:
274              
275             =head2 Markdent::Parser->new(...)
276              
277             This method creates a new parser. It accepts the following parameters:
278              
279             =over 4
280              
281             =item * dialects => $name or [ $name1, $name2 ]
282              
283             You can use this to apply dialect roles to the standard parser class.
284              
285             If a dialect name does not contain a namespace separator (::), the constructor
286             looks for roles named C<Markdent::Dialect::${dialect}::BlockParser> and
287             C<Markdent::Dialect::${dialect}::SpanParser>.
288              
289             If a dialect name does contain a namespace separator, it is used a prefix -
290             C<$dialect::BlockParser> and C<$dialect::SpanParser>.
291              
292             If any relevant roles are found, they will be used by the parser.
293              
294             It is okay if a given dialect only provides a block or span parser, but not
295             both.
296              
297             =item * block_parser_class => $class
298              
299             This defaults to L<Markdent::Parser::BlockParser>, but can be any class which
300             implements the L<Markdent::Role::BlockParser> role.
301              
302             =item * span_parser_class => $class
303              
304             This defaults to L<Markdent::Parser::SpanParser>, but can be any class which
305             implements the L<Markdent::Role::SpanParser> role.
306              
307             =item * handler => $handler
308              
309             This can be any object which implements the L<Markdent::Role::Handler>
310             role. It is required.
311              
312             =back
313              
314             =head2 $parser->parse( markdown => $markdown )
315              
316             This method parses the given document. The parsing will cause events to be
317             fired which will be passed to the parser's handler.
318              
319             =head1 ROLES
320              
321             This class does the L<Markdent::Role::EventsAsMethods> and
322             L<Markdent::Role::Handler> roles.
323              
324             =head1 BUGS
325              
326             See L<Markdent> for bug reporting details.
327              
328             Bugs may be submitted at L<https://github.com/houseabsolute/Markdent/issues>.
329              
330             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
331              
332             =head1 SOURCE
333              
334             The source code repository for Markdent can be found at L<https://github.com/houseabsolute/Markdent>.
335              
336             =head1 AUTHOR
337              
338             Dave Rolsky <autarch@urth.org>
339              
340             =head1 COPYRIGHT AND LICENSE
341              
342             This software is copyright (c) 2020 by Dave Rolsky.
343              
344             This is free software; you can redistribute it and/or modify it under
345             the same terms as the Perl 5 programming language system itself.
346              
347             The full text of the license can be found in the
348             F<LICENSE> file included with this distribution.
349              
350             =cut