Getopt-Long-DescriptivePod
view release on metacpan or search on metacpan
lib/Getopt/Long/DescriptivePod.pm view on Meta::CPAN
package Getopt::Long::DescriptivePod; ## no critic (TidyCode)
use strict;
use warnings;
our $VERSION = '0.06';
use Carp qw(confess);
use English qw(-no_match_vars $PROGRAM_NAME $OS_ERROR $INPUT_RECORD_SEPARATOR);
use Params::Validate qw(validate SCALAR SCALARREF CODEREF);
use Sub::Exporter -setup => {
exports => [ qw( replace_pod trim_lines ) ],
groups => {
default => [ qw( replace_pod trim_lines ) ],
},
};
sub _on_verbose {
my ($param_ref, $string) = @_;
if ( $param_ref->{on_verbose} ) {
$param_ref->{on_verbose}->($string);
}
return;
}
sub _close_data {
# after __END__ this handle is open
no warnings qw(once); ## no critic (ProhibitNoWarnings)
return close ::DATA;
}
sub _format_block {
my $block_ref = shift;
for my $key ( keys %{$block_ref} ) {
VALUE: for my $value ( $block_ref->{$key} ) { # alias only
defined $value
or next VALUE;
$value =~ s{ \r\n | [\n\r] }{\n}xmsg; # compatible \n
$value =~ s{ \A \n* (.*?) \n* \z }{$1}xms; # trim
$value = [
( $key eq 'after' ? q{} : () ),
( split m{ \n }xms, $value ),
( $key eq 'before' ? q{} : () ),
];
}
}
return;
}
sub _read_file {
my $param_ref = shift;
if ( ref $param_ref->{filename} ) {
return ${ $param_ref->{filename} };
}
if ( open my $file, '< :raw', $param_ref->{filename} ) {
local $INPUT_RECORD_SEPARATOR = ();
my $content = <$file>;
() = close $file;
return $content;
}
_verbose( $param_ref, "Can not open file $param_ref->{filename} $OS_ERROR" );
return;
}
sub _write_file {
my ($param_ref, $content) = @_;
if ( ref $param_ref->{filename} ) {
${ $param_ref->{filename} } = $content;
return;
}
open my $file, '> :raw', $param_ref->{filename}
or confess "Can not open file $param_ref->{filename} $OS_ERROR";
print {$file} $content
or confess "Can not write file $param_ref->{filename} $OS_ERROR";
close $file
or confess "Can not close file $param_ref->{filename} $OS_ERROR";
return;
}
sub replace_pod { ## no critic (ArgUnpacking)
my %param_of = validate(
@_,
{
filename => { type => SCALAR | SCALARREF, default => $PROGRAM_NAME },
tag => { regex => qr{ \A = \w }xms },
before_code_block => { type => SCALAR, optional => 1 },
code_block => { type => SCALAR },
after_code_block => { type => SCALAR, optional => 1 },
indent => { regex => qr{ \A \d+ \z }xms, default => 1 },
on_verbose => { type => CODEREF, optional => 1 },
},
);
BLOCK: for my $block ( qw(before_code_block code_block after_code_block) ) {
defined $param_of{$block}
or next BLOCK;
$param_of{$block} =~ m{ ^ = }xms
and confess "A Pod tag is not allowed in $block";
}
_close_data;
# clone
my %block_of = (
before => $param_of{before_code_block},
code => $param_of{code_block},
after => $param_of{after_code_block},
);
_format_block( \%block_of );
for my $line ( @{ $block_of{code} } ) {
$line = q{ } x $param_of{indent} . $line;
}
# \t to indent, trim EOL
my @block = map { ## no critic (ComplexMappings)
my $value = $_;
$value =~ s{ \t }{ q{ } x $param_of{indent} }xmsge;
$value =~ s{ \s+ \z }{}xms;
$value;
} (
@{ $block_of{before} || [] },
@{ $block_of{code} },
@{ $block_of{after} || [] },
);
my $current_content = _read_file( \%param_of );
if ( ! $current_content ) {
_on_verbose( \%param_of, 'Empty file detected' );
return;
}
my ($newline) = $current_content =~ m{ ( \r\n | [\n\r] ) }xms;
$current_content =~ s{ \r\n | [\n\r] }{\n}xmsg;
my ($newlines_at_eof) = $current_content =~ m{ ( \n+ ) \z }xms;
$newlines_at_eof = length +( $newlines_at_eof || q{} );
$current_content =~ s{ \n+ \z }{}xms;
my @content = split m{ \n }xms, $current_content;
# replace Pod
my $is_found;
my $index = 0;
LINE: while ( $index < @content ) {
my $line = $content[$index];
if ( $is_found ) {
if ( $line =~ m{ \A = \w }xms ) { # stop deleting on next tag
$is_found = ();
last LINE;
}
splice @content, $index, 1; # delete current line
redo LINE;
}
if ( $line =~ m{ \A \Q$param_of{tag}\E \z }xms ) {
$is_found++;
splice @content, $index + 1, 0, q{}, @block, q{};
$index += 1 + @block + 1;
}
$index++;
}
# check changes
my $new_content = join "\n", @content;
if ( $newlines_at_eof ) {
# restore current_content too
for my $content ( $current_content, $new_content ) {
$content .= "\n" x $newlines_at_eof;
}
_on_verbose( \%param_of, "$newlines_at_eof newline(s) at EOF detected" );
}
else {
_on_verbose( \%param_of, 'No newline at EOF detected' );
}
if ( $new_content eq $current_content ) {
_on_verbose( \%param_of, 'Equal content - nothing to do' );
return;
}
$new_content =~ s{ \n }{$newline}xmsg;
_write_file( \%param_of, $new_content );
return;
}
sub trim_lines {
my ($text, $indent) = @_;
if (! $indent) {
$text =~ s{ \s+ }{ }xmsg;
$text =~ s{ \A \s+ }{}xms;
$text =~ s{ \s+ \z }{}xms;
return $text;
}
$indent =~ m{ \A [1-9] \d* \z }xms
or confess "Indent $indent is not a positive integer";
# measure the first line
($indent) = $text =~ m{ \A ( (?: [ ]{$indent} )+ ) }xms;
$indent = length $indent;
( run in 0.662 second using v1.01-cache-2.11-cpan-71847e10f99 )