PageRenderTime 41ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/src/tutorial/build-tutorial.pod

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