File Coverage

blib/lib/Pod/ROBODoc.pm
Criterion Covered Total %
statement 98 101 97.0
branch 26 34 76.4
condition 9 15 60.0
subroutine 12 12 100.0
pod 3 3 100.0
total 148 165 89.7


line stmt bran cond sub pod time code
1             package Pod::ROBODoc;
2              
3 8     8   1171143 use strict;
  8         28  
  8         392  
4 8     8   50 use warnings;
  8         14  
  8         1263  
5              
6             our $VERSION = '0.3';
7              
8 8     8   285 use Carp;
  8         23  
  8         720  
9 8     8   14442 use IO::File;
  8         74371  
  8         1331  
10 8     8   21225 use IO::String;
  8         32379  
  8         363  
11 8     8   9883 use Params::Validate qw( :all );
  8         192394  
  8         28142  
12              
13             my $EMPTY = q{};
14             my $RD_BEGIN_MARK = qr{
15             ^ [*]{4} # starts with 4 asterisks
16             i? # optional "internal" flag
17             ([a-z*]) # header type
18             [*] # ends with an asterisk
19             [ ]* # optional trailing whitespace
20             (.*) # optional item name
21             }ox;
22             my $RD_END_MARK = qr{ \A [*]{4} \s* \z }ox;
23             my %RD_HEADERS = (
24             c => 'Class',
25             d => 'Constant',
26             f => 'Function',
27             h => 'Module',
28             m => 'Method',
29             s => 'Structure',
30             t => 'Type',
31             u => 'Unit Test',
32             v => 'Variable',
33             q{*} => $EMPTY,
34             );
35              
36             my @RD_TAGS = (
37             'NAME', 'COPYRIGHT', 'SYNOPSIS', 'USAGE',
38             'FUNCTION', 'DESCRIPTION', 'PURPOSE', 'AUTHOR',
39             'CREATION DATE', 'MODIFICATION HISTORY', 'INPUTS',
40             'ARGUMENTS', 'OPTIONS', 'PARAMETERS', 'SWITCHES',
41             'OUTPUT', 'SIDE EFFECTS', 'RESULT', 'RETURN VALUE',
42             'EXAMPLE', 'NOTES', 'DIAGNOSTICS', 'WARNINGS',
43             'ERRORS', 'BUGS', 'TODO', 'IDEAS',
44             'PORTABILITY', 'SEE ALSO', 'METHODS', 'NEW METHODS',
45             'ATTRIBUTES', 'NEW ATTRIBUTES', 'TAGS', 'COMMANDS',
46             'DERIVED FROM', 'DERIVED BY', 'USES', 'CHILDREN',
47             'USED BY', 'PARENTS', 'SOURCE', 'LICENSE',
48             );
49             my $RD_TAG_STRING;
50             my $RD_TAG_REGEX;
51              
52             sub new
53             {
54 8     8 1 2605276 my $class = shift;
55 8         427 my %params = validate( @_, {
56             keepsource => { default => 0 },
57             skipblanks => { default => 0 },
58             customtags => { default => [], type => ARRAYREF },
59             });
60              
61 8         93 my $self = bless { %params }, $class;
62              
63 8         62 $self->_load_custom_tags();
64              
65 8         40 return $self;
66             }
67              
68             sub filter
69             {
70 7     7 1 505625 my $self = shift;
71 7         792 my %params = validate( @_, {
72             input => { default => undef, type => UNDEF | SCALAR },
73             output => { default => undef, type => UNDEF | SCALAR },
74             });
75              
76             ## Setup input file handle
77 5         39 my $in_fh;
78              
79 5 100       27 if ( $params{input} ) {
80 4 100       36 $in_fh = IO::File->new( $params{input}, '<' )
81             or croak "Can't open input file '$params{input}': $!";
82             }
83             else {
84 1         4 $in_fh = \*STDIN;
85             }
86              
87             ## Setup output file handle
88 4         561 my $out_fh;
89              
90 4 100       20 if ( $params{output} ) {
91 3 50       19 $out_fh = IO::File->new( $params{output}, '>' )
92             or croak "Can't open output file '$params{output}': $!";
93             }
94             else {
95 1         3 $out_fh = \*STDOUT;
96             }
97              
98 4         491 $self->_parse_robodoc( $in_fh );
99 4 100       9 $self->_write_pod ( $out_fh ) if @{ $self->{_parsed} };
  4         45  
100              
101 4 50       61 $in_fh ->close or carp "Can't close input file: $!";
102 4 50       229 $out_fh->close or carp "Can't close output file: $!";
103              
104 4         371082 return;
105             }
106              
107             sub convert
108             {
109 3     3 1 948 my $self = shift;
110 3         341 my ( $rd ) = validate_pos( @_, { type => SCALAR } );
111              
112 2         32 my $in_fh = IO::String->new( $rd );
113 2         109 my $out_fh = IO::String->new();
114              
115 2         65 $self->_parse_robodoc( $in_fh );
116 2 100       3 $self->_write_pod ( $out_fh ) if @{ $self->{_parsed} };
  2         13  
117              
118 2         3 return ${ $out_fh->string_ref() };
  2         9  
119             }
120              
121             sub _load_custom_tags
122             {
123 8     8   18 my ( $self ) = @_;
124              
125 8         20 push @RD_TAGS, grep { / [A-Z0-9\s_-]+ /x } @{ $self->{customtags} };
  2         11  
  8         388  
126              
127 8         100 $RD_TAG_STRING = join q{|}, @RD_TAGS;
128 8         2107 $RD_TAG_REGEX = qr{\A[ ]*($RD_TAG_STRING)[ ]*\z}o;
129              
130 8         93 return;
131             }
132              
133             sub _parse_robodoc
134             {
135 6     6   19 my $self = shift;
136 6         196 my ( $fh ) = validate_pos( @_, { type => HANDLE } );
137              
138 6         26 my @parsed;
139             my $inrobodoc;
140 0         0 my $insource;
141 0         0 my $tag;
142              
143 6         381 while ( $_ = $fh->getline() )
144             {
145 160         6272 chomp;
146 160         203 my $rawline = $_;
147              
148 160         1070 s/^[ ]*#//;
149 160 50 66     445 next if not $_ and $self->{skipblanks};
150              
151 160 100       662 $inrobodoc = 0 if /$RD_END_MARK/;
152              
153 160 100 100     1027 if ( $inrobodoc and /$RD_TAG_REGEX/ )
154             {
155 36         67 $tag = $1;
156 36         187 $insource = $tag eq 'SOURCE';
157              
158 36 50 33     92 next if $insource and not $self->{keepsource};
159              
160 36         39 push @{ $parsed[ -1 ]{ tags } }, { tag => $tag, text => [] };
  36         172  
161 36         786 next;
162             }
163              
164 124 100 66     537 if ( $inrobodoc and $tag )
165             {
166 104 50 33     218 next if $insource and not $self->{keepsource};
167              
168 104 50       111 push @{ $parsed[ -1 ]{ tags }->[ -1 ]->{ text } },
  104         579  
169             ( $insource ? $rawline : $_ );
170             }
171              
172 124 100       3762 if ( /$RD_BEGIN_MARK/ )
173             {
174 4         67 $inrobodoc = 1;
175 4         8 $tag = undef;
176              
177 4         120 push @parsed, { type => $RD_HEADERS{ $1 }, element => $2, tags => [] };
178             }
179             }
180              
181 6         248 $self->{_parsed} = \@parsed;
182 6         33 return;
183             }
184              
185             sub _write_pod
186             {
187 4     4   12 my $self = shift;
188 4         104 my ( $fh ) = validate_pos( @_, { type => HANDLE } );
189              
190 4         19 my @pod;
191              
192 4         25 push @pod, sprintf '# Generated by %s version %s', __PACKAGE__, $VERSION;
193 4         11 push @pod, '=pod';
194              
195 4         8 for my $doc ( @{ $self->{_parsed} } )
  4         19  
196             {
197 4         26 push @pod, $EMPTY, "=head1 $doc->{type} $doc->{element}", $EMPTY;
198              
199 4         9 for my $tag ( @{ $doc->{tags} } )
  4         15  
200             {
201 36 50       88 if ( $tag->{tag} eq 'SOURCE' ) {
202 0         0 push @pod, $EMPTY, '=cut', $EMPTY;
203             }
204             else {
205 36         108 push @pod, $EMPTY, "=head2 $tag->{tag}", $EMPTY;
206             }
207              
208 36         41 push @pod, $_ for @{ $tag->{text} };
  36         205  
209             }
210             }
211              
212 4         14 push @pod, $EMPTY, '=cut', $EMPTY;
213              
214 4         117 print $fh "$_\n" for @pod;
215 4         1115 return;
216             }
217              
218             1;
219              
220             __END__