line
stmt
bran
cond
sub
pod
time
code
1
package Text::Nimble;
2
3
2
2
42037
use 5.010; # for recursive regexes, but also using //
2
8
2
87
4
2
2
13
use strict;
2
3
2
76
5
2
2
11
use warnings FATAL => 'all';
2
9
2
105
6
7
2
2
12
use Carp;
2
3
2
15714
8
9
our $VERSION = '0.002001';
10
$VERSION = eval $VERSION;
11
12
13
my %renderer = (
14
html => \&_renderer_html,
15
);
16
17
18
sub render {
19
4
4
1
11060
my ($format, $nimble) = @_;
20
4
50
14
croak "Text::Nimble::render takes two arguments but got ".(scalar @_) unless @_ == 2;
21
4
50
33
39
croak "Text::Nimble::render takes a render format name as its first argument" unless defined $_[0] && $_[0] =~ /^\w+$/;
22
4
50
66
53
croak "Text::Nimble::render takes Nimble text or a Nimble parse result as its second argument" unless defined $_[1] && (!ref $_[1] || ref $_[1] eq 'ARRAY' || (ref $_[1] eq 'HASH' && $_[1]{tree} && $_[1]{macro}));
33
23
4
50
16
croak "Text::Nimble::render was given an unknown render format '$_[0]'" unless $renderer{$format};
24
25
4
100
17
$nimble = parse($nimble) unless ref $nimble eq 'HASH';
26
4
18
my $output = $renderer{$format}($nimble);
27
4
100
732
return wantarray ? ($output, $nimble->{meta}, $nimble->{error}) : $output;
28
}
29
30
sub parse {
31
5
50
5
1
205113
croak "Text::Nimble::parse takes one argument but got ".(scalar @_) unless @_ == 1;
32
33
5
8
my (%meta, %macro, @error);
34
35
local *_nimble_makelines = sub {
36
# break input into lines
37
5
50
33
5
66
croak "Text::Nimble::parse expects a string" unless @_==1 && defined $_[0] && !ref $_[0];
33
38
5
11
my $input = $_[0];
39
5
70
$input =~ s/\r//g;
40
5
669
my @lines = split(/\n/, $input, -1);
41
42
5
71
my $next_indent;
43
5
23
for (my $i=$#lines; $i>=0; $i--) {
44
2215
100
33
6857
if ($lines[$i] =~ /^( *)\S/) {
50
45
1550
3714
$next_indent = $1;
46
} elsif (defined $next_indent && $lines[$i] =~ /^\s*$/) {
47
665
1925
$lines[$i] = $next_indent;
48
}
49
}
50
51
5
16
return \@lines;
52
5
32
};
53
54
local *_macro_arg_preprocess = sub {
55
170
170
249
my ($type, $arg) = @_;
56
57
170
100
383
if ($type eq 'nimble') {
50
58
30
100
87
return ref $arg ? _nimble_parse($arg) : _nimble_parse_inline($arg);
59
} elsif ($type eq 'raw') {
60
140
100
624
return ref $arg ? join("\n", @$arg) : $arg;
61
} else {
62
0
0
die "unreachable: _macro_arg_preprocess tried to decode a macro argument with unrecognized type '$type'";
63
}
64
5
26
};
65
66
local *_macro_invocation_preprocess = sub {
67
50
50
81
my ($macro_name, $node) = @_;
68
50
76
foreach my $arg (keys %{$macro{$macro_name}{args}}) {
50
151
69
105
100
396
$node->{args}{$arg} =
70
defined $node->{args}{$arg}
71
? _macro_arg_preprocess($macro{$macro_name}{args}{$arg}{type}, $node->{args}{$arg})
72
: $macro{$macro_name}{args}{$arg}{default};
73
}
74
5
24
};
75
76
5
23
my %inlinestyle = (
77
'*' => 'strong',
78
'/' => 'emphasis',
79
);
80
5
42
my $inlinestyle_re = '['.quotemeta(join('', keys %inlinestyle)).']';
81
82
5
40
my %entityshorthand = (
83
'--' => '–',
84
'---' => '—',
85
'<--' => '←',
86
'-->' => '→',
87
'<==' => '⇐',
88
'==>' => '⇒',
89
);
90
5
35
my $entityshorthand_re = '(?:'.join('|', map {quotemeta} sort {length $b <=> length $a} keys %entityshorthand).')';
30
56
49
71
91
92
5
13152
my $inline_re = qr/
93
# start at pos(), then group for delimiter-matching recursion
94
# 1
95
\G(
96
# plain text or escapes
97
# 2
98
((?:[\w\s\.\,\(\)\']+|\\.)+)
99
# inline code
100
# 3 4
101
| (\`+)\s*(.*?)\s*\g{-2}
102
# inline style
103
# 5 6
104
| ($inlinestyle_re)(?!\s)((?>(?1))+?)(?
105
# entity literal
106
# 7
107
| (\&(?:\#(?:\d+|x[0-9a-fA-F]+)|\w+)\;)
108
# entity shorthand
109
# 8
110
| ($entityshorthand_re)
111
# brackets
112
# 9
113
| \[((?>(?1))+?)\]
114
# failure; take any one character
115
# 10
116
| (.)
117
)
118
/x;
119
120
local *_nimble_parse_inline = sub {
121
1155
1155
1603
my $text = shift;
122
1155
50
33
6878
croak "_nimble_parse_inline takes exactly one nonref defined argument" if @_ || !defined $text || ref $text;
33
123
124
1155
1142
my @output;
125
my $append_text = sub {
126
2330
100
100
8109
if (@output && $output[-1]{type} eq 'text') {
127
835
7947
$output[-1]{text} .= $_[0];
128
} else {
129
1495
13454
push @output, {type=>"text", text=>$_[0]};
130
}
131
1155
3749
};
132
1155
8087
while ($text =~ /$inline_re/g) {
133
2825
8731
my ($plain, $code, $styletype, $stylecontent, $entity_literal, $entity_shorthand, $bracketcontents, $char)
134
= ($2, $4, $5, $6, $7, $8, $9, $10 );
135
136
2825
100
7283
if (defined $plain) {
100
100
100
100
100
50
137
1795
2557
$plain =~ s/\\(.)/$1/g;
138
1795
3150
$append_text->($plain);
139
} elsif (defined $code) {
140
70
542
push @output, {type=>"code", text=>$code};
141
} elsif (defined $styletype) {
142
210
555
push @output, {type=>$inlinestyle{$styletype}, content=>_nimble_parse_inline($stylecontent)};
143
} elsif (defined $entity_literal) {
144
20
143
push @output, {type=>"entity", html=>$entity_literal};
145
} elsif (defined $entity_shorthand) {
146
70
541
push @output, {type=>"entity", html=>$entityshorthand{$entity_shorthand}};
147
} elsif (defined $bracketcontents) {
148
145
100
1241
if ($bracketcontents =~ /^\$(\w+)((?:\s+\w+\=(?:\S*|([\"\'])((?:(?!\\|\g{-2}).|\\.)*)\g{-2}))*)\s*$/) {
100
100
100
149
65
161
my ($macro_name, $macro_args) = ($1, $2);
150
65
74
my ($node, @errors);
151
152
65
100
196
push @errors, "no macro by that name is defined" unless $macro{$macro_name};
153
154
65
100
149
if (!@errors) {
155
60
199
$node = {type=>"macro", macro=>$macro_name, args=>{}};
156
60
347
while ($macro_args =~ /\G\s+(\w+)\=(?:(?![\"\'])(\S*)|([\"\'])((?:(?!\\|\g{-2}).|\\.)*)\g{-2})/g) {
157
150
66
527
my ($arg_name, $arg_value) = ($1, $2 // $4);
158
150
100
395
push @errors, "duplicate argument '$arg_name'" if $node->{args}{$arg_name};
159
150
100
439
push @errors, "unknown argument '$arg_name'" unless $macro{$macro_name}{args}{$arg_name};
160
150
100
428
next if @errors; #skip work if any errors have been seen, but keep parsing to find more errors
161
95
50
215
$arg_value =~ s/^\[|\]$//g if $arg_value =~ /^(\[(?:(?:(?-1)*?)|[^\[\]\\]+|\\.|.)*?\])$/;
162
95
150
$arg_value =~ s/\\(.)/$1/g;
163
95
575
$node->{args}{$arg_name} = $arg_value;
164
}
165
60
100
187
if (!@errors) {
166
35
81
_macro_invocation_preprocess($macro_name, $node);
167
}
168
}
169
170
65
100
173
if (@errors) {
171
30
116
$node = {type=>"error", context=>"building inline macro '$macro_name' invocation", errors=>\@errors};
172
30
86
push @error, $node;
173
}
174
65
404
push @output, $node;
175
} elsif ($bracketcontents =~ /^raw\s+(\w+)\s+(.+?)\s*$/) {
176
5
18
my ($format, $content) = ($1, $2);
177
5
14
$content =~ s/\\(.)/$1/g;
178
5
52
push @output, {type=>"raw", format=>$format, content=>$content};
179
} elsif ($bracketcontents =~ /^img\s+(\S+)(?:\s+(.+?))?$/) {
180
25
46
my $url = $1;
181
25
50
86
my $alt = $2 // "";
182
25
39
$url =~ s/\\(.)/$1/g;
183
25
86
$alt =~ s/\\(.)/$1/g;
184
25
220
push @output, {type=>"img", src=>$url, alt=>$alt};
185
} elsif ($bracketcontents =~ /^(?:link\s+)?(\S+)(?:\s+(.+?))?$/) {
186
40
74
my $url = $1;
187
40
66
my $content = $2;
188
40
103
$url =~ s/\\(.)/$1/g;
189
40
100
202
push @output, {type=>"link", url=>$url, content=>(defined $content ? _nimble_parse_inline($content) : [{type=>"text", text=>$url}])};
190
} else {
191
10
18
$append_text->('[');
192
10
14
push @output, @{_nimble_parse_inline($bracketcontents)};
10
21
193
10
21
$append_text->(']');
194
}
195
} elsif (defined $char) {
196
515
819
$append_text->($char);
197
} else {
198
0
0
die "unreachable: _nimble_parse_inline matched an inline pattern but failed to determine which rule should handle it: " . substr($text, $-[0], $+[0] - $-[0]) . "\n";
199
}
200
}
201
202
1155
7437
return \@output;
203
5
1018
};
204
205
local *_nimble_parse = sub {
206
595
50
33
595
2497
croak "_nimble_parse takes 1-2 arguments but got ".(scalar @_) if @_ < 1 || @_ > 2;
207
595
50
66
1427
croak "_nimble_parse expects an arrayref for its second argument" if @_==2 && !ref($_[1]) eq 'ARRAY';
208
209
595
610
my @lines = @{$_[0]};
595
1524
210
211
595
100
1134
my @extra_rules = $_[1] ? @{$_[1]} : ();
35
62
212
213
595
1093
my $section_depth = 0;
214
215
# parse block-level syntaxes
216
595
604
my ($i, @output, $tail, $prev_i);
217
595
1338
OUTER_LINE: for ($i=0; $i<@lines;) {
218
2095
50
66
7227
die "_nimble_parse: no rule advanced line index for lines[$i]" if defined $prev_i && $i == $prev_i;
219
2095
2362
$prev_i = $i;
220
221
2095
3372
foreach my $extra_rule (@extra_rules) {
222
95
127
my ($extra_re, $extra_fn) = @$extra_rule;
223
95
94
my @extra_matches;
224
95
100
593
if (@extra_matches = $lines[$i] =~ $extra_re) {
225
30
76
push @output, $extra_fn->(\@extra_matches, \$i, \@lines);
226
30
130
next OUTER_LINE;
227
}
228
}
229
230
2065
100
24999
if (0) {
100
100
100
100
100
100
100
100
100
100
100
100
231
# metadata
232
0
0
} elsif (my ($meta_key, $meta_value) = $lines[$i] =~ /^\@(\w+)(?:\s+(.*?))?\s*$/) {
233
5
50
25
$meta{$meta_key} = $meta_value // "";
234
5
17
$i++;
235
# codeblock
236
} elsif (my ($codeblock_marker, $codeblock_lang) = $lines[$i] =~ /^(\`{3,})(?:\s*(\w+))?\s*$/) {
237
15
54
my $node = {type=>"codeblock", lang=>$codeblock_lang, lines=>[]};
238
15
19
$i++;
239
240
15
60
for (; $i<@lines; $i++) {
241
70
100
351
if ($lines[$i] =~ /^\Q$codeblock_marker\E\s*$/) {
242
15
18
$i++; last;
15
25
243
} else {
244
55
58
push @{$node->{lines}}, $lines[$i];
55
203
245
}
246
}
247
15
48
push @output, $node;
248
# ul
249
} elsif (my ($ul_indent, $ul_content) = $lines[$i] =~ /^\*( +)(.*?)$/) {
250
105
362
my $node = {type=>"ul", list=>[[$ul_content]]};
251
105
146
$i++;
252
105
239
for (; $i<@lines; $i++) {
253
340
100
2213
if (($tail) = $lines[$i] =~ /^ $ul_indent(.*?)$/) {
100
254
150
175
push @{$node->{list}[-1]}, $tail;
150
577
255
} elsif (($ul_indent, $ul_content) = $lines[$i] =~ /^\*( +)(.*?)$/) {
256
140
164
push @{$node->{list}}, [$ul_content];
140
650
257
} else {
258
50
88
last;
259
}
260
}
261
105
122
$node->{list} = [ map { _nimble_parse($_) } @{$node->{list}} ];
245
703
105
179
262
105
512
push @output, $node;
263
# ol
264
} elsif (my ($ol_indent, $ol_value, $ol_content) = $lines[$i] =~ /^((?:(\d+)|\#)\. +)(.*?)$/) {
265
25
134
my $node = {type=>"ol", content=>[{type=>"li", value=>$ol_value, content=>[$ol_content]}]};
266
25
41
$i++;
267
25
42
$ol_indent = " " x length $ol_indent;
268
25
63
for (; $i<@lines; $i++) {
269
70
100
603
if (($tail) = $lines[$i] =~ /^$ol_indent(.*?)$/) {
100
270
35
42
push @{$node->{content}[-1]{content}}, $tail;
35
161
271
} elsif (($ol_value, $ol_content) = $lines[$i] =~ /^(?:(\d+)|\#)\. (.*?)$/) {
272
30
38
push @{$node->{content}}, {type=>"li", value=>$ol_value, content=>[$ol_content]};
30
124
273
30
100
156
$ol_indent = " " x ((defined $ol_value ? length $ol_value : 1) + 2);
274
} else {
275
5
11
last;
276
}
277
}
278
25
30
$_->{content} = _nimble_parse($_->{content}) for @{$node->{content}};
25
78
279
25
107
push @output, $node;
280
# dl
281
} elsif (my ($dl_indent, $dl_content) = $lines[$i] =~ /^\?( +)(.*?)$/) {
282
40
175
my $node = {type=>"dl", content=>[{type=>"dt",content=>[$dl_content]}]};
283
40
54
$i++;
284
40
90
for (; $i<@lines; $i++) {
285
180
100
1228
if (($tail) = $lines[$i] =~ /^ $dl_indent(.*?)$/) {
100
100
286
50
54
push @{$node->{content}[-1]{content}}, $tail;
50
195
287
} elsif (($dl_indent, $dl_content) = $lines[$i] =~ /^\=( +)(.*?)$/) {
288
75
81
push @{$node->{content}}, {type=>"dd",content=>[$dl_content]};
75
435
289
} elsif (($dl_indent, $dl_content) = $lines[$i] =~ /^\?( +)(.*?)$/) {
290
35
39
push @{$node->{content}}, {type=>"dt",content=>[$dl_content]};
35
231
291
} else {
292
20
38
last;
293
}
294
}
295
40
48
$_->{content} = _nimble_parse($_->{content}) for @{$node->{content}};
40
130
296
40
164
push @output, $node;
297
# figure / figcaption
298
} elsif (my ($fig_indent, $fig_content) = $lines[$i] =~ /^\%( +)(.*?)$/) {
299
35
111
my $node = {type=>"figure", content=>[$fig_content]};
300
35
43
$i++;
301
302
35
77
for (; $i<@lines; $i++) {
303
135
100
733
if (($tail) = $lines[$i] =~ /^ $fig_indent(.*?)$/) {
304
100
105
push @{$node->{content}}, $tail;
100
391
305
} else {
306
35
56
last;
307
}
308
}
309
310
$node->{content} = _nimble_parse($node->{content}, [
311
[qr/^\=( +)(.*?)$/ => sub {
312
30
45
my ($matches, $i, $lines) = @_;
313
30
53
my ($figcap_indent, $figcap_content) = @$matches;
314
30
88
my $node = {type=>"figcaption", content=>[$figcap_content]};
315
30
35
$$i++;
316
317
30
29
my $tail;
318
30
68
for (; $$i<@$lines; $$i++) {
319
10
50
125
if (($tail) = $lines->[$$i] =~ /^ $figcap_indent(.*?)$/) {
320
0
0
push @{$node->{content}}, $tail;
0
0
321
} else {
322
10
19
last;
323
}
324
}
325
326
30
61
$node->{content} = _nimble_parse($node->{content});
327
30
75
return $node;
328
35
256
}],
329
]);
330
331
35
366
push @output, $node;
332
# h#
333
} elsif (my ($h_indent, $h_depth, $h_content) = $lines[$i] =~ /^(\!([123456]?) +)(.*?)$/) {
334
10
50
65
my $node = {type=>"h", content=>[$h_content], depth=>$h_depth||1};
335
10
12
$i++;
336
337
10
22
$h_indent = " " x length $h_indent;
338
10
27
for (; $i<@lines; $i++) {
339
20
100
197
if (($tail) = $lines[$i] =~ /^$h_indent(.*?)$/) {
340
10
11
push @{$node->{content}}, $tail;
10
42
341
} else {
342
10
15
last;
343
}
344
}
345
346
10
16
$node->{content} = _nimble_parse_inline(join(" ", @{$node->{content}}));
10
37
347
10
45
push @output, $node;
348
# blockquote
349
} elsif (my ($bq_indent, $bq_content) = $lines[$i] =~ /^\"( +)(.*?)$/) {
350
20
74
my $node = {type=>"blockquote", quote=>[$bq_content]};
351
20
27
$i++;
352
353
20
26
my $section = 'quote';
354
20
48
for (; $i<@lines; $i++) {
355
90
100
100
607
if (($bq_content) = $lines[$i] =~ /^ $bq_indent(.*?)$/) {
100
356
65
78
push @{$node->{$section}}, $bq_content;
65
256
357
} elsif ($section eq 'quote' && (($bq_indent, $bq_content) = $lines[$i] =~ /^\-( +)(.*?)$/)) {
358
5
10
$section = 'cite';
359
5
25
$node->{$section} = [$bq_content];
360
} else {
361
20
35
last;
362
}
363
}
364
365
20
30
$node->{$_} = _nimble_parse($node->{$_}) for grep { defined $node->{$_} } qw(quote cite);
40
119
366
20
82
push @output, $node;
367
# raw data
368
} elsif (my ($raw_format, $raw_inline) = $lines[$i] =~ /^\#raw\s+(\w+)(?:\s+(.*?))?\s*$/) {
369
10
53
my $node = {type=>"raw", format=>$raw_format, content=>[]};
370
10
16
$i++;
371
372
10
100
19
if (defined $raw_inline) {
373
5
10
$node->{content} = $raw_inline;
374
} else {
375
5
17
for (; $i<@lines; $i++) {
376
35
100
179
if (my ($raw_line) = $lines[$i] =~ /^ +(.*?)\s*$/) {
377
30
35
push @{$node->{content}}, $raw_line;
30
107
378
} else {
379
5
7
last;
380
}
381
}
382
5
8
$node->{content} = join("\n", @{$node->{content}});
5
17
383
}
384
385
10
44
push @output, $node;
386
# macro definition
387
} elsif (my ($macro_decl) = $lines[$i] =~ /^\#macro\s+(\w+)\s*$/) {
388
55
164
my $macro = {args=>{}, results=>{}};
389
55
69
$i++;
390
391
55
58
my @errors;
392
55
100
131
push @errors, "a macro named '$macro_decl' already exists" if $macro{$macro_decl};
393
# we're building $macro as a temp variable, so no need to skip altering logic based on @errors
394
395
55
54
my ($block_type, $block_name, $block_value, $block_indent, $block_internal_indent);
396
55
4846
for (; $i<@lines; $i++) {
397
240
100
100
2895
if (defined $block_type && (my ($block_line) = $lines[$i] =~ /^$block_indent( +.*?)\s*$/)) {
100
100
398
45
100
99
if (!defined $block_internal_indent) {
399
25
50
$block_line =~ /^( +)/;
400
25
48
$block_internal_indent = $1;
401
}
402
45
165
$block_line =~ s/^$block_internal_indent//;
403
45
63
push @{$macro->{$block_type}{$block_name}{$block_value}}, $block_line;
45
214
404
} elsif (my ($arg_indent, $arg_name, $arg_type, $arg_inline_default) = $lines[$i] =~ /^( +)\#arg\s+(\w+)\s+(\w+)(?:\s+(\S.*?))?\s*$/) {
405
100
100
250
push @errors, "duplicate definition for argument '$arg_name'" if $macro->{args}{$arg_name};
406
100
100
346
push @errors, "invalid type '$arg_type' for argument '$arg_name'" unless $arg_type =~ /^(?:nimble|raw)$/;
407
100
547
$macro->{args}{$arg_name} = {type=>$arg_type, default=>[]};
408
100
100
178
if (defined $arg_inline_default) {
409
15
36
$macro->{args}{$arg_name}{default} = $arg_inline_default;
410
15
57
$block_type = undef;
411
} else {
412
85
521
($block_type, $block_name, $block_value, $block_indent, $block_internal_indent) = ("args", $arg_name, "default", $arg_indent, undef);
413
}
414
} elsif (my ($result_indent, $result_format, $result_inline_default) = $lines[$i] =~ /^( +)\#result\s+(\w+)(?:\s+(\S.*?))?\s*$/) {
415
40
100
107
push @errors, "duplicate definition of '$result_format'-format result" if $macro->{results}{$result_format};
416
40
123
$macro->{results}{$result_format} = {output=>[]};
417
40
100
84
if (defined $result_inline_default) {
418
20
38
$macro->{results}{$result_format}{output} = $result_inline_default;
419
20
67
$block_type = undef;
420
} else {
421
20
78
($block_type, $block_name, $block_value, $block_indent, $block_internal_indent) = ("results", $result_format, "output", $result_indent, undef);
422
}
423
} else {
424
55
90
last;
425
}
426
}
427
428
55
100
101
if (!@errors) {
429
30
30
foreach my $arg_name (keys %{$macro->{args}}) {
30
97
430
70
204
$macro->{args}{$arg_name}{default} = _macro_arg_preprocess($macro->{args}{$arg_name}{type}, $macro->{args}{$arg_name}{default});
431
}
432
30
53
foreach my $result_format (keys %{$macro->{results}}) {
30
83
433
30
100
93
$macro->{results}{$result_format}{output} = join("\n", @{$macro->{results}{$result_format}{output}}) if ref $macro->{results}{$result_format}{output};
20
102
434
}
435
436
30
166
$macro{$macro_decl} = $macro;
437
} else {
438
25
123
my $node = {type=>"error", context=>"defining macro '$macro_decl'", errors=>\@errors};
439
25
47
push @error, $node;
440
25
127
push @output, $node;
441
}
442
# macro usage
443
} elsif (my ($macro_name) = $lines[$i] =~ /^\$\s*(\w+)\s*$/) {
444
45
154
my $node = {type=>"macro", macro=>$macro_name, args=>{}};
445
45
55
$i++;
446
447
45
50
my @errors;
448
45
100
114
push @errors, "no macro by that name is defined" unless $macro{$macro_name};
449
450
45
45
my ($block_indent, $arg_block_name, $block_internal_indent);
451
45
96
for (; $i<@lines; $i++) {
452
190
100
100
1278
if (defined $arg_block_name && (my ($block_line) = $lines[$i] =~ /^$block_indent( +.*?)\s*$/)) {
100
453
25
100
53
if (!defined $block_internal_indent) {
454
10
21
$block_line =~ /^( +)/;
455
10
23
$block_internal_indent = $1;
456
}
457
25
99
$block_line =~ s/^$block_internal_indent//;
458
25
50
59
if (!@errors) {
459
25
27
push @{$node->{args}{$arg_block_name}}, $block_line;
25
124
460
}
461
} elsif (my ($arg_indent, $arg_name, $arg_inline_default) = $lines[$i] =~ /^( +)(\w+)\:(?:\s+(\S.*?))?\s*$/) {
462
120
100
330
push @errors, "duplicate argument '$arg_name'" if $node->{args}{$arg_name};
463
120
100
338
push @errors, "unknown argument '$arg_name'" unless $macro{$macro_name}{args}{$arg_name};
464
120
100
237
if (!@errors) {
465
65
148
$node->{args}{$arg_name} = [];
466
}
467
120
100
221
if (defined $arg_inline_default) {
468
110
100
184
if (!@errors) {
469
55
111
$node->{args}{$arg_name} = $arg_inline_default;
470
}
471
110
356
$arg_block_name = undef;
472
} else {
473
10
39
($arg_block_name, $block_indent, $block_internal_indent) = ($arg_name, $arg_indent, undef);
474
}
475
} else {
476
45
71
last;
477
}
478
}
479
480
45
100
87
if (!@errors) {
481
15
30
_macro_invocation_preprocess($macro_name, $node);
482
}
483
484
45
100
111
if (@errors) {
485
30
120
$node = {type=>"error", context=>"building block macro '$macro_name' invocation", errors=>\@errors};
486
30
92
push @error, $node;
487
}
488
45
191
push @output, $node;
489
# space
490
} elsif ($lines[$i] =~ /^\s*$/) {
491
655
100
100
3747
push @output, {type=>"space"} unless @output && $output[-1]{type} eq 'space';
492
655
2205
$i++;
493
# section start/end markers; below 'space' so at least one half of the regex matches
494
} elsif (my ($section_end, $header_indent, $header_content) = $lines[$i] =~ /^(\}*)(?:(\{ +)(.*?))?$/) {
495
55
64
$i++;
496
55
50
106
if (defined $section_end) {
497
55
71
my $section_end_num = length($section_end);
498
55
50
104
if ($section_end_num > $section_depth) {
499
0
0
my $node = {type=>"error", context=>"processing section end makers", errors=>["tried to end a section which wasn't open"]};
500
0
0
push @error, $node;
501
0
0
push @output, $node;
502
0
0
$section_end_num = $section_depth;
503
}
504
55
165
push @output, {type=>"section_end"} for 1..$section_end_num;
505
55
81
$section_depth -= $section_end_num;
506
}
507
55
100
164
if (defined $header_content) {
508
30
96
my $node = {type=>"section_start", content=>[$header_content]};
509
510
30
50
84
$header_indent = " " x (length($header_indent) + (defined $section_end ? length($section_end) : 0));
511
30
88
for (; $i<@lines; $i++) {
512
55
100
370
if (($tail) = $lines[$i] =~ /^$header_indent(.*?)$/) {
513
25
27
push @{$node->{content}}, $tail;
25
108
514
} else {
515
30
48
last;
516
}
517
}
518
519
30
67
$node->{content} = _nimble_parse($node->{content});
520
30
82
push @output, $node;
521
30
5752
$section_depth++;
522
}
523
# paragraph
524
} else {
525
990
1314
my $line = $lines[$i];
526
990
1573
$line =~ s/^\s+//; $line =~ s/\s+$//;
990
1973
527
990
100
100
3421
if (@output && $output[-1]{type} eq 'paragraph') {
528
105
241
$output[-1]{content} .= " $line";
529
} else {
530
885
2532
push @output, {type=>"paragraph", content=>$line};
531
}
532
990
3682
$i++;
533
}
534
}
535
536
# clean up remaining sections
537
595
1066
push @output, {type=>"section_end"} for 1..$section_depth;
538
539
# drop spaces; they were just to delimit paragraphs
540
595
755
@output = grep {$_->{type} ne 'space'} @output;
1930
4419
541
542
# parse text which has been collected into paragraphs
543
595
808
$_->{content} = _nimble_parse_inline($_->{content}) for grep {$_->{type} eq 'paragraph'} @output;
1305
3141
544
545
595
2564
return \@output;
546
5
124
};
547
548
5
11
do {
549
5
21
my $lines = _nimble_makelines($_[0]);
550
5
19
my $tree = _nimble_parse($lines);
551
5
50
1257
return {tree=>$tree, meta=>\%meta, macro=>\%macro, (@error ? (error=>\@error) : ())};
552
};
553
}
554
555
sub _renderer_html {
556
4
50
4
13
croak "Text::Nimble::_renderer_html takes one argument but got ".(scalar @_) unless @_ == 1;
557
4
50
33
49
croak "Text::Nimble::_renderer_html got an argument that doesn't look like the result of Text::Nimble::parse" unless ref $_[0] eq 'HASH' && $_[0]{tree} && $_[0]{macro};
33
558
559
4
7
my %macro = %{$_[0]{macro}};
4
33
560
561
4
23
my %xmlenc = (
562
'&' => '&',
563
'"' => '"',
564
'<' => '<',
565
'>' => '>',
566
);
567
local *_xmlenc = sub {
568
1608
1608
2085
my $s = $_[0];
569
1608
2539
$s =~ s/([\&\"\<\>])/$xmlenc{$1}/g;
570
1608
8534
return $s;
571
4
21
};
572
573
local *_urlenc = sub {
574
16
16
20
my $s = $_[0];
575
16
39
$s =~ s/([^a-zA-Z0-9\ ])/sprintf("%%%02X",ord($1))/ge;
4
38
576
16
32
$s =~ s/\ /\+/g;
577
16
56
return $s;
578
4
16
};
579
580
4
44
my %macro_filters = (
581
xmlenc => \&_xmlenc,
582
urlenc => \&_urlenc,
583
);
584
585
local *_macro_var_interpolate = sub {
586
100
100
202
my ($macro, $arg_name, $filters) = @_;
587
100
100
293
my @filters = defined $filters ? split(/\|/, $filters) : ();
588
100
137
my @unknown_filters = grep {!$macro_filters{$_}} @filters;
104
242
589
100
50
206
return "[nimble macro error: unknown argument filters(s): " . join(", ", @unknown_filters) . "]" if @unknown_filters;
590
100
193
my $value = $macro->{args}{$arg_name};
591
100
224
$value = $macro_filters{$_}($value) for @filters;
592
100
450
return $value;
593
4
19
};
594
595
local *_render_html = sub {
596
3876
3876
4473
my $node = shift;
597
598
# handle a list of nodes, especially the root level of Text::Nimble::parse
599
3876
100
8115
if (ref $node eq 'ARRAY') {
600
# a lone paragraph should just be plain text instead
601
1420
100
100
5721
if (@$node == 1 && $node->[0]{type} eq 'paragraph') {
602
324
663
return _render_html($node->[0]{content});
603
} else {
604
1096
50
2620
return join("", map { _render_html($_) } @$node) if ref $node eq 'ARRAY';
2456
4325
605
}
606
}
607
608
2456
3633
my $type = $node->{type};
609
2456
100
18033
if (0) {}
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
100
50
610
0
0
elsif ($type eq 'paragraph' ) { return "" ._render_html($node->{content})."
" }
360
670
611
32
64
elsif ($type eq 'dl' ) { return "" ._render_html($node->{content})." " }
612
60
118
elsif ($type eq 'dt' ) { return "" ._render_html($node->{content})." " }
613
60
123
elsif ($type eq 'dd' ) { return "" ._render_html($node->{content})." " }
614
80
171
elsif ($type eq 'emphasis' ) { return "" ._render_html($node->{content})." " }
615
20
45
elsif ($type eq 'ol' ) { return "" ._render_html($node->{content})." " }
616
28
54
elsif ($type eq 'figure' ) { return ""._render_html($node->{content})." " }
617
24
50
elsif ($type eq 'figcaption' ) { return ""._render_html($node->{content})." " }
618
24
78
elsif ($type eq 'h' ) { return "{depth}>"._render_html($node->{content})." {depth}\n>" }
619
84
191
elsif ($type eq 'strong' ) { return ""._render_html($node->{content})." " }
620
32
63
elsif ($type eq 'link' ) { return "{url})."\">"._render_html($node->{content})." " }
621
20
40
elsif ($type eq 'img' ) { return " {src})."\" alt=\""._xmlenc($node->{alt})."\"/>" }
622
44
100
154
elsif ($type eq 'li' ) { return "{value}?" value=\"$node->{value}\"":"").">"._render_html($node->{content})." " }
623
56
105
elsif ($type eq 'code' ) { return ""._xmlenc($node->{text})."
" }
624
12
18
elsif ($type eq 'codeblock' ) { return "".join("\n", map{_xmlenc($_)} @{$node->{lines}})." " }
44
66
12
29
625
1184
2231
elsif ($type eq 'text' ) { return _xmlenc($node->{text}) }
626
72
199
elsif ($type eq 'entity' ) { return $node->{html} }
627
80
100
elsif ($type eq 'ul' ) { return "".join("", map{""._render_html($_)." "} @{$node->{list}})." " }
188
356
80
146
628
12
50
45
elsif ($type eq 'raw' ) { return $node->{format} eq 'html' ? $node->{content} : '' }
629
68
133
elsif ($type eq 'error' ) { return "Nimble error while "._xmlenc($node->{context}).": ".join("; ", map{_xmlenc($_)} @{$node->{errors}})." " }
96
144
68
133
630
elsif ($type eq 'blockquote' ) {
631
16
36
my $bq_html = ""._render_html($node->{quote})." ";
632
16
100
56
$bq_html = "$bq_html"._render_html($node->{cite})." " if $node->{cite};
633
16
42
return $bq_html;
634
}
635
elsif ($type eq 'section_start') {
636
24
60
return "
637
. (@{$node->{content}}
638
? "
639
16
107
@{$node->{content}} == 1 && $node->{content}[0]{type} eq 'paragraph'
640
24
100
66
31
? [{%{$node->{content}[0]}, type=>"h", depth=>1}]
50
641
: $node->{content}
642
).""
643
: ""
644
)
645
}
646
24
61
elsif ($type eq 'section_end') { return "" }
647
elsif ($type eq 'macro') {
648
40
50
144
return "" unless $macro{$node->{macro}}{results}{html};
649
650
40
45
$node->{args}{$_} = _render_html($node->{args}{$_}) for grep {ref $node->{args}{$_}} keys %{$node->{args}};
84
223
40
121
651
652
40
136
my $output = $macro{$node->{macro}}{results}{html}{output};
653
40
194
$output =~ s/\{\{(\w+)(?:\|(\w+(?:\|\w+)*))?\}\}/_macro_var_interpolate($node, $1, $2)/ge;
100
183
654
40
179
return $output;
655
}
656
4
115
};
657
658
4
8
do {
659
4
6
my $parse = $_[0];
660
4
12
return _render_html($parse->{tree});
661
};
662
}
663
664
1; # End of Text::Nimble
665
666
__END__