> dzil

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" );

# ensure that dest_dir and needed subpath exist
$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'>&gt; 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;
}

 

You can fork and improve this documentation on GitHub!