PageRenderTime 50ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/tutorial/build-tutorial.pod

https://github.com/daxelrod/dzil.org
Perl | 225 lines | 171 code | 51 blank | 3 comment | 10 complexity | aa3523262a3fe25114b8f283b1b3a5a3 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. </body>
  57. END_FOOTER
  58. my %number_of = (start => 1);
  59. my %title_of;
  60. my $i = 2;
  61. sub number_of {
  62. my ($name) = @_;
  63. return($number_of{ $name } ||= $i++);
  64. }
  65. my $pod5 = Pod::Elemental::Transformer::Pod5->new;
  66. my $cyoa = Pod::CYOA::Transformer->new;
  67. my $list = Pod::Elemental::Transformer::List->new;
  68. for my $podfile (sort { $a cmp $b } grep { /\.pod$/ } $src_dir->children) {
  69. my ($short_name) = $podfile->basename =~ /(.+)\.pod\z/;
  70. my $pod = `cat $podfile`;
  71. say "processing $short_name.pod...";
  72. my $pod_doc = Pod::Elemental->read_string($pod);
  73. $pod5->transform_node($pod_doc);
  74. $cyoa->transform_node($pod_doc);
  75. $list->transform_node($pod_doc);
  76. for my $i (0 .. $#{ $pod_doc->children }) {
  77. my $para = $pod_doc->children->[ $i ];
  78. next unless $para->isa('Pod::Elemental::Element::Pod5::Nonpod');
  79. next if $para->content !~ /\S/ or $para->content =~ /\A#!/;
  80. my $new_content = "#!perl\n" . $para->content;
  81. chomp $new_content;
  82. my $new = Pod::Elemental::Element::Pod5::Verbatim->new({
  83. content => $new_content,
  84. });
  85. $pod_doc->children->[ $i ] = $new;
  86. }
  87. Pod::Elemental::Transformer::List->new->transform_node($pod_doc);
  88. my $html = pod_to_html($pod_doc);
  89. my $header = $header;
  90. $header =~ s/NUM/number_of($short_name)/e;
  91. $header =~ s{SOURCE}{src/$short_name.pod};
  92. $html = join qq{\n}, $header, $html, $footer;
  93. my $root = HTML::TreeBuilder->new;
  94. $root->no_space_compacting(1);
  95. $root->parse_content($html);
  96. $root->eof;
  97. for my $link ($root->look_down(_tag => 'h3')) {
  98. $title_of{ $short_name } = $link->as_text;
  99. last;
  100. }
  101. if ($title_of{ $short_name }) {
  102. my $title = $root->look_down(_tag => 'title');
  103. my $content = $title->as_text;
  104. $title->delete_content;
  105. $title->push_content("$content - $title_of{ $short_name }");
  106. }
  107. for my $link (
  108. $root->look_down(class => 'pod')
  109. ->look_down(href => qr{\A[-a-z0-9]+\.html\z})
  110. ) {
  111. next unless $link->look_up(class => 'cyoa');
  112. my ($name) = $link->attr('href') =~ m{\A(.+)\.html\z};
  113. my $num = number_of($name);
  114. my $text = $link->as_text;
  115. $link->delete_content;
  116. $link->push_content("$text, turn to page $num");
  117. }
  118. copy($podfile, $dest_dir->subdir('src'));
  119. open my $out_fh, '>', $dest_dir->file("$short_name.html");
  120. print { $out_fh } $root->as_HTML;
  121. }
  122. number_of('build-tutorial');
  123. my %is_missing;
  124. for (grep { ! exists $title_of{ $_ } } keys %number_of) {
  125. $is_missing{ $_ } = 1;
  126. $title_of{ $_ } = $_;
  127. }
  128. {
  129. $title_of{contents} = 'Table of Contents / Index';
  130. my $header = $header;
  131. $header =~ s/NUM/number_of('contents')/e;
  132. $header =~ s/^.+SOURCE.+$//m;
  133. $header =~ s/^.+contents.+$//m;
  134. open my $cheat_fh, '>', $dest_dir->file('contents.html');
  135. print { $cheat_fh } $header, "<h3>Index</h3>\n";
  136. print { $cheat_fh } join "\n",
  137. "<h4>Index of Topics</h4>",
  138. "<table class='index'>",
  139. (map {; "<tr><td><a href='$_.html'>$title_of{$_}</a></td><td>$number_of{$_}</td></tr>" }
  140. sort { $a cmp $b } keys %number_of),
  141. "</table>";
  142. print { $cheat_fh } join "\n",
  143. "<h4>Index of Pages</h4>",
  144. "<table class='index'>",
  145. (map {; "<tr><td>$number_of{$_}</td><td><a href='$_.html'>$title_of{$_}</a></td></tr>" }
  146. sort { $number_of{$a} <=> $number_of{$b} } keys %number_of),
  147. "</table>";
  148. print { $cheat_fh } $footer;
  149. }
  150. sub pod_to_html {
  151. my ($doc) = @_;
  152. my $mux = Pod::Elemental::Transformer::SynMux->new({
  153. transformers => [
  154. Pod::Elemental::Transformer::Codebox->new,
  155. Pod::Elemental::Transformer::PPIHTML->new,
  156. Pod::Elemental::Transformer::VimHTML->new,
  157. ],
  158. });
  159. $mux->transform_node($doc);
  160. my $pod = $doc->as_pod_string;
  161. my $parser = Pod::CYOA::XHTML->new;
  162. $parser->output_string(\my $html);
  163. $parser->html_h_level(3);
  164. $parser->html_header('');
  165. $parser->html_footer('');
  166. $parser->parse_string_document( Encode::encode('utf-8', $pod) );
  167. $html = "<div class='pod'>$html</div>";
  168. $html =~ s{
  169. \s*(<pre>)\s*
  170. (<table\sclass='code-listing'>.+?
  171. \s*</table>)\s*(?:<!--\shack\s-->)?\s*(</pre>)\s*
  172. }{my $str = $2; $str =~ s/\G^\s\s[^\$]*$//gm; $str}gesmx;
  173. return $html;
  174. }