File Coverage

blib/lib/Perldoc/Parser/Kwid.pm
Criterion Covered Total %
statement 85 88 96.5
branch 20 22 90.9
condition 2 3 66.6
subroutine 13 14 92.8
pod 0 3 0.0
total 120 130 92.3


line stmt bran cond sub pod time code
1             package Perldoc::Parser::Kwid;
2 1     1   6 use Perldoc::Base -Base;
  1         1  
  1         12  
3 1     1   1709 use Perldoc::Reader;
  1     1   2  
  1     1   35  
  1         6  
  1         2  
  1         30  
  1         643  
  1         3  
  1         10  
4              
5             field 'receiver';
6             field 'reader';
7              
8 1     1 0 500 sub init {
9 1         11 my $reader = Perldoc::Reader->new(@_);
10 1         3282 $self->reader($reader);
11 1         12 return $self;
12             }
13              
14             my ($kwid, $head, $para, $bold, $italic, $tt, $brace_bold, $brace_italic, $brace_tt, $brace_any, $comment, $verbatim, $link, $url, $li, $directive_any);
15              
16             my @has_inline = (
17             \$bold, \$italic, \$tt,
18             \$brace_bold, \$brace_italic, \$brace_tt, \$brace_any,
19             \$link, \$url, \$comment, \$li,
20             );
21              
22             $kwid = {
23             begins => qr/^/,
24             id => 'body',
25             has => [ \$head, \$verbatim, \$comment, \$para ],
26             ends => qr/\Z/,
27             };
28              
29             $verbatim = {
30             begins => qr/^(?=[ \t])/m,
31             id => 'pre',
32             ends => qr/(?=\n[^ \t])/,
33             };
34              
35             $comment = {
36             begins => qr/^#/m,
37             id => 'comment',
38             ends => qr/\n/,
39             };
40              
41             $li = {
42             begins => qr/^[-+*]+ /m,
43             id => 'li',
44             has => [ grep {$_ != \$li} @has_inline ],
45             ends => qr/\n/, # (?=[-+*\n])/,
46             };
47              
48             $head = {
49             begins => qr/^=+ /m,
50             id => 'head',
51             event => sub { "h" . (length($_[0]) - 1) },
52             has => \@has_inline,
53             ends => qr/\n/,
54             };
55              
56             $para = {
57             begins => qr//,
58             id => 'p',
59             has => \@has_inline,
60             ends => qr/\n\n/,
61             };
62              
63             $url = {
64             begins => qr{\b\w+://(?:[^,.)\s]|[,.](?!\s))+},
65             id => 'a',
66             event => sub { "a $_[0]" },
67             ends => qr{},
68             };
69              
70             $link = {
71             begins => qr/\[ (?: [^\]\|\n]+ \| )?/x,
72             id => 'a',
73             event => sub { $_[0] =~ /.([^\]\|\n]*)/; "a $1" },
74             has => [ grep {$_ != \$link} @has_inline ],
75             ends => qr/\]/,
76             };
77              
78             $brace_any = {
79             begins => qr{\{+\w+: },
80             id => 'brace',
81             event => sub { $_[0] =~ /(\w+)/; $1 },
82             has => \@has_inline,
83             ends => sub { $_[0] =~ /^(\{+)/; my $len = length($1); qr/\}{$len}/ },
84             nest => 1,
85             };
86              
87             $directive_any = {
88             begins => qr{^\.\w+\n}m,
89             id => 'directive',
90             event => sub { substr($_[0], 1, -1) },
91             has => \@has_inline,
92             ends => sub { $_[0] =~ /(\w+)/; qr/^!$1/ },
93             nest => 1,
94             };
95              
96             inline(\$brace_italic, \$italic => 'i', '/');
97             inline(\$brace_bold, \$bold => 'b', '*');
98             inline(\$brace_tt, \$tt => 'tt', '`');
99              
100             sub inline() {
101 3     3 0 7 my ($b, $p, $name, $sym) = @_;
102 3         5 my $punct = '()$@%&,.!;?';
103              
104 3         6 $sym = quotemeta($sym);
105 33         83 $$p = {
106             begins => qr{(?<=(?:\a|\s))$sym(?=[$punct]*\b)},
107             id => $name,
108             has => [ grep {$_ != $p} @has_inline ],
109 2     2   108 ends => sub { qr{(?<=[\w$punct])$sym(?=[$punct]*(?=\Z|\s))} },
110 3         109 };
111 33         92 $$b = {
112             begins => qr{\{+$sym},
113             id => $name,
114             has => [ grep {$_ != $b} @has_inline ],
115 0     0   0 ends => sub { my $len = length($_[0]) - 1; qr/$sym\}{$len}/ },
  0         0  
116 3         56 };
117             }
118              
119 1     1   1429 use constant ID => 0;
  1         2  
  1         63  
120 1     1   5 use constant HAS => 1;
  1         2  
  1         42  
121 1     1   5 use constant ENDS => 2;
  1         7  
  1         48  
122 1     1   5 use constant EVENT => 3;
  1         1  
  1         351  
123              
124 1     1 0 3 sub parse {
125 1         2 my @stack; # ([$id, $has, $ends, $event], ...)
126 1         4 my @has = (\$kwid);
127 1         25 my $str = $self->reader->all;
128              
129 1 50       10 $str = '' unless defined($str);
130 1         5 pos($str) = 0;
131              
132 184         425 PARSE: {
133 1         3 my $candidates = join('|',
134 34         79 map { "($_)" } (
135 150         277 (map { $_->[ENDS] } @stack),
136 16         69 (map { ($$_)->{begins} } @has)
137             )
138             );
139              
140 16         48 my $pos = pos($str);
141 16         24 my $cur = $pos;
142              
143 77         173 MATCH:
144             pos($str) = $cur;
145 77 100       2951 $str =~ /\G(?:$candidates)/g or do {
146 61 50       129 if ($str =~ /\G(?:\\.)+/gs) {
147 0         0 $cur = pos($str);
148             }
149             else {
150 61         575 ++$cur;
151             }
152 61         174 goto MATCH;
153             };
154              
155             # Now let's find out which ones matched...
156 16         74 foreach my $idx (1 .. $#+) {
157 1     1   6 no strict 'refs';
  1         2  
  1         404  
158 80 100       286 defined $$idx or next;
159              
160 16 100       203 $self->receiver->text(substr($str, $pos, $cur - $pos))
161             if $cur > $pos;
162              
163 16 100       84 if ($idx <= @stack) {
164             # For each stack item from the end on, emit "id" events
165             $self->receiver->ends($_->[EVENT])
166 7         169 for reverse splice(@stack, $idx - 1);
167              
168 7 100       92 @stack or last PARSE;
169              
170             # Pop onto the last frame
171 6         8 @has = @{ $stack[-1][HAS] };
  6         30  
172 6         30 redo PARSE;
173             }
174              
175             # Now we are at "begins".
176 9         273 my $parser = ${ $has[ $idx - @stack - 1 ] };
  9         23  
177 9         19 my $id = $parser->{id};
178 9         13 my $ends = $parser->{ends};
179 9   66     42 my $event = $parser->{event} || $id;
180              
181 9 100       52 $ends = $ends->($$idx) if ref $ends eq 'CODE';
182 9 100       29 $event = $event->($$idx) if ref $event eq 'CODE';
183              
184             # Grep for nestedness
185 9         16 my @this_has = ();
186              
187 9         24 HAS:
188 9         14 foreach my $has (@{ $parser->{has} }) {
189 87 100       179 if (($$has)->{nest}) {
190 8         16 push @this_has, $has;
191             }
192             else {
193 79         110 foreach my $frame (@stack) {
194 129 100       575 next HAS if $frame->[ID] eq ($$has)->{id};
195             }
196             }
197 86         166 push @this_has, $has;
198             }
199              
200 9         31 push @stack, [ $id, \@this_has, $ends, $event ];
201 9         30 @has = @this_has;
202              
203 9         351 $self->receiver->begins($event);
204 9         34 redo PARSE;
205             }
206             }
207             }
208              
209             =head1 NAME
210              
211             Perldoc::Parser::Kwid;
212              
213             =head1 SYNOPSIS
214              
215             # Convert kwid to html:
216             use Perldoc::Parser::Kwid;
217             use Perldoc::Emitter::HTML;
218              
219             my $html = '';
220             my $receiver = Perldoc::Emitter::HTML->new->init(stringref => $html);
221             my $kwid_text = 'This is Kwid markup';
222             my $parser = Perldoc::Parser::Kwid->new(receiver => $receiver)
223             ->init(string => $kwid_text);
224             $parser->parse;
225             print $html;
226              
227             =head1 DESCRIPTION
228              
229             Parse Kwid markup and fire events.
230              
231             =head1 AUTHOR
232              
233             Audrey Tang
234             Ingy döt Net
235              
236             Audrey wrote the original code for this parser.
237              
238             =head1 COPYRIGHT
239              
240             Copyright (c) 2006. Ingy döt Net. All rights reserved.
241              
242             This program is free software; you can redistribute it and/or modify it
243             under the same terms as Perl itself.
244              
245             See L
246              
247             =cut