summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Spek <p.spek@tyil.nl>2018-06-23 19:32:25 +0200
committerPatrick Spek <p.spek@tyil.nl>2018-06-23 19:32:25 +0200
commit4a2b457746736b8075f86145e0e3fed8473606a6 (patch)
tree41aa6b01a17e2197f3e19555faf6ffb187b0b416
downloadPod::To::Pager-4a2b457746736b8075f86145e0e3fed8473606a6.tar.gz
Pod::To::Pager-4a2b457746736b8075f86145e0e3fed8473606a6.tar.bz2
Commit the first version of the module
-rw-r--r--.editorconfig12
-rw-r--r--.gitignore6
-rw-r--r--.travis.yml13
-rw-r--r--META6.json25
-rw-r--r--README.adoc44
-rw-r--r--lib/Pod/To/Pager.pm622
-rw-r--r--lib/Pod/To/Pager/BorderedBlock.pm6101
-rw-r--r--lib/Pod/To/Pager/Handlers.pm6371
-rw-r--r--t/test-program.pl124
9 files changed, 718 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..6bbc5b4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,12 @@
+[*]
+charset = utf8
+end_of_line = lf
+insert_final_newline = true
+indent_style = tab
+indent_size = 4
+
+[*.json]
+indent_style = space
+indent_size = 2
+
+# vim: ft=dosini
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5f2320e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+# Perl 6 precompiled files
+.precomp
+
+# Editor files
+*~ # emacs
+.*.sw? # vim
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..077ddb0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+language: perl6
+
+perl6:
+ - latest
+
+os:
+ - linux
+
+install:
+ - rakudobrew build zef
+ - zef install --deps-only .
+
+script: AUTHOR_TESTING=1 prove -v -e "perl6 -Ilib" t/
diff --git a/META6.json b/META6.json
new file mode 100644
index 0000000..db8a5ac
--- /dev/null
+++ b/META6.json
@@ -0,0 +1,25 @@
+{
+ "authors": [
+ "Patrick Spek <p.spek@tyil.work>"
+ ],
+ "depends": [
+ "Terminal::ANSIColor"
+ ],
+ "description": "Nondescript",
+ "license": "AGPL-3.0",
+ "meta-version": 0,
+ "name": "Pod::To::Pager",
+ "perl": "6.c",
+ "provides": {
+ "Pod::To::Pager": "lib/Pod/To/Pager.pm6",
+ "Pod::To::Pager::BorderedBlock": "lib/Pod/To/Pager/BorderedBlock.pm6",
+ "Pod::To::Pager::Handlers": "lib/Pod/To/Pager/Handlers.pm6"
+ },
+ "resources": [
+
+ ],
+ "tags": [
+
+ ],
+ "version": "0.0.0"
+} \ No newline at end of file
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..7c2da04
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,44 @@
+= Pod::To::Pager
+:toc: preamble
+
+This is a Perl 6 module to convert a Perl 6 Pod document to a more
+user-friendly variant for viewing on the shell. It is intended to be piped
+through a pager, such as `less`.
+
+== Differences with the default Pod parser
+
+To see the difference with the default Pod parser bundled with `perl6`, run the
+following commands and compare the output:
+
+ perl6 --doc t/test-program.pl # Default
+ perl6 -Ilib --doc=Pager t/test-program.pl # Pod::To::Pager variant
+
+=== No declarator blocks
+
+Declarator blocks are ignored by `Pod::To::Pager`. These are useful for
+developers, but less so for end users. They also look very out of place in the
+rest of the Pod output.
+
+=== More styling
+
+`Pod::To::Pager` uses `Terminal::ANSIColor` to apply more styling than the
+default Pod parser. This includes bold, italic or underlined characters, and a
+few colors.
+
+== Using it
+
+To use this module for your Pod reading needs, install the module with `zef`:
+
+ zef install Pod::To::Pager
+
+This module is bundled with a utility, similar to `p6doc` when used on regular
+Perl 6 programs, called `p6man`. This is because the style of the output has
+been heavily inspired by the style of regular man pages found on all sorts of
+systems.
+
+ p6man t/test-program.pl
+
+== License
+
+This module is distributed under the terms of the GNU Affero GPL license,
+version 3.
diff --git a/lib/Pod/To/Pager.pm6 b/lib/Pod/To/Pager.pm6
new file mode 100644
index 0000000..8da16bf
--- /dev/null
+++ b/lib/Pod/To/Pager.pm6
@@ -0,0 +1,22 @@
+use v6.c;
+
+use Pod::To::Pager::Handlers;
+
+class Pod::To::Pager
+{
+ multi method render(@pod is copy)
+ {
+ @pod .= grep({ $_.WHAT ~~ Pod::Block::Named }) if (1 < @pod.elems);
+
+ self.render(@pod[0]);
+
+ return;
+ }
+
+ multi method render($pod)
+ {
+ print handle-pod($pod);
+ }
+}
+
+# vim: ft=perl6 noet
diff --git a/lib/Pod/To/Pager/BorderedBlock.pm6 b/lib/Pod/To/Pager/BorderedBlock.pm6
new file mode 100644
index 0000000..ac0b35e
--- /dev/null
+++ b/lib/Pod/To/Pager/BorderedBlock.pm6
@@ -0,0 +1,101 @@
+#! /usr/bin/env false
+
+use v6.c;
+
+use Terminal::ANSIColor;
+
+class Pod::To::Pager::BorderedBlock
+{
+ has Str %.box-characters =
+ outer-top-left => "┏",
+ outer-top-right => "┓",
+ outer-bottom-left => "┗",
+ outer-bottom-right => "┛",
+ outer-horizontal => "━",
+ outer-vertical => "┃",
+ seperator-left => "┠",
+ seperator-right => "┨",
+ seperator => "─",
+ ;
+
+ has Str $.content = "";
+ has Str $.header = "";
+ has Str $.footer = "";
+
+ multi method render (
+ --> Str
+ ) {
+ self.render(
+ $!content,
+ header => $!header,
+ footer => $!footer,
+ );
+ }
+
+ multi method render (
+ Str:D $content is copy,
+ :$header = "",
+ :$footer = "",
+ --> Str
+ ) {
+ # Trim content
+ $content .= trim;
+
+ # Calculate the maximum width of the content
+ my Int $longest-string = 0;
+
+ for $content.lines { $longest-string = colorstrip($_).chars if $longest-string < colorstrip($_).chars; }
+ for $header.lines { $longest-string = colorstrip($_).chars if $longest-string < colorstrip($_).chars; }
+ for $footer.lines { $longest-string = colorstrip($_).chars if $longest-string < colorstrip($_).chars; }
+
+ # Create the top bar
+ my Str $block = %!box-characters<outer-top-left>;
+ $block ~= %!box-characters<outer-horizontal> x $longest-string;
+ $block ~= %!box-characters<outer-top-right>;
+ $block ~= "\n";
+
+ # Add a header, if needed
+ if ($header ne "") {
+ for $header.lines {
+ $block ~= self!wrap-line($_, $longest-string);
+ }
+
+ $block ~= %!box-characters<seperator-left>;
+ $block ~= %!box-characters<seperator> x $longest-string;
+ $block ~= %!box-characters<seperator-right>;
+ $block ~= "\n";
+ }
+
+ # Add the main content
+ for $content.lines {
+ $block ~= self!wrap-line($_, $longest-string);
+ }
+
+ # Add a footer, if needed
+ if ($footer ne "") {
+ $block ~= %!box-characters<seperator-left>;
+ $block ~= %!box-characters<seperator> x $longest-string;
+ $block ~= %!box-characters<seperator-right>;
+ $block ~= "\n";
+
+ for $footer.lines {
+ $block ~= self!wrap-line($_, $longest-string);
+ }
+ }
+
+ # Create the bottom bar
+ $block ~= %!box-characters<outer-bottom-left>;
+ $block ~= %!box-characters<outer-horizontal> x $longest-string;
+ $block ~= %!box-characters<outer-bottom-right>;
+ }
+
+ method !wrap-line (
+ Str:D $line,
+ Int:D $length,
+ --> Str
+ ) {
+ %!box-characters<outer-vertical> ~ $line ~ " " x ($length - colorstrip($line).chars) ~ %!box-characters<outer-vertical> ~ "\n";
+ }
+}
+
+# vim: ft=perl6 noet
diff --git a/lib/Pod/To/Pager/Handlers.pm6 b/lib/Pod/To/Pager/Handlers.pm6
new file mode 100644
index 0000000..c7e54f5
--- /dev/null
+++ b/lib/Pod/To/Pager/Handlers.pm6
@@ -0,0 +1,371 @@
+#! /usr/bin/env false
+
+use v6.c;
+
+use Terminal::ANSIColor;
+use Pod::To::Pager::BorderedBlock;
+
+unit module Pod::To::Pager::Handlers;
+
+my @footnotes;
+
+multi sub handle-pod (
+ Pod::FormattingCode $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix is copy = "",
+ Str :$suffix is copy = "",
+ --> Str
+) {
+ $suffix = 0 < $suffix.chars
+ ?? RESET ~ $prefix
+ !! RESET
+ ;
+
+ given $pod.type {
+ when "B" { $prefix = BOLD; }
+ when "C" { $prefix = BOLD; }
+ when "E" {
+ return handle-pod(
+ $pod.contents,
+ :$max-line-length,
+ :$prefix,
+ :$suffix,
+ indent-size => 0,
+ );
+ }
+ when "I" { $prefix = ITALIC; }
+ when "K" { $prefix = color('yellow'); }
+ when "L" {
+ @footnotes.append(color('blue underline') ~ $pod.meta[0] ~ color('reset'));
+
+ return handle-pod(
+ $pod.contents[0] ~ "[" ~ @footnotes.elems ~ "]",
+ :$max-line-length,
+ indent-size => 0,
+ prefix => "",
+ suffix => "",
+ );
+ }
+ when "N" {
+ @footnotes.append($pod.contents[0]);
+
+ return "[" ~ @footnotes.elems ~ "]";
+ }
+ when "T" { $prefix = color('magenta') }
+ when "U" { $prefix = UNDERLINE; }
+ when "Z" { return ""; }
+ }
+
+ handle-pod(
+ $pod.contents,
+ :$max-line-length,
+ :$prefix,
+ indent-size => 0,
+ suffix => RESET,
+ );
+}
+
+multi sub handle-pod (
+ Pod::Block::Code $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Pod::To::Pager::BorderedBlock $block .= new;
+
+ my $code-block = $block.render(
+ handle-pod(
+ $pod.contents,
+ indent-size => 0,
+ max-line-length => 0,
+ prefix => BOLD,
+ suffix => color('reset'),
+ ),
+ header => $pod.config<name> // "",
+ );
+
+ $code-block.split("\n").map({ $_.indent($indent-size + 4) }).join("\n") ~ "\n\n";
+}
+
+#| Handle definition lists.
+multi sub handle-pod(
+ Pod::Block::Named $pod where { $pod.name eq "defn" },
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Str $first-line = $pod.contents[0].contents[0];
+ my ($word, @leftovers) = $first-line.split(/\s+/);
+
+ $pod.contents[0].contents[0] = @leftovers.join(" ");
+
+ my Str $text = "{color('cyan')}{$word}{color('reset')}\n".indent($indent-size);
+
+ $text ~= handle-pod(
+ $pod.contents,
+ indent-size => $indent-size + 2,
+ :$max-line-length,
+ :$prefix,
+ :$suffix
+ );
+
+ $text;
+}
+
+multi sub handle-pod (
+ Pod::Block::Named $pod where { $pod.name eq "input" },
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Pod::To::Pager::BorderedBlock $block .= new;
+
+ my $code-block = $block.render(
+ handle-pod(
+ $pod.contents,
+ indent-size => 0,
+ max-line-length => 0,
+ prefix => color('yellow'),
+ suffix => color('reset'),
+ ),
+ header => $pod.config<name> // "",
+ );
+
+ $code-block.split("\n").map({ $_.indent($indent-size + 4) }).join("\n") ~ "\n\n";
+}
+
+multi sub handle-pod (
+ Pod::Block::Named $pod where { $pod.name eq "output" },
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Pod::To::Pager::BorderedBlock $block .= new;
+
+ my $code-block = $block.render(
+ handle-pod(
+ $pod.contents,
+ indent-size => 0,
+ max-line-length => 0,
+ prefix => color('magenta'),
+ suffix => color('reset'),
+ ),
+ header => $pod.config<name> // "",
+ );
+
+ $code-block.split("\n").map({ $_.indent($indent-size + 4) }).join("\n") ~ "\n\n";
+}
+
+#| Handle the main pod block
+multi sub handle-pod(
+ Pod::Block::Named $pod where { $pod.name eq "pod" },
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Str $document;
+
+ for $pod.contents {
+ $document ~= handle-pod(
+ $_,
+ :$indent-size,
+ :$max-line-length,
+ :$prefix,
+ :$suffix
+ );
+ }
+
+ if (@footnotes) {
+ my $index-length = @footnotes.elems.Str.chars;
+
+ $document ~= handle-pod(
+ Pod::Block::Named.new(
+ name => "Footnotes and references",
+ )
+ );
+
+ $document ~= "\n";
+
+ loop (my $i = 0; $i < @footnotes.elems; $i++) {
+ my @lines;
+ my $line = [sprintf("%{$index-length}d: ", $i + 1)];
+ my @words = @footnotes[$i].split(/\s+/);
+
+ for @words -> $word {
+ if ($max-line-length < ($indent-size + $line.chars + $word.chars)) {
+ @lines.append(chomp($line).indent($indent-size));
+ $line = $word;
+ next;
+ }
+
+ $line ~= " $word";
+ }
+
+ @lines.append(chomp($line).indent($indent-size));
+
+ $document ~= @lines.join("\n") ~ "\n";
+ }
+ }
+
+ $document;
+}
+
+#| Handle all other non-specific named pod blocks.
+multi sub handle-pod (
+ Pod::Block::Named $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Str $text;
+
+ $text ~= "{color('bold blue')}{$pod.name}{color('reset')}\n";
+ $text ~= handle-pod(
+ $pod.contents,
+ :$indent-size,
+ :$max-line-length,
+ :$prefix,
+ :$suffix
+ ) // "";
+
+ $text;
+}
+
+multi sub handle-pod(
+ Pod::Block::Para $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my Str $text = "";
+
+ for handle-pod(
+ $pod.contents,
+ indent-size => 0,
+ max-line-length => $max-line-length - $indent-size,
+ :$prefix,
+ :$suffix
+ ).lines {
+ $text ~= $_.indent($indent-size) ~ "\n";
+ }
+
+ $text ~ "\n";
+}
+
+multi sub handle-pod(
+ Pod::Item $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ my @contents = $pod.contents;
+ my Str $bullet = "• ";
+ my Int $width = ($pod.level - 1) × 2;
+ my Str $text = $bullet.indent($indent-size + $width);
+
+ $text ~= handle-pod(
+ $pod.contents,
+ indent-size => ($indent-size + $width + $bullet.chars),
+ max-line-length => ($max-line-length - $indent-size),
+ :$prefix,
+ :$suffix,
+ ).trim-leading;
+
+ $text;
+}
+
+multi sub handle-pod(
+ Pod::Heading $pod,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ handle-pod(
+ $pod.contents,
+ :$max-line-length,
+ indent-size => 0,
+ prefix => color('bold blue'),
+ suffix => RESET,
+ );
+}
+
+multi sub handle-pod(
+ Str $text,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) {
+ if ($max-line-length < 1) {
+ for $text.lines {
+ if ($_ eq "") {
+ return "\n";
+ }
+
+ return "{$prefix}{$_.indent($indent-size)}{$suffix}";
+ }
+ }
+
+ my @words = "{$prefix}{$text}{$suffix}".split(/\s+/);
+ my @lines;
+ my $line = @words.shift;
+
+ for @words -> $word {
+ if ($max-line-length < ($indent-size + $line.chars + $word.chars)) {
+ @lines.append(chomp($line).indent($indent-size));
+ $line = $word;
+ next;
+ }
+
+ $line ~= " $word";
+ }
+
+ @lines.append(chomp($line).indent($indent-size));
+
+ return @lines.join("\n");
+}
+
+multi sub handle-pod (
+ @nodes,
+ Int :$indent-size = 8,
+ Int :$max-line-length = 80,
+ Str :$prefix = "",
+ Str :$suffix = "",
+ --> Str
+) is export {
+ my Str $text;
+
+ for @nodes {
+ $text ~= handle-pod(
+ $_,
+ :$indent-size,
+ :$max-line-length,
+ :$prefix,
+ :$suffix,
+ );
+ }
+
+ $text;
+}
+
+# vim: ft=perl6 noet
diff --git a/t/test-program.pl b/t/test-program.pl
new file mode 100644
index 0000000..95ad872
--- /dev/null
+++ b/t/test-program.pl
@@ -0,0 +1,124 @@
+#! /usr/bin/env perl6
+
+use v6.c;
+
+#| This is a prefix declarator.
+sub MAIN ()
+{
+ say "This is not supposed to be shown.";
+}
+#= And a postfix declarator.
+
+=begin pod
+
+=NAME Test Program for App::POD::Manual
+=AUTHOR Patrick Spek
+=VERSION 0.0.1
+=LICENSE GNU Affero GPL v3
+
+=head1 A test program for App::POD::Manual
+
+Now we're reaching the real POD document that I care about. Let's add in some
+test cases as well here.
+
+=head2 Text styles
+
+=item1 This text is B<bold>
+=item1 This text is I<italic>
+=item1 This text is U<underlined>
+=item1 This text is C<code>
+=item1 This text is L<a link|https://www.tyil.work> to my blog
+=item1 This text is normal Z<or is it?>
+=item1 I'm running ouf of ideas N<Search the Internet for more!>
+
+=head2 Lists
+
+=item2 Starting off at level 2
+=item5 Progressing to leel 5
+=item1 Back to level 1
+=item2 On to level 2
+=item3 On to level 3
+=item4 On to level 4
+
+=item8 But what about an item with content that goes well beyond the 80 characters?
+
+=begin item10
+Which can easily occur in a block item, for instance. It should wrap around
+and bring the next lines on a similar level of indentation.
+
+The question is, does it?
+=end item10
+
+=head3 Definition lists
+
+=defn pod
+Plain Ol' Documentation
+
+=defn foo
+Not the same as bar
+
+=head2 Code blocks
+
+Now on to some code blocks. These are generally used to show code samples.
+
+ This is a code block. It is indented by 4 spaces to indicate this.
+
+Code samples are important to easily show a user how to interact with the
+program they're using. Nobody wants to read through pages of a manual when they
+just want to know how to use it for their particular use case.
+
+=begin code :name<Named code block>
+This is a code block by name. It is covered by a begin and end block. It has no limit to line length.
+Spaces in the code are preserved .
+
+Similar for newlines, actually.
+=end code
+
+=code Code on a single line should also work as expected.
+
+And that concludes the test for code blocks!
+
+=head2 IO blocks
+
+There are blocks to denote program input and output, called IO blocks. They
+also have in-line variants:
+
+=item1 This is K<keyboard input>
+=item1 This is T<terminal output>
+
+The bigger blocks deserve a test as well, I think:
+
+=head3 Short-hand input and output
+
+=input A simple input line
+=output A simple output line
+
+=head3 Big input and output blocks
+
+=begin input
+A larger block of input.
+=end input
+
+=begin output
+A larger block of output.
+=end output
+
+=head2 Unicode
+
+And of course, let's throw in some Unicode stuff. I stole these examples from
+the documentation site, but I don't think that's a bad source of input for
+testing purposes.
+
+Perl 6 makes considerable use of the E<171> and E<187> characters.
+
+Perl 6 makes considerable use of the E<laquo> and E<raquo> characters.
+
+Perl 6 makes considerable use of the E<0b10101011> and E<0b10111011> characters.
+
+Perl 6 makes considerable use of the E<0o253> and E<0o273> characters.
+
+Perl 6 makes considerable use of the E<0d171> and E<0d187> characters.
+
+Perl 6 makes considerable use of the E<0xAB> and E<0xBB> characters.
+
+=end pod