Choose Your Own Tutorial
How the Tutorial Gets Built
The Dist::Zilla tutorial is modeled after the Choose Your Own Adventure books that I read when I was a kid. Each page is written as a simple Pod-like document and transformed into standard Perl 5 Pod with tools built using Pod::Elemental, including the Pod::Elemental::Transformer::SynHi-based transformers. The resultant Pod is transformed into XHTML by Pod::Simple::XHTML.
The program that I run to rebuild the site when I update the source documents (or the libraries that perform the conversion) is seen below. In fact, this HTML page is generated from the very program itself; it transforms non-Pod sections into Perl-highlighted code listings, as you can see in the source below. (You want to look for Nonpod
.)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: |
use 5.12.0; use warnings;
use autodie; use Encode; use File::Copy qw(copy); use Path::Class; use Pod::Elemental; use Pod::Elemental::Transformer::Pod5; use Pod::Elemental::Transformer::SynMux; use Pod::Elemental::Transformer::Codebox; use Pod::Elemental::Transformer::PPIHTML; use Pod::Elemental::Transformer::VimHTML; use Pod::Elemental::Transformer::List;
use Pod::CYOA::Transformer; use Pod::CYOA::XHTML;
use HTML::TreeBuilder;
my $src_dir = dir( $ARGV[0] || die "$0 src dest" ); my $dest_dir = dir( $ARGV[1] || die "$0 src dest" );
$dest_dir->subdir('src')->mkpath;
my $header = <<'END_HEADER'; <html> <head> <title>Dist::Zilla - Tutorial</title> <link rel='stylesheet' type='text/css' href='../style.css' /> <link rel='stylesheet' type='text/css' href='../ppi-html.css' /> <link rel='stylesheet' type='text/css' href='../vim-html.css' /> <meta name='viewport' content='width=device-width,initial-scale=1'> </head>
<body> <h1><a href='../index.html'>> dzil</a></h1> <div id='content'> <h2>Choose Your Own Tutorial</h2> <div class='nav'> <a href='SOURCE'>page source</a> | <a href='contents.html'>index</a> | page NUM </div> END_HEADER
my $footer = <<'END_FOOTER'; <div> You can fork and improve <a href='https://github.com/rjbs/dzil.org'>this documentation on GitHub</a>! </div> </div> </body> END_FOOTER
my %number_of = (start => 1); my %title_of; my $i = 2; sub number_of { my ($name) = @_; return($number_of{ $name } ||= $i++); }
my $pod5 = Pod::Elemental::Transformer::Pod5->new; my $cyoa = Pod::CYOA::Transformer->new; my $list = Pod::Elemental::Transformer::List->new;
for my $podfile (sort { $a cmp $b } grep { /\.pod$/ } $src_dir->children) { my ($short_name) = $podfile->basename =~ /(.+)\.pod\z/; my $pod = `cat $podfile`;
say "processing $short_name.pod...";
my $pod_doc = Pod::Elemental->read_string($pod);
$pod5->transform_node($pod_doc); $cyoa->transform_node($pod_doc); $list->transform_node($pod_doc);
for my $i (0 .. $#{ $pod_doc->children }) { my $para = $pod_doc->children->[ $i ]; next unless $para->isa('Pod::Elemental::Element::Pod5::Nonpod'); next if $para->content !~ /\S/ or $para->content =~ /\A#!/;
my $new_content = "#!perl\n" . $para->content; chomp $new_content;
my $new = Pod::Elemental::Element::Pod5::Verbatim->new({ content => $new_content, });
$pod_doc->children->[ $i ] = $new; }
Pod::Elemental::Transformer::List->new->transform_node($pod_doc);
my $html = pod_to_html($pod_doc);
my $header = $header; $header =~ s/NUM/number_of($short_name)/e; $header =~ s{SOURCE}{src/$short_name.pod};
$html = join qq{\n}, $header, $html, $footer;
my $root = HTML::TreeBuilder->new; $root->no_space_compacting(1); $root->parse_content($html); $root->eof;
for my $link ($root->look_down(_tag => 'h3')) { $title_of{ $short_name } = $link->as_text; last; }
if ($title_of{ $short_name }) { my $title = $root->look_down(_tag => 'title'); my $content = $title->as_text; $title->delete_content; $title->push_content("$content - $title_of{ $short_name }"); }
for my $link ( $root->look_down(class => 'pod') ->look_down(href => qr{\A[-a-z0-9]+\.html\z}) ) { next unless $link->look_up(class => 'cyoa'); my ($name) = $link->attr('href') =~ m{\A(.+)\.html\z}; my $num = number_of($name); my $text = $link->as_text; $link->delete_content; $link->push_content("$text, turn to page $num"); }
copy($podfile, $dest_dir->subdir('src'));
open my $out_fh, '>', $dest_dir->file("$short_name.html"); print { $out_fh } $root->as_HTML; }
number_of('build-tutorial');
my %is_missing; for (grep { ! exists $title_of{ $_ } } keys %number_of) { $is_missing{ $_ } = 1; $title_of{ $_ } = $_; }
{ $title_of{contents} = 'Table of Contents / Index';
my $header = $header; $header =~ s/NUM/number_of('contents')/e; $header =~ s/^.+SOURCE.+$//m; $header =~ s/^.+contents.+$//m;
open my $cheat_fh, '>', $dest_dir->file('contents.html'); print { $cheat_fh } $header, "<h3>Index</h3>\n";
print { $cheat_fh } join "\n", "<h4>Index of Topics</h4>", "<table class='index'>", (map {; "<tr><td><a href='$_.html'>$title_of{$_}</a></td><td>$number_of{$_}</td></tr>" } sort { $a cmp $b } keys %number_of), "</table>";
print { $cheat_fh } join "\n", "<h4>Index of Pages</h4>", "<table class='index'>", (map {; "<tr><td>$number_of{$_}</td><td><a href='$_.html'>$title_of{$_}</a></td></tr>" } sort { $number_of{$a} <=> $number_of{$b} } keys %number_of), "</table>";
print { $cheat_fh } $footer; }
sub pod_to_html { my ($doc) = @_;
my $mux = Pod::Elemental::Transformer::SynMux->new({ transformers => [ Pod::Elemental::Transformer::Codebox->new, Pod::Elemental::Transformer::PPIHTML->new, Pod::Elemental::Transformer::VimHTML->new, ], });
$mux->transform_node($doc);
my $pod = $doc->as_pod_string;
my $parser = Pod::CYOA::XHTML->new; $parser->output_string(\my $html); $parser->html_h_level(3); $parser->html_header(''); $parser->html_footer(''); $parser->perldoc_url_prefix("https://metacpan.org/module/"); $parser->parse_string_document( Encode::encode('utf-8', $pod) );
$html = "<div class='pod'>$html</div>";
$html =~ s{ \s*(<pre>)\s* (<table\sclass='code-listing'>.+? \s*</table>)\s*(?:<!--\shack\s-->)?\s*(</pre>)\s* }{my $str = $2; $str =~ s/\G^\s\s[^\$]*$//gm; $str}gesmx;
return $html; } |