File Coverage

blib/lib/Getopt/Long/DescriptivePod.pm
Criterion Covered Total %
statement 100 124 80.6
branch 29 44 65.9
condition 2 2 100.0
subroutine 13 14 92.8
pod 2 2 100.0
total 146 186 78.4


line stmt bran cond sub pod time code
1             package Getopt::Long::DescriptivePod; ## no critic (TidyCode)
2            
3 7     7   610935 use strict;
  7         39  
  7         184  
4 7     7   36 use warnings;
  7         11  
  7         377  
5            
6             our $VERSION = '0.05';
7            
8 7     7   37 use Carp qw(confess);
  7         17  
  7         345  
9 7     7   3390 use English qw(-no_match_vars $PROGRAM_NAME $OS_ERROR $INPUT_RECORD_SEPARATOR);
  7         20012  
  7         33  
10 7     7   2616 use Params::Validate qw(validate SCALAR SCALARREF CODEREF);
  7         23999  
  7         489  
11 7         63 use Sub::Exporter -setup => {
12             exports => [ qw( replace_pod trim_lines ) ],
13             groups => {
14             default => [ qw( replace_pod trim_lines ) ],
15             },
16 7     7   1509 };
  7         34036  
17            
18             sub _on_verbose {
19 6     6   16 my ($param_ref, $string) = @_;
20            
21 6 50       20 if ( $param_ref->{on_verbose} ) {
22 6         27 $param_ref->{on_verbose}->($string);
23             }
24            
25 6         3705 return;
26             }
27            
28             sub _close_data {
29             # after __END__ this handle is open
30 7     7   3368 no warnings qw(once); ## no critic (ProhibitNoWarnings)
  7         15  
  7         9030  
31            
32 5     5   14 return close ::DATA;
33             }
34            
35             sub _format_block {
36 5     5   11 my $block_ref = shift;
37            
38 5         10 for my $key ( keys %{$block_ref} ) {
  5         75  
39 15         31 VALUE: for my $value ( $block_ref->{$key} ) { # alias only
40 15 100       45 defined $value
41             or next VALUE;
42 7         78 $value =~ s{ \r\n | [\n\r] }{\n}xmsg; # compatible \n
43 7         88 $value =~ s{ \A \n* (.*?) \n* \z }{$1}xms; # trim
44 7 100       51 $value = [
    100          
45             ( $key eq 'after' ? q{} : () ),
46             ( split m{ \n }xms, $value ),
47             ( $key eq 'before' ? q{} : () ),
48             ];
49             }
50             }
51            
52 5         13 return;
53             }
54            
55             sub _read_file {
56 5     5   10 my $param_ref = shift;
57            
58 5 50       21 if ( ref $param_ref->{filename} ) {
59 5         10 return ${ $param_ref->{filename} };
  5         15  
60             }
61 0 0       0 if ( open my $file, '< :raw', $param_ref->{filename} ) {
62 0         0 local $INPUT_RECORD_SEPARATOR = ();
63 0         0 my $content = <$file>;
64 0         0 () = close $file;
65 0         0 return $content;
66             }
67 0         0 _verbose( $param_ref, "Can not open file $param_ref->{filename} $OS_ERROR" );
68            
69 0         0 return;
70             }
71            
72             sub _write_file {
73 3     3   7 my ($param_ref, $content) = @_;
74            
75 3 50       12 if ( ref $param_ref->{filename} ) {
76 3         6 ${ $param_ref->{filename} } = $content;
  3         7  
77 3         7 return;
78             }
79             open my $file, '> :raw', $param_ref->{filename}
80 0 0       0 or confess "Can not open file $param_ref->{filename} $OS_ERROR";
81 0 0       0 print {$file} $content
  0         0  
82             or confess "Can not write file $param_ref->{filename} $OS_ERROR";
83 0 0       0 close $file
84             or confess "Can not close file $param_ref->{filename} $OS_ERROR";
85            
86 0         0 return;
87             }
88            
89             sub replace_pod { ## no critic (ArgUnpacking)
90 8     8 1 9053 my %param_of = validate(
91             @_,
92             {
93             filename => { type => SCALAR | SCALARREF, default => $PROGRAM_NAME },
94             tag => { regex => qr{ \A = \w }xms },
95             before_code_block => { type => SCALAR, optional => 1 },
96             code_block => { type => SCALAR },
97             after_code_block => { type => SCALAR, optional => 1 },
98             indent => { regex => qr{ \A \d+ \z }xms, default => 1 },
99             on_verbose => { type => CODEREF, optional => 1 },
100             },
101             );
102            
103 8         281 BLOCK: for my $block ( qw(before_code_block code_block after_code_block) ) {
104 21 100       65 defined $param_of{$block}
105             or next BLOCK;
106 11 100       64 $param_of{$block} =~ m{ ^ = }xms
107             and confess "A Pod tag is not allowed in $block";
108             }
109            
110 5         30 _close_data;
111            
112             # clone
113             my %block_of = (
114             before => $param_of{before_code_block},
115             code => $param_of{code_block},
116             after => $param_of{after_code_block},
117 5         23 );
118            
119 5         19 _format_block( \%block_of );
120            
121 5         6 for my $line ( @{ $block_of{code} } ) {
  5         17  
122 16         60 $line = q{ } x $param_of{indent} . $line;
123             }
124            
125             # \t to indent, trim EOL
126             my @block = map { ## no critic (ComplexMappings)
127 22         29 my $value = $_;
128 22         40 $value =~ s{ \t }{ q{ } x $param_of{indent} }xmsge;
  8         24  
129 22         59 $value =~ s{ \s+ \z }{}xms;
130 22         47 $value;
131             } (
132 5 100       28 @{ $block_of{before} || [] },
133 5         17 @{ $block_of{code} },
134 5 100       11 @{ $block_of{after} || [] },
  5         28  
135             );
136            
137 5         31 my $current_content = _read_file( \%param_of );
138 5 100       39 if ( ! $current_content ) {
139 1         4 _on_verbose( \%param_of, 'Empty file detected' );
140 1         6 return;
141             }
142 4         24 my ($newline) = $current_content =~ m{ ( \r\n | [\n\r] ) }xms;
143 4         47 $current_content =~ s{ \r\n | [\n\r] }{\n}xmsg;
144 4         20 my ($newlines_at_eof) = $current_content =~ m{ ( \n+ ) \z }xms;
145 4   100     18 $newlines_at_eof = length +( $newlines_at_eof || q{} );
146 4         16 $current_content =~ s{ \n+ \z }{}xms;
147 4         27 my @content = split m{ \n }xms, $current_content;
148            
149             # replace Pod
150 4         17 my $is_found;
151 4         9 my $index = 0;
152 4         15 LINE: while ( $index < @content ) {
153 26         41 my $line = $content[$index];
154 26 100       44 if ( $is_found ) {
155 11 100       25 if ( $line =~ m{ \A = \w }xms ) { # stop deleting on next tag
156 4         8 $is_found = ();
157 4         16 last LINE;
158             }
159 7         10 splice @content, $index, 1; # delete current line
160 7         11 redo LINE;
161             }
162 15 100       105 if ( $line =~ m{ \A \Q$param_of{tag}\E \z }xms ) {
163 4         15 $is_found++;
164 4         29 splice @content, $index + 1, 0, q{}, @block, q{};
165 4         10 $index += 1 + @block + 1;
166             }
167 15         36 $index++;
168             }
169            
170             # check changes
171 4         18 my $new_content = join "\n", @content;
172 4 100       12 if ( $newlines_at_eof ) {
173             # restore current_content too
174 3         15 for my $content ( $current_content, $new_content ) {
175 6         14 $content .= "\n" x $newlines_at_eof;
176             }
177 3         14 _on_verbose( \%param_of, "$newlines_at_eof newline(s) at EOF detected" );
178             }
179             else {
180 1         3 _on_verbose( \%param_of, 'No newline at EOF detected' );
181             }
182 4 100       16 if ( $new_content eq $current_content ) {
183 1         4 _on_verbose( \%param_of, 'Equal content - nothing to do' );
184 1         7 return;
185             }
186            
187 3         22 $new_content =~ s{ \n }{$newline}xmsg;
188 3         12 _write_file( \%param_of, $new_content );
189            
190 3         22 return;
191             }
192            
193             sub trim_lines {
194 0     0 1   my ($text, $indent) = @_;
195            
196 0 0         if (! $indent) {
197 0           $text =~ s{ \s+ }{ }xmsg;
198 0           $text =~ s{ \A \s+ }{}xms;
199 0           $text =~ s{ \s+ \z }{}xms;
200 0           return $text;
201             }
202 0 0         $indent =~ m{ \A [1-9] \d* \z }xms
203             or confess "Indent $indent is not a positive integer";
204            
205             # measure the first line
206 0           ($indent) = $text =~ m{ \A ( (?: [ ]{$indent} )+ ) }xms;
207 0           $indent = length $indent;
208            
209 0           $text =~ s{ ^ [ ]{$indent} }{}xmsg;
210 0           $text =~ s{ [ ]+ $ }{}xmsg;
211            
212 0           return $text;
213             }
214            
215             # $Id: $
216            
217             1;
218            
219             __END__