File Coverage

blib/lib/WikiText/Socialtext/Parser.pm
Criterion Covered Total %
statement 64 64 100.0
branch 2 4 50.0
condition 8 14 57.1
subroutine 22 22 100.0
pod 0 3 0.0
total 96 107 89.7


line stmt bran cond sub pod time code
1             ##
2             # name: WikiText::Socialtext::Parser
3             # abstract: Socialtext WikiText Parser Module
4             # author: Ingy döt Net
5             # license: perl
6             # copyright: 2008, 2010, 2011
7              
8             package WikiText::Socialtext::Parser;
9 4     4   2478 use strict;
  4         8  
  4         164  
10 4     4   22 use warnings;
  4         10  
  4         156  
11 4     4   23 use base 'WikiText::Parser';
  4         11  
  4         1022  
12              
13             # Reusable regexp generators used by the grammar
14             my $ALPHANUM = '\p{Letter}\p{Number}\pM';
15              
16             # These are all stolen from URI.pm
17             my $reserved = q{;/?:@&=+$,[]#};
18             my $mark = q{-_.!~*'()};
19             my $unreserved = "A-Za-z0-9\Q$mark\E";
20             my $uric = quotemeta($reserved) . $unreserved . "%";
21             my %im_types = (
22             yahoo => 'yahoo',
23             ymsgr => 'yahoo',
24             callto => 'callto',
25             skype => 'callto',
26             callme => 'callto',
27             aim => 'aim',
28             msn => 'msn',
29             asap => 'asap',
30             );
31             my $im_re = join '|', keys %im_types;
32              
33             sub create_grammar {
34 27     27 0 8925 my $all_phrases = [
35             qw(waflphrase asis wikilink a im mail tt b i del)
36             ];
37 27         128 my $all_blocks = [
38             qw(
39             pre wafl_block
40             hr hx
41             waflparagraph
42             ul ol
43             blockquote table
44             p empty
45             else
46             )
47             ];
48              
49             return {
50             _all_blocks => $all_blocks,
51             _all_phrases => $all_phrases,
52              
53             top => {
54             blocks => $all_blocks,
55             },
56              
57             empty => {
58             match => qr/^\s*\n/,
59             filter => sub {
60 2     2   877 my $node = shift;
61 2         6 $node->{type} = '';
62             },
63             },
64              
65             wafl_block => {
66             match => qr/(?:^\.([\w\-]+)\ *\n)((?:.*\n)*?)(?:\.\1\ *\n|\z)/,
67             },
68              
69             p => {
70             match => qr/^( # Capture whole thing
71             (?m:
72             ^(?! # All consecutive lines *not* starting with
73             (?:
74             [\#\-\*]+[\ ] |
75             [\^\|\>] |
76             \.\w+\s*\n |
77             \{[^\}]+\}\s*\n
78             )
79             )
80             .*\S.*\n
81             )+
82             )
83             (\s*\n)* # and all blank lines after
84             /x,
85             phrases => $all_phrases,
86 28     28   17995 filter => sub { chomp },
87             },
88              
89             else => {
90             match => qr/^(.*)\n/,
91             phrases => [],
92             filter => sub {
93 2     2   1210 my $node = shift;
94 2         7 $node->{type} = 'p';
95             },
96             },
97              
98             pre => {
99             match => qr/^(?m:^\.pre\ *\n)((?:.*\n)*?)(?m:^\.pre\ *\n)(?:\s*\n)?/,
100             },
101              
102             blockquote => {
103             match => qr/^((?m:^>.*\n)+)(\s*\n)?/,
104             blocks => $all_blocks,
105             filter => sub {
106 4     4   1784 s/^>\ ?//gm;
107             },
108             },
109              
110             waflparagraph => {
111             match => qr/^\{(.*)\}[\ \t]*\n(?:\s*\n)?/,
112             filter => sub {
113 1     1   612 my $node = shift;
114 1         5 my ($function, $options) = split /[: ]/, $node->{text}, 2;
115 1 50       6 my $replacement = defined $1 ? $1 : '';
116 1 50       4 $options = '' unless defined $options; # protect against an undefined here
117 1         5 $options =~ s/\s*(.*?)\s*/$replacement/;
118 1         4 $node->{attributes}{function} = $function;
119 1         4 $node->{attributes}{options} = $options;
120 1         6 undef $_;
121             },
122             },
123              
124             hx => {
125             match => qr/^(\^+) *(.*?)(\s+=+)?\s*?\n+/,
126             phrases => $all_phrases,
127             filter => sub {
128 6     6   5901 my $node = shift;
129 6         31 $node->{type} = 'h' . length($node->{1});
130 6         26 $_ = $node->{text} = $node->{2};
131             },
132             },
133              
134             ul => {
135             match => re_list('[\*\-\+]'),
136             blocks => [qw(ul ol subl li)],
137 4     4   2662 filter => sub { s/^[\*\-\+\#] *//mg },
138             },
139              
140             ol => {
141             match => re_list('\#'),
142             blocks => [qw(ul ol subl li)],
143 1     1   785 filter => sub { s/^[\*\#] *//mg },
144             },
145              
146             subl => {
147             type => 'li',
148              
149             match => qr/^( # Block must start at beginning
150             # Capture everything in $1
151             (.*)\n # Capture the whole first line
152             [\*\#]+\ .*\n # Line starting with one or more bullet
153             (?:[\*\#]+\ .*\n)* # Lines starting with '*' or '#'
154             )(?:\s*\n)?/x, # Eat trailing lines
155             blocks => [qw(ul ol li2)],
156             },
157              
158             li => {
159             match => qr/(.*)\n/, # Capture the whole line
160             phrases => $all_phrases,
161             },
162              
163             li2 => {
164             type => '',
165             match => qr/(.*)\n/, # Capture the whole line
166             phrases => $all_phrases,
167             },
168              
169             hr => {
170             match => qr/^--+(?:\s*\n)?/,
171             },
172              
173             table => {
174             match => qr/^(
175             (
176             (?m:^\|.*\|\ \n(?=\|))
177             |
178             (?m:^\|.*\|\ \ +\n)
179             |
180             (?ms:^\|.*?\|\n)
181             )+
182             )(?:\s*\n)?/x,
183             blocks => ['tr'],
184             },
185              
186             tr => {
187             match => qr/^((?m:^\|.*?\|(?:\n| \n(?=\|)| +\n)))/s,
188             blocks => ['td'],
189 6     6   4607 filter => sub { s/\s+\z// },
190             },
191              
192             # XXX Need to support blocks in TD
193             td => {
194             match => qr/\|?\s*(.*?)\s*\|\n?/s,
195             phrases => $all_phrases,
196             },
197              
198             wikilink => {
199             match => qr/
200             (?:"([^"]*)"\s*)?(?:^|(?<=[^$ALPHANUM]))\[(?=[^\s\[\]])
201             (.*?)
202             \](?=[^$ALPHANUM]|\z)
203             /x,
204             filter => sub {
205 2     2   377 my $node = shift;
206 2         10 $node->{attributes}{target} = $node->{2};
207 2   66     13 $_ = $node->{1} || $node->{2};
208             },
209             },
210              
211             b => {
212             match => re_huggy(q{\*}),
213             phrases => $all_phrases,
214             },
215              
216             tt => {
217             match => re_huggy(q{\`}),
218             },
219              
220             i => {
221             match => WikiText::Socialtext::Parser::re_huggy(q{\_}),
222             phrases => $all_phrases,
223             },
224              
225             del => {
226             match => re_huggy(q{\-}),
227             phrases => $all_phrases,
228             },
229              
230             im => {
231             match => qr/(\b(?:$im_re)\:[^\s\>\)]+)/,
232             filter => sub {
233 1     1   342 my $node = shift;
234 1         6 my ($type, $id) = split /:/, $node->{text}, 2;
235 1         6 $node->{attributes}{type} = $type;
236 1         4 $node->{attributes}{id} = $id;
237 1         4 undef $_;
238             },
239             },
240              
241             waflphrase => {
242             match => qr/
243             (?:^|(?<=[\s\-]))
244             (?:"(.+?)")?
245             \{
246             ([\w-]+)
247             (?=[\:\ \}])
248             (?:\s*:)?
249             \s*(.*?)\s*
250             \}
251             (?=[^A-Za-z0-9]|\z)
252             /x,
253             filter => sub {
254 1     1   188 my $node = shift;
255 1         3 my ($label, $function, $options) = @{$node}{qw(1 2 3)};
  1         5  
256 1   50     4 $label ||= '';
257 1         4 $node->{attributes}{function} = $function;
258 1         4 $node->{attributes}{options} = $options;
259 1         3 $_ = $label;
260             },
261             },
262              
263             mail => {
264             match => qr/
265             (?:"([^"]*)"\s*)?
266            
267             (?:mailto:)?
268             ([\w+%\-\.]+@(?:[\w\-]+\.)+[\w\-]+)
269             >?
270             /x,
271             filter => sub {
272 3     3   631 my $node = shift;
273 3   66     14 $_ = $node->{1} || $node->{2};
274 3         14 $node->{attributes}{address} = $node->{2};
275             },
276             },
277              
278             a => {
279             type => 'hyperlink',
280             match => qr{
281             (?:"([^"]*)"\s*)?
282            
283             (
284             (?:http|https|ftp|irc|file):
285             (?://)?
286             [$uric]+
287             [A-Za-z0-9/#]
288             )
289             >?
290             }x,
291             filter => sub {
292 5     5   1001 my $node = shift;
293 5   66     34 $_ = $node->{1} || $node->{2};
294 5         24 $node->{attributes}{target} = $node->{2};
295             },
296             },
297              
298             asis => {
299             match => qr/
300             \{\{
301             (.*?)
302             \}\}(\}*)
303             /xs,
304             filter => sub {
305 3     3   642 my $node = shift;
306 3         4 $node->{type} = '';
307 3         10 $_ = $node->{1} . $node->{2};
308             },
309             },
310              
311 4     4   4161 };
  4         44  
  4         56  
  27         3052  
312             }
313              
314             sub re_huggy {
315 108     108 0 190286 my $brace1 = shift;
316 108   33     517 my $brace2 = shift || $brace1;
317              
318 108         2700 qr/
319             (?:^|(?<=[^{$ALPHANUM}$brace1]))$brace1(?=\S)(?!$brace2)
320             (.*?)
321             $brace2(?=[^{$ALPHANUM}$brace2]|\z)
322             /x;
323             }
324              
325             sub re_list {
326 54     54 0 93 my $bullet = shift;
327 54         3434 return qr/^( # Block must start at beginning
328             # Capture everything in $1
329             ^$bullet+\ .*\n # Line starting with one or more bullet
330             (?:[\*\-\+\#]+\ .*\n)* # Lines starting with '*' or '#'
331             )(?:\s*\n)?/x, # Eat trailing lines
332             }
333              
334             1;