File Coverage

blib/lib/Pod/IkiWiki.pm
Criterion Covered Total %
statement 21 197 10.6
branch 0 86 0.0
condition 0 2 0.0
subroutine 7 32 21.8
pod 6 6 100.0
total 34 323 10.5


line stmt bran cond sub pod time code
1             package Pod::IkiWiki;
2 1     1   26083 use strict;
  1         3  
  1         35  
3 1     1   7 use warnings;
  1         2  
  1         27  
4 1     1   6 use Carp;
  1         6  
  1         248  
5 1     1   1218 use utf8;
  1         12  
  1         7  
6              
7 1     1   51 use base qw(Pod::Parser);
  1         2  
  1         114  
8              
9 1     1   1320 use Pod::ParseUtils;
  1         3736  
  1         44  
10              
11             use constant {
12 1         3032 YES => 1,
13             NOT => 0,
14 1     1   10 };
  1         3  
15              
16             our $VERSION = '0.0.4';
17              
18             my %search_in_level1 = (
19             'author' => qr{AUTHOR}xmsi,
20             'title' => qr{NAME}xmsi,
21             'license' => undef,
22             'copyright' => undef,
23             'description' => undef,
24             );
25              
26             sub new {
27 0     0 1   my ($class,@params) = @_;
28 0           my $self = $class->SUPER::new( );
29              
30 0 0         if ($self) {
31             # add private data as a hash ...
32 0           $self->_private( @params );
33             }
34              
35 0           return $self;
36             }
37              
38             sub _private {
39 0     0     my $self = shift;
40 0           my %params = @_;
41              
42 0 0         if (not exists $self->{_PACKAGE_}) {
43             # initialize the internal structure:
44 0           $self->{_PACKAGE_} = {
45             Paragraphs => [], # Paragraphs
46             Globals => { # updatable values
47             Indent => 4, # Spaces by indent level
48             Metadata => YES, # Metadata scanning
49             Wikilinks => undef, # Build wikilinks (undef) or insert into a base
50             Formatters => { # Include special paragraph for these
51             'ikiwiki' => 1, # formatters
52             },
53             },
54             Meta => { # Meta directives: search only in level
55             # one headers
56             Title => undef, # Page title
57             Author => undef, # Page author
58             License => undef,
59             Copyright => undef,
60             Description => undef,
61             },
62             LinkParser => undef, # Reference to parser links object
63             ListCounter => 0, # Lists counter
64             ListType => [], # list type array: bullets, numbered or plain
65             ActiveCommand => undef, # what is the active command ?
66             IgnoreParagraph => NOT, # ignore paragraphs ?
67             Searching => undef, # what are we search ? (title, author, undef ...)
68             };
69              
70             #
71             # Analyze the parameters
72             #
73            
74             # Blank spaces for every indent level ...
75 0 0         if (defined $params{'indent'}) {
76 0           $self->{_PACKAGE_}->{Globals}->{Indent} = $params{indent};
77             }
78              
79             # Don't scanning metadata ...
80 0 0         if (defined $params{no_metadata}) {
81 0           $self->{_PACKAGE_}->{Globals}->{Metadata} = NOT;
82             }
83              
84             # Don't build ikiwiki wikilinks ...
85 0 0         if (defined $params{links_base}) {
86 0           $self->{_PACKAGE_}->{Globals}->{Wikilinks} = $params{links_base};
87             }
88              
89             # Included special formatters ...
90 0 0         if (defined $params{formatters}) {
91 0           my @fmt_list = split(m{, }, $params{formatters});
92              
93 0           $self->{_PACKAGE}->{Globals}->{Formatters}->{$_} = 1 foreach @fmt_list;
94             }
95             }
96              
97 0           return $self->{_PACKAGE_};
98             }
99              
100             sub _ignore_next_paragraph {
101 0     0     my $parser = shift;
102 0           my $switch = shift;
103            
104 0           $parser->_private()->{IgnoreParagraph} = $switch;
105              
106 0           return $parser;
107             }
108              
109             sub _process_paragraph {
110 0     0     my $parser = shift;
111              
112 0           return $parser->_private()->{IgnoreParagraph} == NOT;
113             }
114              
115             sub dump_as_ikiwiki {
116 0     0 1   my $parser = shift;
117 0           my $data = $parser->_private();
118 0           my @ikiwiki = ();
119              
120 0           push(@ikiwiki, $parser->_build_mdwn_head());
121              
122 0           foreach my $pair (@{ $data->{Paragraphs} }) {
  0            
123 0           push(@ikiwiki, $parser->_indent_text( $pair->[0], $pair->[1] ));
124             }
125              
126             # add the necesary lines for every logical paragraph
127 0           return join("\n" x 2, @ikiwiki );
128             }
129              
130             #
131             # This function adds meta directives for ikiwiki
132             #
133              
134             sub _build_mdwn_head {
135 0     0     my $parser = shift;
136 0           my $data = $parser->_private();
137 0           my @headerlines = ();
138              
139             # Add meta directives with content
140 0           foreach my $meta_name (keys %{ $data->{Meta} }) {
  0            
141 0 0         if (defined $data->{Meta}->{$meta_name}) {
142 0           push( @headerlines, sprintf '[[meta %s="%s"]]',
143             lc $meta_name,
144             $data->{Meta}->{$meta_name}
145             );
146             }
147             }
148              
149 0           return @headerlines;
150             }
151              
152             sub _save {
153 0     0     my $parser = shift;
154 0           my $text = shift;
155 0   0       my $indent_level = shift || 0;
156 0           my $data = $parser->_private();
157              
158 0           push @{ $data->{Paragraphs} }, [ $indent_level, $text ];
  0            
159              
160 0           return;
161             }
162              
163             sub _indent_text {
164 0     0     my ($parser, $indent_level, $text) = @_;
165 0           my $data = $parser->_private();
166 0           my $indent = '';
167              
168 0 0         if ($indent_level > 0) {
169 0           $indent = ' ' x ($indent_level * $data->{Globals}->{Indent});
170             }
171              
172 0           return sprintf '%s%s', $indent, $text;
173             }
174              
175             sub _clean_text {
176 0     0     my $parser = shift;
177 0           my $text = shift;
178 0           my @trimmed = grep { $_; } split(/\n/, $text);
  0            
179              
180 0 0         return wantarray ? @trimmed : join("\n", @trimmed);
181             }
182              
183             sub command {
184 0     0 1   my ($parser, $command, $paragraph, $line_num) = @_;
185 0           my $data = $parser->_private();
186              
187             # cleaning the text
188 0           $paragraph = $parser->_clean_text( $paragraph );
189              
190             # saving the command name
191 0           $data->{ActiveCommand} = $command;
192              
193             # is it a header ?
194 0 0         if ($command =~ m{head(\d)}xms) {
    0          
    0          
195 0           my $level = $1;
196              
197             # the headers never are indented
198 0           $parser->_save( sprintf '%s %s', '#' x $level, $paragraph );
199              
200             # extract the next paragraph as metadata ?
201 0 0         if ($level == 1) {
202 0           $data->{Searching} = undef;
203              
204 0 0         if ($data->{Globals}->{Metadata} == YES) {
205 0           foreach my $search_name (keys %search_in_level1) {
206 0 0         next if not defined $search_in_level1{$search_name};
207              
208 0 0         if ($paragraph =~ $search_in_level1{$search_name}) {
209 0           $data->{Searching} = $search_name;
210 0           last;
211             }
212             }
213             }
214             }
215              
216 0           $parser->_ignore_next_paragraph(NOT);
217             }
218             # is a list command ?
219             elsif ($command =~ m{over|back|item}xmsi) {
220 0           $parser->_list_command( $command, $paragraph, $line_num );
221             }
222             # is a special formatter text ?
223             elsif ($command =~ m{begin|end|for}xms) {
224 0           $parser->_special_formatter( $command, $paragraph, $line_num );
225             }
226              
227             # ignore other commands
228 0           return;
229             }
230              
231             sub _list_command {
232 0     0     my $parser = shift;
233 0           my $command = shift;
234 0           my $paragraph = shift;
235 0           my $line_num = shift;
236 0           my $data = $parser->_private();
237              
238             # opening a list ?
239 0 0         if ($command =~ m{over}xms) {
    0          
    0          
240             # update indent level
241 0           $data->{ListCounter} ++;
242              
243             # closing a list ?
244             } elsif ($command =~ m{back}xms) {
245             # decrement indent level
246 0           $data->{ListCounter} --;
247              
248             }
249             elsif ($command =~ m{item}xms) {
250 0           my ($list_type, $paragraph) = $parser->_scan_list_type( $paragraph );
251              
252             # is this the first item viewed in the list ?
253 0 0         if (not defined $data->{ListType}->[ $data->{ListCounter} ]) {
254             # yes, take his type as the list type
255 0           $data->{ListType}->[ $data->{ListCounter} ] = $list_type;
256             }
257             else {
258             # no, take the list type instead
259 0           $list_type = $data->{ListType}->[ $data->{ListCounter} ];
260             }
261              
262             # interpolate, indent (with trick) and save the text
263 0           $parser->_save( sprintf ('%s %s', $list_type,
264             $parser->interpolate($paragraph, $line_num) ),
265             $data->{ListCounter} - 1);
266             }
267              
268 0           return;
269             }
270              
271             #
272             # This function parses a item list and extracts the list type and the item
273             # text.
274             #
275              
276             sub _scan_list_type {
277 0     0     my ($parser, $paragraph) = @_;
278 0           my $data = $parser->_private();
279 0           my $list_type = undef;
280 0           my $newparagraph = undef;
281              
282             # looking for a number and a period
283 0 0         if ($paragraph =~ m{^(\d+.)\s+(.+)}) {
    0          
284 0           $list_type = '1.';
285 0           $newparagraph = $2;
286             }
287             # looking for an asterisk or a minus sign
288             elsif ($paragraph =~ m{^([\*\-])\s+(.+)}) {
289 0           $list_type = '-';
290 0           $newparagraph = $2;
291             }
292             # select a default value
293             else {
294 0           $list_type = '-';
295 0           $newparagraph = $paragraph;
296             }
297              
298 0           return ($list_type, $newparagraph);
299             }
300              
301             sub _special_formatter {
302 0     0     my $parser = shift;
303 0           my $command = shift;
304 0           my $paragraph = shift;
305 0           my $line_num = shift;
306 0           my $data = $parser->_private();
307              
308 0 0         if ($command =~ m{begin}xmsi) {
    0          
    0          
309 0 0         if (exists $data->{Globals}->{Formatters}->{lc $paragraph}) {
310 0           $parser->_ignore_next_paragraph(NOT);
311             }
312             else {
313 0           $parser->_ignore_next_paragraph(YES);
314             }
315             }
316             elsif ($command =~ m{end}xmsi) {
317 0           $parser->_ignore_next_paragraph(NOT);
318             }
319             elsif ($command =~ m{for}xmsi) {
320 0 0         if ($paragraph =~ m{^(\w+)\s+(.+)$}xms) {
321 0 0         if (exists $data->{Globals}->{Formatters}->{lc $1}) {
322             # copy
323 0           $parser->_save( $parser->interpolate($2, $line_num) );
324             }
325             }
326             }
327              
328 0           return;
329             }
330              
331              
332             sub verbatim {
333 0     0 1   my ($parser, $paragraph, $line_num) = @_;
334 0           my $data = $parser->_private();
335              
336 0 0         if ($parser->_process_paragraph()) {
337 0           $parser->_save( $parser->_clean_text($paragraph),
338             $data->{ListCounter} + 1);
339             }
340             }
341              
342             sub textblock {
343 0     0 1   my ($parser, $paragraph, $line_num) = @_;
344 0           my $data = $parser->_private();
345              
346 0 0         if (not $parser->_process_paragraph()) {
347 0           return;
348             }
349              
350             # interpolate the paragraph for embebed sequences
351 0           $paragraph = $parser->interpolate( $paragraph, $line_num );
352              
353             # clean the empty lines
354 0           $paragraph = $parser->_clean_text( $paragraph );
355              
356             # searching ?
357 0 0         if ($data->{Searching}) {
358 0           $data->{Meta}->{ucfirst $data->{Searching}} = $paragraph;
359 0           $data->{Searching} = undef;
360             }
361              
362             # save the text
363 0           $parser->_save( $paragraph, $data->{ListCounter});
364             }
365              
366             sub interior_sequence {
367 0     0 1   my ($parser, $seq_command, $seq_argument, $pod_seq) = @_;
368 0           my $data = $parser->_private();
369             my %interiores = (
370 0     0     'I' => sub { return '_' . $_[1] . '_' }, # cursive
371 0     0     'B' => sub { return '__' . $_[1] . '__' }, # bold
372 0     0     'C' => sub { return '`' . $_[1] . '`' }, # monospace
373 0     0     'F' => sub { return '`' . $_[1] . '`' }, # system path
374 0     0     'S' => sub { return '`' . $_[1] . '`' }, # code
375             'E' => sub {
376 0     0     my ($seq, $charname) = @_;
377              
378 0 0         return '<' if $charname eq 'lt';
379 0 0         return '>' if $charname eq 'gt';
380 0 0         return '|' if $charname eq 'verbar';
381 0 0         return '/' if $charname eq 'sol';
382 0           return $charname;
383             },
384             'L' => sub {
385 0     0     $parser->_resolv_link( @_ ),
386             },
387 0           );
388              
389 0 0         if (exists $interiores{$seq_command}) {
390 0           my $code = $interiores{$seq_command};
391              
392 0           return $code->( $seq_command, $seq_argument, $pod_seq );
393             }
394             else {
395 0           return sprintf '%s<%s>', $seq_command, $seq_argument;
396             }
397             }
398              
399             sub _resolv_link {
400 0     0     my $parser = shift;
401 0           my ($cmd, $arg, $pod_seq) = @_;
402 0           my $data = $parser->_private();
403              
404 0 0         if (not defined $data->{LinkParser}) {
405 0           $data->{LinkParser} = Pod::Hyperlink->new( $arg );
406             }
407             else {
408 0           $data->{LinkParser}->parse( $arg );
409             }
410              
411             # if is a hyper link ...
412 0           my $type = $data->{LinkParser}->type();
413 0 0         if ($type eq 'hyperlink') {
    0          
414 0           return sprintf '<%s>', $data->{LinkParser}->node();
415             }
416             elsif ($type =~ 'page|section|item') {
417 0           return $parser->_build_page_link( $data->{LinkParser} );
418             }
419              
420 0           return;
421             }
422              
423             sub _build_page_link {
424 0     0     my $parser = shift;
425 0           my $link = shift;
426 0           my $data = $parser->_private();
427              
428 0 0         if (not defined $data->{Globals}->{Wikilinks}) {
429 0           my $wikilink = '[[';
430              
431 0 0         if ($link->alttext()) {
432 0           $wikilink .= $link->alttext() . '|';
433             }
434              
435 0 0         if ($link->page()) {
436 0           $wikilink .= $link->page();
437             }
438              
439 0 0         if ($link->node()) {
440 0           $wikilink .= '#' . $link->node();
441             }
442              
443 0           $wikilink .= ']]';
444              
445 0           return $wikilink;
446             }
447             else {
448 0           return sprintf ($data->{Globals}->{Wikilinks} . '%s', $link->page());
449             }
450              
451             }
452              
453             1;
454              
455             __END__