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 35     35   20618 use strict;
  35         91  
  35         1290  
4 35     35   207 use warnings;
  35         81  
  35         1429  
5 35     35   211 use namespace::autoclean 0.09;
  35         1227  
  35         293  
6              
7             our $VERSION = '0.40';
8              
9 35     35   22176 use Markdent::Parser::BlockParser;
  35         146  
  35         1717  
10 35     35   22031 use Markdent::Parser::SpanParser;
  35         175  
  35         1825  
11 35     35   357 use Markdent::Types;
  35         123  
  35         392  
12 35     35   911288 use Module::Runtime qw( require_module );
  35         116  
  35         362  
13 35     35   2221 use Moose::Meta::Class;
  35         90  
  35         1683  
14 35     35   678 use Params::ValidationCompiler 0.14 qw( validation_for );
  35         1353  
  35         2715  
15 35     35   283 use Specio::Declare;
  35         94  
  35         426  
16 35     35   7421 use Try::Tiny;
  35         342  
  35         2510  
17              
18 35     35   282 use Moose 0.92;
  35         614  
  35         332  
19 35     35   257001 use MooseX::SemiAffordanceAccessor 0.05;
  35         872  
  35         287  
20 35     35   151272 use MooseX::StrictConstructor 0.08;
  35         859  
  35         292  
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 186     186 0 473 my $self = shift;
86 186         403 my $args = shift;
87              
88 186         808 $self->_set_classes_for_dialects($args);
89              
90 186         427 my %sp_args;
91 186         5356 for my $key (
92 1674         3266 grep {defined}
93 1674         19196 map { $_->init_arg }
94             $self->_span_parser_class->meta->get_all_attributes
95             ) {
96              
97             $sp_args{$key} = $args->{$key}
98 558 100       1679 if exists $args->{$key};
99             }
100              
101 186         5986 $sp_args{handler} = $self->handler;
102              
103 186         6329 $self->_set_span_parser_args( \%sp_args );
104              
105 186         375 my %bp_args;
106 186         4999 for my $key (
107 1142         2116 grep {defined}
108 1142         13687 map { $_->init_arg }
109             $self->_block_parser_class->meta->get_all_attributes
110             ) {
111              
112             $bp_args{$key} = $args->{$key}
113 558 100       1580 if exists $args->{$key};
114             }
115              
116 186         5590 $bp_args{handler} = $self->handler;
117 186         5001 $bp_args{span_parser} = $self->_span_parser;
118              
119 186         6294 $self->_set_block_parser_args( \%bp_args );
120             }
121              
122             sub _set_classes_for_dialects {
123 186     186   409 my $self = shift;
124 186         377 my $args = shift;
125              
126 186         485 my $dialects = delete $args->{dialects};
127              
128 186 100       404 return unless @{ $dialects || [] };
  186 100       1159  
129              
130 48         148 for my $thing (qw( block_parser span_parser )) {
131 96         228 my @roles;
132              
133 96         171 for my $dialect ( @{$dialects} ) {
  96         257  
134 98 50       291 next if $dialect eq 'Standard';
135              
136 98         363 my $role = $self->_role_name_for_dialect( $dialect, $thing );
137              
138             my $found = try {
139 98     98   4734 require_module($role);
140             }
141             catch {
142 1 50   1   425 die $_ unless $_ =~ /Can't locate/;
143 1         5 0;
144 98         757 };
145 98 100       34094 next unless $found;
146              
147 97         372 my $specified_class = $args->{ $thing . '_class' };
148              
149             next
150 97 50 66     357 if $specified_class
      66        
151             && $specified_class->can('meta')
152             && $specified_class->meta->does_role($role);
153              
154 97         346 push @roles, $role;
155             }
156              
157 96 100       291 next unless @roles;
158              
159 95         253 my $class_meth = q{_} . $thing . '_class';
160              
161 95         3254 my $class = Moose::Meta::Class->create_anon_class(
162             superclasses => [ $self->$class_meth() ],
163             roles => \@roles,
164             cache => 1,
165             )->name;
166              
167 95         185134 my $set_meth = '_set' . $class_meth;
168 95         3648 $self->$set_meth($class);
169             }
170             }
171              
172             sub _role_name_for_dialect {
173 98     98   189 my $self = shift;
174 98         183 my $dialect = shift;
175 98         171 my $type = shift;
176              
177 98         398 my $suffix = join q{}, map {ucfirst} split /_/, $type;
  196         673  
178              
179 98 100       420 if ( $dialect =~ /::/ ) {
180 4         19 return join '::', $dialect, $suffix;
181             }
182             else {
183 94         395 return join '::', 'Markdent::Dialect', $dialect, $suffix;
184             }
185             }
186              
187             sub _build_block_parser {
188 184     184   401 my $self = shift;
189              
190 184         4825 return $self->_block_parser_class->new( $self->_block_parser_args );
191             }
192              
193             sub _build_span_parser {
194 186     186   403 my $self = shift;
195              
196 186         4884 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 180     180 1 998 my $self = shift;
207 180         3508 my ($text) = $validator->(@_);
208              
209 180         5347 $self->_clean_text( \$text );
210              
211 180         825 $self->_send_event('StartDocument');
212              
213 180         6098 $self->_block_parser->parse_document( \$text );
214              
215 178         997 $self->_send_event('EndDocument');
216              
217 178         5239 return;
218             }
219             }
220              
221             sub _clean_text {
222 180     180   368 my $self = shift;
223 180         330 my $text = shift;
224              
225 180         311 ${$text} =~ s/\r\n?/\n/g;
  180         909  
226 4         16 ${$text} .= "\n"
227 180 100       356 unless substr( ${$text}, -1, 1 ) eq "\n";
  180         1254  
228              
229 180         377 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.40
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> role. It
310             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) 2021 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