App-DocKnot
view release on metacpan or search on metacpan
lib/App/DocKnot/Generate.pm view on Meta::CPAN
(?: \s* \n )* # optional blank lines
(\2 [ ] \3) # matching indent
[^\n]+ \n # rest of line
(?: # one or more of
\4 # matching indent
[^\n]+ \n # rest of line
| # or
\s* \n # blank lines
)+ # end of indented block
) # full bullet with leading bullet
}{
my $text = $1;
$text =~ s{ [*] }{ }xms;
"\\bullet[\n\n" . $text . "\n]\n";
}xmsge;
# Do the same thing, but with numbered lists. This doesn't handle
# numbers larger than 9 currently, since that requires massaging the
# spacing.
$text =~ s{
( # capture whole contents
^ (\s*) # indent before number
\d [.] (\s+) # number and following indent
[^\n]+ \n # rest of line
(?: \s* \n )* # optional blank lines
(\2 [ ][ ] \3) # matching indent
[^\n]+ \n # rest of line
(?: # one or more of
\4 # matching indent
[^\n]+ \n # rest of line
| # or
\s* \n # blank lines
)+ # end of indented block
) # full bullet with leading bullet
}{
my $text = $1;
$text =~ s{ \A (\s*) \d [.] }{$1 }xms;
"\\number[\n\n" . $text . "\n]\n\n";
}xmsge;
# Rewrite compact bulleted lists.
$text =~ s{ \n ( (?: \s* [*] \s+ [^\n]+ \s* \n ){2,} ) }{
my $list = $1;
$list =~ s{ \n [*] \s+ ([^\n]+) }{\n\\bullet(packed)[$1]}xmsg;
"\n" . $list;
}xmsge;
# Done. Return the results.
return $text;
};
return $to_thread;
}
##############################################################################
# Helper methods
##############################################################################
# Word-wrap a paragraph of text. This is a helper function for _wrap, mostly
# so that it can be invoked recursively to wrap bulleted paragraphs.
#
# If the paragraph looks like regular text, which means indented by two or
# four spaces and consistently on each line, remove the indentation and then
# add it back in while wrapping the text.
#
# $para - A paragraph of text to wrap
# $options_ref - Options to controll the wrapping
# ignore_indent - Ignore indentation when choosing whether to wrap
#
# Returns: The wrapped paragraph
sub _wrap_paragraph {
my ($self, $para, $options_ref) = @_;
$options_ref //= {};
my ($indent) = ($para =~ m{ \A ([ ]*) \S }xms);
# If the indent is longer than five characters and the ignore indent
# option is not set, leave it alone. Allow an indent of five characters
# since it may be a continuation of a numbered list entry.
if (length($indent) > 5 && !$options_ref->{ignore_indent}) {
return $para;
}
# If this looks like thread commands or URLs, leave it alone.
if ($para =~ m{ \A \s* (?: \\ | \[\d+\] ) }xms) {
return $para;
}
# If this starts with a bullet, strip the bullet off, wrap the paragraph,
# and then add it back in.
if ($para =~ s{ \A (\s*) [*] (\s+) }{$1 $2}xms) {
my $offset = length($1);
$para = $self->_wrap_paragraph($para, { ignore_indent => 1 });
substr($para, $offset, 1, q{*});
return $para;
}
# If this starts with a number, strip the number off, wrap the paragraph,
# and then add it back in.
if ($para =~ s{\A (\s*) (\d+[.]) (\s+)}{$1 . q{ } x length($2) . $3}xmse) {
my $offset = length($1);
my $number = $2;
$para = $self->_wrap_paragraph($para, { ignore_indent => 1 });
substr($para, $offset, length($number), $number);
return $para;
}
# If this looks like a Markdown block quote, strip trailing whitespace,
# remove the leading indentation marks, wrap the paragraph, and then put
# them back.
## no critic (RegularExpressions::ProhibitCaptureWithoutTest)
if ($para =~ m{ \A (\s*) > \s }xms) {
$para =~ s{ [ ]+ \n }{\n}xmsg;
$para =~ s{ ^ (\s*) > (\s) }{$1 $2}xmsg;
my $offset = length($1);
$para = $self->_wrap_paragraph($para, { ignore_indent => 1 });
$para =~ s{ ^ (\s{$offset}) \s }{$1>}xmsg;
return $para;
}
## use critic
# If this looks like a bunch of short lines, leave it alone.
if ($para =~ m{ \A (?: \Q$indent\E [^\n]{1,45} \n ){3,} }xms) {
return $para;
}
# If this paragraph is not consistently indented, leave it alone.
if ($para !~ m{ \A (?: \Q$indent\E \S[^\n]+ \n )+ \z }xms) {
return $para;
}
# Strip the indent from each line.
$para =~ s{ (?: \A | (?<=\n) ) \Q$indent\E }{}xmsg;
# Remove any existing newlines, preserving two spaces after periods.
$para =~ s{ [.] ([)\"]?) \n (\S) }{.$1 $2}xmsg;
$para =~ s{ \n(\S) }{ $1}xmsg;
# Force locally correct configuration of Text::Wrap.
local $Text::Wrap::break = qr{\s+}xms;
local $Text::Wrap::columns = $self->{width} + 1;
local $Text::Wrap::huge = 'overflow';
local $Text::Wrap::unexpand = 0;
# Do the wrapping. This modifies @paragraphs in place.
$para = wrap($indent, $indent, $para);
# Strip any trailing whitespace, since some gets left behind after periods
# by Text::Wrap.
$para =~ s{ [ ]+ \n }{\n}xmsg;
# All done.
return $para;
}
# Word-wrap a block of text. This requires some annoying heuristics, but the
# alternative is to try to get the template to always produce correctly
# wrapped results, which is far harder.
#
# $text - The text to wrap
#
# Returns: The wrapped text
sub _wrap {
my ($self, $text) = @_;
# First, break the text up into paragraphs. (This will also turn more
# than two consecutive newlines into just two newlines.)
my @paragraphs = split(m{ \n(?:[ ]*\n)+ }xms, $text);
# Add back the trailing newlines at the end of each paragraph.
@paragraphs = map { $_ . "\n" } @paragraphs;
# Wrap all of the paragraphs. This modifies @paragraphs in place.
for my $paragraph (@paragraphs) {
$paragraph = $self->_wrap_paragraph($paragraph);
}
# Glue the paragraphs back together and return the result. Because the
# last newline won't get stripped by the split above, we have to strip an
# extra newline from the end of the file.
my $result = join("\n", @paragraphs);
$result =~ s{ \n+ \z }{\n}xms;
( run in 3.147 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )