github-markup 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/.gitignore +1 -1
  4. data/.gitmodules +3 -0
  5. data/.travis.yml +4 -1
  6. data/Dockerfile +39 -0
  7. data/HISTORY.md +4 -0
  8. data/README.md +11 -2
  9. data/github-markup.gemspec +2 -1
  10. data/lib/github-markup.rb +1 -1
  11. data/lib/github/commands/pod62html +10 -0
  12. data/lib/github/markup.rb +1 -0
  13. data/lib/github/markup/implementation.rb +5 -1
  14. data/lib/github/markups.rb +1 -0
  15. data/test/markup_test.rb +2 -3
  16. data/test/markups/README.pod6 +151 -0
  17. data/test/markups/README.pod6.html +128 -0
  18. data/vendor/Pod-To-HTML/LICENSE +201 -0
  19. data/vendor/Pod-To-HTML/META6.json +20 -0
  20. data/vendor/Pod-To-HTML/README.md +96 -0
  21. data/vendor/Pod-To-HTML/README.pod6 +42 -0
  22. data/vendor/Pod-To-HTML/TODO +5 -0
  23. data/vendor/Pod-To-HTML/lib/Pod/To/HTML.pm +691 -0
  24. data/vendor/Pod-To-HTML/resources/examples/01-parse-files.p6 +7 -0
  25. data/vendor/Pod-To-HTML/resources/examples/README.md +13 -0
  26. data/vendor/Pod-To-HTML/resources/examples/main.mustache +26 -0
  27. data/vendor/Pod-To-HTML/resources/examples/render.p6 +17 -0
  28. data/vendor/Pod-To-HTML/resources/templates/main.mustache +32 -0
  29. data/vendor/Pod-To-HTML/t/010-basic.t +29 -0
  30. data/vendor/Pod-To-HTML/t/020-code.t +49 -0
  31. data/vendor/Pod-To-HTML/t/030-comment.t +15 -0
  32. data/vendor/Pod-To-HTML/t/040-lists.t +82 -0
  33. data/vendor/Pod-To-HTML/t/050-format-x-index.t +47 -0
  34. data/vendor/Pod-To-HTML/t/060-table.t +108 -0
  35. data/vendor/Pod-To-HTML/t/070-headings.t +45 -0
  36. data/vendor/Pod-To-HTML/t/075-defn.t +45 -0
  37. data/vendor/Pod-To-HTML/t/080-lang.t +14 -0
  38. data/vendor/Pod-To-HTML/t/090-css.t +16 -0
  39. data/vendor/Pod-To-HTML/t/100-issue-37.t +16 -0
  40. data/vendor/Pod-To-HTML/t/110-issue-41.t +18 -0
  41. data/vendor/Pod-To-HTML/t/120-templates.t +42 -0
  42. data/vendor/Pod-To-HTML/t/templates/main.mustache +33 -0
  43. metadata +39 -6
@@ -0,0 +1,201 @@
1
+ The Artistic License 2.0
2
+
3
+ Copyright (c) 2000-2006, The Perl Foundation.
4
+
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ This license establishes the terms under which a given free software
11
+ Package may be copied, modified, distributed, and/or redistributed.
12
+ The intent is that the Copyright Holder maintains some artistic
13
+ control over the development of that Package while still keeping the
14
+ Package available as open source and free software.
15
+
16
+ You are always permitted to make arrangements wholly outside of this
17
+ license directly with the Copyright Holder of a given Package. If the
18
+ terms of this license do not permit the full use that you propose to
19
+ make of the Package, you should contact the Copyright Holder and seek
20
+ a different licensing arrangement.
21
+
22
+ Definitions
23
+
24
+ "Copyright Holder" means the individual(s) or organization(s)
25
+ named in the copyright notice for the entire Package.
26
+
27
+ "Contributor" means any party that has contributed code or other
28
+ material to the Package, in accordance with the Copyright Holder's
29
+ procedures.
30
+
31
+ "You" and "your" means any person who would like to copy,
32
+ distribute, or modify the Package.
33
+
34
+ "Package" means the collection of files distributed by the
35
+ Copyright Holder, and derivatives of that collection and/or of
36
+ those files. A given Package may consist of either the Standard
37
+ Version, or a Modified Version.
38
+
39
+ "Distribute" means providing a copy of the Package or making it
40
+ accessible to anyone else, or in the case of a company or
41
+ organization, to others outside of your company or organization.
42
+
43
+ "Distributor Fee" means any fee that you charge for Distributing
44
+ this Package or providing support for this Package to another
45
+ party. It does not mean licensing fees.
46
+
47
+ "Standard Version" refers to the Package if it has not been
48
+ modified, or has been modified only in ways explicitly requested
49
+ by the Copyright Holder.
50
+
51
+ "Modified Version" means the Package, if it has been changed, and
52
+ such changes were not explicitly requested by the Copyright
53
+ Holder.
54
+
55
+ "Original License" means this Artistic License as Distributed with
56
+ the Standard Version of the Package, in its current version or as
57
+ it may be modified by The Perl Foundation in the future.
58
+
59
+ "Source" form means the source code, documentation source, and
60
+ configuration files for the Package.
61
+
62
+ "Compiled" form means the compiled bytecode, object code, binary,
63
+ or any other form resulting from mechanical transformation or
64
+ translation of the Source form.
65
+
66
+
67
+ Permission for Use and Modification Without Distribution
68
+
69
+ (1) You are permitted to use the Standard Version and create and use
70
+ Modified Versions for any purpose without restriction, provided that
71
+ you do not Distribute the Modified Version.
72
+
73
+
74
+ Permissions for Redistribution of the Standard Version
75
+
76
+ (2) You may Distribute verbatim copies of the Source form of the
77
+ Standard Version of this Package in any medium without restriction,
78
+ either gratis or for a Distributor Fee, provided that you duplicate
79
+ all of the original copyright notices and associated disclaimers. At
80
+ your discretion, such verbatim copies may or may not include a
81
+ Compiled form of the Package.
82
+
83
+ (3) You may apply any bug fixes, portability changes, and other
84
+ modifications made available from the Copyright Holder. The resulting
85
+ Package will still be considered the Standard Version, and as such
86
+ will be subject to the Original License.
87
+
88
+
89
+ Distribution of Modified Versions of the Package as Source
90
+
91
+ (4) You may Distribute your Modified Version as Source (either gratis
92
+ or for a Distributor Fee, and with or without a Compiled form of the
93
+ Modified Version) provided that you clearly document how it differs
94
+ from the Standard Version, including, but not limited to, documenting
95
+ any non-standard features, executables, or modules, and provided that
96
+ you do at least ONE of the following:
97
+
98
+ (a) make the Modified Version available to the Copyright Holder
99
+ of the Standard Version, under the Original License, so that the
100
+ Copyright Holder may include your modifications in the Standard
101
+ Version.
102
+
103
+ (b) ensure that installation of your Modified Version does not
104
+ prevent the user installing or running the Standard Version. In
105
+ addition, the Modified Version must bear a name that is different
106
+ from the name of the Standard Version.
107
+
108
+ (c) allow anyone who receives a copy of the Modified Version to
109
+ make the Source form of the Modified Version available to others
110
+ under
111
+
112
+ (i) the Original License or
113
+
114
+ (ii) a license that permits the licensee to freely copy,
115
+ modify and redistribute the Modified Version using the same
116
+ licensing terms that apply to the copy that the licensee
117
+ received, and requires that the Source form of the Modified
118
+ Version, and of any works derived from it, be made freely
119
+ available in that license fees are prohibited but Distributor
120
+ Fees are allowed.
121
+
122
+
123
+ Distribution of Compiled Forms of the Standard Version
124
+ or Modified Versions without the Source
125
+
126
+ (5) You may Distribute Compiled forms of the Standard Version without
127
+ the Source, provided that you include complete instructions on how to
128
+ get the Source of the Standard Version. Such instructions must be
129
+ valid at the time of your distribution. If these instructions, at any
130
+ time while you are carrying out such distribution, become invalid, you
131
+ must provide new instructions on demand or cease further distribution.
132
+ If you provide valid instructions or cease distribution within thirty
133
+ days after you become aware that the instructions are invalid, then
134
+ you do not forfeit any of your rights under this license.
135
+
136
+ (6) You may Distribute a Modified Version in Compiled form without
137
+ the Source, provided that you comply with Section 4 with respect to
138
+ the Source of the Modified Version.
139
+
140
+
141
+ Aggregating or Linking the Package
142
+
143
+ (7) You may aggregate the Package (either the Standard Version or
144
+ Modified Version) with other packages and Distribute the resulting
145
+ aggregation provided that you do not charge a licensing fee for the
146
+ Package. Distributor Fees are permitted, and licensing fees for other
147
+ components in the aggregation are permitted. The terms of this license
148
+ apply to the use and Distribution of the Standard or Modified Versions
149
+ as included in the aggregation.
150
+
151
+ (8) You are permitted to link Modified and Standard Versions with
152
+ other works, to embed the Package in a larger work of your own, or to
153
+ build stand-alone binary or bytecode versions of applications that
154
+ include the Package, and Distribute the result without restriction,
155
+ provided the result does not expose a direct interface to the Package.
156
+
157
+
158
+ Items That are Not Considered Part of a Modified Version
159
+
160
+ (9) Works (including, but not limited to, modules and scripts) that
161
+ merely extend or make use of the Package, do not, by themselves, cause
162
+ the Package to be a Modified Version. In addition, such works are not
163
+ considered parts of the Package itself, and are not subject to the
164
+ terms of this license.
165
+
166
+
167
+ General Provisions
168
+
169
+ (10) Any use, modification, and distribution of the Standard or
170
+ Modified Versions is governed by this Artistic License. By using,
171
+ modifying or distributing the Package, you accept this license. Do not
172
+ use, modify, or distribute the Package, if you do not accept this
173
+ license.
174
+
175
+ (11) If your Modified Version has been derived from a Modified
176
+ Version made by someone other than you, you are nevertheless required
177
+ to ensure that your Modified Version complies with the requirements of
178
+ this license.
179
+
180
+ (12) This license does not grant you the right to use any trademark,
181
+ service mark, tradename, or logo of the Copyright Holder.
182
+
183
+ (13) This license includes the non-exclusive, worldwide,
184
+ free-of-charge patent license to make, have made, use, offer to sell,
185
+ sell, import and otherwise transfer the Package with respect to any
186
+ patent claims licensable by the Copyright Holder that are necessarily
187
+ infringed by the Package. If you institute patent litigation
188
+ (including a cross-claim or counterclaim) against any party alleging
189
+ that the Package constitutes direct or contributory patent
190
+ infringement, then this Artistic License to you shall terminate on the
191
+ date that such litigation is filed.
192
+
193
+ (14) Disclaimer of Warranty:
194
+ THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
195
+ IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
196
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
197
+ NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
198
+ LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
199
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
200
+ DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
201
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,20 @@
1
+ {
2
+ "perl" : "6.*",
3
+ "name" : "Pod::To::HTML",
4
+ "version" : "0.4.0",
5
+ "author" : "Perl 6",
6
+ "description" : "Convert Perl 6 Pod to HTML",
7
+ "license" : "Artistic-2.0",
8
+ "depends" : [
9
+ "URI",
10
+ "Template::Mustache"
11
+ ],
12
+ "test-depends" : ["Test::Output"],
13
+ "provides" : {
14
+ "Pod::To::HTML" : "lib/Pod/To/HTML.pm"
15
+ },
16
+ "resources": [
17
+ "templates/main.mustache"
18
+ ],
19
+ "source-url" : "git://github.com/perl6/Pod-To-HTML.git"
20
+ }
@@ -0,0 +1,96 @@
1
+ # Pod::To::HTML
2
+
3
+ [![Build Status](https://travis-ci.org/perl6/Pod-To-HTML.svg?branch=master)](https://travis-ci.org/perl6/Pod-To-HTML)
4
+
5
+ Render Perl 6 Pod as HTML
6
+
7
+ ## Install
8
+
9
+ This module is in the [Perl 6 ecosystem](https://modules.perl6.org), so you install it in the usual way:
10
+
11
+ zef install Pod::To::HTML
12
+
13
+ **Note**: Perl 6 2018.06 introduces changes on how non-breaking
14
+ whitespace was handled; this is now included in the tests. If
15
+ the installation fails, please upgrade to Perl 6 >= 2018.06 or
16
+ simply disregard the test and install with `--force` if that
17
+ particular feature is of no use to you.
18
+
19
+ **Note**: Perl6 2018.11 introduced handling of Definition blocks,
20
+ `Defn`. Please upgrade if you are using that feature in the
21
+ documentation.
22
+
23
+ ## SYNOPSIS
24
+
25
+ From the command line:
26
+
27
+ perl6 --doc=HTML lib/FancyModule.pm > FancyModule.html
28
+
29
+ From within Perl 6:
30
+
31
+ ```perl6
32
+ # Pod Block
33
+ =pod My I<super B<awesome>> embedded C<pod> document!
34
+
35
+ say Pod::To::HTML.render($=pod[0]);
36
+
37
+ # Pod file
38
+ say Pod::To::HTML.render('your/file.pod'.IO, header =>
39
+ "your-custom-header-inside-body", footer =>
40
+ "your-custom-footer-inside-body", head-fields
41
+ => "tags-inside-head", lang => "document
42
+ language (defaults to 'en')", default-title =
43
+ 'No =title was found so we use this', css-url
44
+ => 'https://example.com/css.css'); # specify
45
+ css-url as empty string to disable CSS
46
+ inclusion
47
+
48
+ # Pod string
49
+
50
+ my $pod = q:to/END/;
51
+ =pod
52
+ My I<super B<awesome>> embedded C<pod>
53
+ document!
54
+ END
55
+ say Pod::To::HTML.render($pod,
56
+ header =>"your-custom-header-inside-body",
57
+ footer => "your-custom-footer-inside-body",
58
+ head-fields => "tags-inside-head",
59
+ lang => "document language (defaults to 'en')",
60
+ default-title => 'No =title was found so we use this');
61
+
62
+ # If you want to use a specific template
63
+ say pod2html $=pod[0], :templates("lib/templates");
64
+ # main.mustache should be in that directory
65
+
66
+
67
+ ```
68
+ ## DESCRIPTION
69
+
70
+ `Pod::To::HTML` takes a Pod 6 tree and outputs correspondingly
71
+ formatted HTML. Generally this is done via the command line,
72
+ using`perl6 --doc=HTML`, which extracts the pod from the document and
73
+ feeds it to `Pod::To::HTML`. The other route is with the `render`
74
+ method (called by `--doc=HTML`), which creates a complete HTML
75
+ document from the Pod tree it is called with.
76
+
77
+ Optionally, a custom header/fooder/head-fields can be
78
+ provided. These can be used to link to custom CSS stylesheets and
79
+ JavaScript libraries.
80
+
81
+ ## Examples
82
+
83
+ Check the [`examples`](resources/examples/README.md) directory (which should have been installed with your distribution, or is right here if you download from source) for a few illustrative examples.
84
+
85
+ ## DEBUGGING
86
+
87
+ You can set the `P6DOC_DEBUG` environmental variable to make the
88
+ module produce some debugging information.
89
+
90
+
91
+ ## LICENSE
92
+
93
+ You can use and distribute this module under the terms of the The Artistic License 2.0. See the LICENSE file included in this distribution for complete details.
94
+
95
+ The META6.json file of this distribution may be distributed and modified without restrictions or attribution.
96
+
@@ -0,0 +1,42 @@
1
+ =begin pod
2
+ =TITLE Pod::To::HTML
3
+
4
+ Render Pod6 as HTML
5
+
6
+ =begin SYNOPSIS
7
+
8
+ Use it integrated with Perl 6 documentation system
9
+
10
+ perl6 --doc=HTML lib/FancyModule.pm > FancyModule.html
11
+
12
+ Or as an external function to render your documentation
13
+
14
+ =begin code
15
+ =pod My I<super B<awesome>> embedded C<pod> document!
16
+
17
+ say Pod::To::HTML.render($=pod[0]);
18
+ =end code
19
+
20
+ =end SYNOPSIS
21
+
22
+ =begin DESCRIPTION
23
+
24
+ B<C<Pod::To::HTML>> takes a C<Pod> tree and outputs correspondingly
25
+ formatted C<HTML>. Generally this is via C<perl6 --doc=HTML>, which
26
+ extracts the pod from the document and feeds it to C<Pod::To::HTML>. The
27
+ other route is with the C<render> method (called by C<--doc=HTML>),
28
+ which creates a complete C<HTML> document from the C<Pod> tree it is
29
+ called with.
30
+
31
+ =end DESCRIPTION
32
+
33
+
34
+ =begin LICENSE
35
+
36
+ You can use and distribute this module under the terms of the The Artistic License 2.0. See the LICENSE file included in this distribution for complete details.
37
+
38
+ The META6.json file of this distribution may be distributed and modified without restrictions or attribution.
39
+
40
+ =end LICENSE
41
+
42
+ =end pod
@@ -0,0 +1,5 @@
1
+ * Tests
2
+ * More of 'em
3
+ * A cleaner way to test than ms[[ ]];
4
+ * Nested items
5
+ * Generate HTML fragments from Pod fragments better
@@ -0,0 +1,691 @@
1
+ unit class Pod::To::HTML;
2
+ use URI::Escape;
3
+ use Template::Mustache;
4
+
5
+ #try require Term::ANSIColor <&colored>;
6
+ #if &colored.defined {
7
+ #&colored = -> $t, $c { $t };
8
+ #}
9
+
10
+ sub colored($text, $how) {
11
+ $text
12
+ }
13
+
14
+ multi method render($pod) {
15
+ pod2html($pod)
16
+ }
17
+
18
+ multi method render(Array $pod, Str :$header = '', Str :$footer = '', Str :head-fields($head) = '', :$default-title = '', :$lang = 'en') {
19
+ pod2html($pod, :$header, :$footer, :$head, :$default-title, :$lang)
20
+ }
21
+
22
+ multi method render(Pod::Block $pod, Str :$header = '', Str :$footer = '', Str :head-fields($head) = '', :$default-title = '', :$lang = 'en') {
23
+ pod2html($pod, :$header, :$footer, :$head, :$default-title, :$lang)
24
+ }
25
+
26
+ multi method render(IO::Path $file, Str :$header = '', Str :$footer = '', Str :head-fields($head) = '', :$default-title = '', :$lang = 'en') {
27
+ use MONKEY-SEE-NO-EVAL;
28
+ pod2html(EVAL($file.slurp ~ "\n\$=pod"), :$header, :$footer, :$head, :$default-title, :$lang);
29
+ }
30
+
31
+ multi method render(Str $pod-string, Str :$header = '', Str :$footer = '', Str :head-fields($head) = '', :$default-title = '', :$lang = 'en') {
32
+ use MONKEY-SEE-NO-EVAL;
33
+ pod2html(EVAL($pod-string ~ "\n\$=pod"), :$header, :$footer, :$head, :$default-title, :$lang);
34
+ }
35
+
36
+ # FIXME: this code's a horrible mess. It'd be really helpful to have a module providing a generic
37
+ # way to walk a Pod tree and invoke callbacks on each node, that would reduce the multispaghetti at
38
+ # the bottom to something much more readable.
39
+
40
+ my &url = {$_};
41
+ my $title;
42
+ my $subtitle;
43
+ my @meta;
44
+ my @indexes;
45
+ my @body;
46
+ my @footnotes;
47
+ my %crossrefs;
48
+
49
+ # see <https://docs.perl6.org/language/traps#Constants_are_Compile_Time>
50
+ my $DEBUG := %*ENV<P6DOC_DEBUG>;
51
+
52
+ sub Debug(Callable $c) { $c() if $DEBUG; }
53
+
54
+ sub escape_html(Str $str --> Str ) {
55
+ return $str unless ( $str ~~ /<[ & < > " ' {   ]>/ ) or ( $str ~~ / ' ' / );
56
+ $str.trans( [ q{&}, q{<}, q{>}, q{"}, q{'}, q{ } ] =>
57
+ [ q{&amp;}, q{&lt;}, q{&gt;}, q{&quot;}, q{&#39;}, q{&nbsp;}]);
58
+ }
59
+
60
+ sub unescape_html(Str $str --> Str ) {
61
+ $str.trans( [ rx{'&amp;'}, rx{'&lt;'}, rx{'&gt;'}, rx{'&quot;'}, rx{'&#39;'} ] =>
62
+ [ q{&}, q{<}, q{>}, q{"}, q{'} ] );
63
+ }
64
+
65
+ sub escape_id ($id) {
66
+ $id.trim.subst(/\s+/, '_', :g)
67
+ .subst('"', '&quot;', :g)
68
+ .subst('&nbsp;', '_', :g)
69
+ .subst('&#39;', "'", :g);
70
+ }
71
+
72
+ multi visit(Nil, |a) {
73
+ Debug { note colored("visit called for Nil", "bold") }
74
+ }
75
+
76
+ multi visit($root, :&pre, :&post, :&assemble = -> *% { Nil }) {
77
+ Debug { note colored("visit called for ", "bold") ~ $root.perl }
78
+ my ($pre, $post);
79
+ $pre = pre($root) if defined &pre;
80
+
81
+ my @content = $root.?contents.map: {visit $_, :&pre, :&post, :&assemble};
82
+ $post = post($root, :@content) if defined &post;
83
+
84
+ return assemble(:$pre, :$post, :@content, :node($root));
85
+ }
86
+
87
+ class Pod::List is Pod::Block { };
88
+ class Pod::DefnList is Pod::Block { };
89
+ BEGIN { if ::('Pod::Defn') ~~ Failure { CORE::Pod::<Defn> := class {} } }
90
+
91
+ sub assemble-list-items(:@content, :$node, *% ) {
92
+ my @newcont;
93
+ my $foundone = False;
94
+ my $everwarn = False;
95
+
96
+ my $atlevel = 0;
97
+ my @pushalias;
98
+
99
+ my sub oodwarn($got, $want) {
100
+ unless $everwarn {
101
+ warn "=item$got without preceding =item$want found!";
102
+ $everwarn = True;
103
+ }
104
+ }
105
+
106
+ for @content {
107
+ when Pod::Item {
108
+ $foundone = True;
109
+
110
+ # here we deal with @newcont being empty (first list), or with the
111
+ # last element not being a list (new list)
112
+ unless +@newcont && @newcont[*-1] ~~ Pod::List {
113
+ @newcont.push(Pod::List.new());
114
+ if $_.level > 1 {
115
+ oodwarn($_.level, 1);
116
+ }
117
+ }
118
+
119
+ # only bother doing the binding business if we're at a different
120
+ # level than previous items
121
+ if $_.level != $atlevel {
122
+ # guaranteed to be bound to a Pod::List (see above 'unless')
123
+ @pushalias := @newcont[*-1].contents;
124
+
125
+ for 2..($_.level) -> $L {
126
+ unless +@pushalias && @pushalias[*-1] ~~ Pod::List {
127
+ @pushalias.push(Pod::List.new());
128
+ if +@pushalias == 1 { # we had to push a sublist to a list with no =items
129
+ oodwarn($OUTER::_.level, $L);
130
+ }
131
+ }
132
+ @pushalias := @pushalias[*-1].contents;
133
+ }
134
+
135
+ $atlevel = $_.level;
136
+ }
137
+
138
+ @pushalias.push($_);
139
+ }
140
+ # This is simpler than lists because we don't need to
141
+ # list
142
+ when Pod::Defn {
143
+ $foundone = True;
144
+ unless +@newcont && @newcont[*-1] ~~ Pod::DefnList {
145
+ @newcont.push(Pod::DefnList.new());
146
+ }
147
+ @newcont[*-1].contents.push($_);
148
+ }
149
+
150
+ default {
151
+ @newcont.push($_);
152
+ $atlevel = 0;
153
+ }
154
+ }
155
+
156
+ return $foundone ?? $node.clone(contents => @newcont) !! $node;
157
+ }
158
+
159
+
160
+ #| Converts a Pod tree to a HTML document using templates
161
+ sub pod2html(
162
+ $pod,
163
+ :&url = -> $url { $url },
164
+ :$head = '',
165
+ :$header = '',
166
+ :$footer = '',
167
+ :$default-title,
168
+ :$css-url = '//design.perl6.org/perl.css',
169
+ :$templates = Str,
170
+ :$lang = 'en'
171
+ --> Str ) is export {
172
+
173
+ my $template-file = %?RESOURCES<templates/main.mustache>;
174
+ with $templates {
175
+ if "$templates/main.mustache".IO ~~ :f {
176
+ $template-file = "$templates/main.mustache".IO
177
+ }
178
+ else {
179
+ note "$templates does not contain required templates. Using default.";
180
+ }
181
+ }
182
+ ($title, $subtitle, @meta, @indexes, @body, @footnotes) = ();
183
+ #| Keep count of how many footnotes we've output.
184
+ my Int $*done-notes = 0;
185
+ &OUTER::url = &url;
186
+ @body.push: node2html($pod.map: { visit $_, :assemble(&assemble-list-items) });
187
+ my $title_html = $title // $default-title // '';
188
+
189
+ my Template::Mustache $main-tm .= new;
190
+ return $main-tm.render( $template-file.IO.slurp, :literal, (
191
+ :$lang,
192
+ :title($title_html),
193
+ :$subtitle,
194
+ :css($css-url),
195
+ :meta( do-metadata ),
196
+ :$head,
197
+ :toc( do-toc($pod) ),
198
+ :$header,
199
+ :@body,
200
+ :footnotes( do-footnotes ),
201
+ :$footer).hash
202
+ );
203
+
204
+ =comment out
205
+ my $prelude = qq:to/END/;
206
+ <!doctype html>
207
+ <html lang="$lang">
208
+ <head>
209
+ <title>{ $title_html }</title>
210
+ <meta charset="UTF-8" />
211
+ <style>
212
+ /* code gets the browser-default font
213
+ * kbd gets a slightly less common monospace font
214
+ * samp gets the hard pixelly fonts
215
+ */
216
+ kbd \{ font-family: "Droid Sans Mono", "Luxi Mono", "Inconsolata", monospace }
217
+ samp \{ font-family: "Terminus", "Courier", "Lucida Console", monospace }
218
+ /* WHATWG HTML frowns on the use of <u> because it looks like a link,
219
+ * so we make it not look like one.
220
+ */
221
+ u \{ text-decoration: none }
222
+ .nested \{
223
+ margin-left: 3em;
224
+ }
225
+ // footnote things:
226
+ aside, u \{ opacity: 0.7 }
227
+ a[id^="fn-"]:target \{ background: #ff0 }
228
+ </style>
229
+ { qq|<link rel="stylesheet" href="$css-url">| if $css-url }
230
+ { do-metadata() // () }
231
+ $head
232
+ </head>
233
+ <body class="pod">
234
+ <div id="___top"></div>
235
+ $header
236
+ END
237
+ =comment out
238
+ return join(qq{\n},
239
+ $prelude,
240
+ ( $title.defined ?? "<h1 class='title'>{$title_html}</h1>"
241
+ !! () ),
242
+ ( $subtitle.defined ?? "<p class='subtitle'>{$subtitle}</p>"
243
+ !! () ),
244
+ ( my $ToC := do-toc($pod) // () ),
245
+ '<div class="pod-body', ($ToC ?? '' !! ' no-toc'), '">',@body,'</div>',
246
+ do-footnotes(),
247
+ $footer,
248
+ '</body>',
249
+ "</html>\n"
250
+ );
251
+
252
+ }
253
+
254
+ #| Returns accumulated metadata as a string of C«<meta>» tags
255
+ sub do-metadata( --> Str ) {
256
+ return +@meta ?? '' !! @meta.map(-> $p {
257
+ qq[<meta name="{escape_html($p.key)}" value="{node2text($p.value)}" />]
258
+ }).join("\n");
259
+ }
260
+
261
+ #| Turns accumulated headings into a nested-C«<ol>» table of contents
262
+ sub do-toc($pod --> Str ) {
263
+ my @levels is default(0) = 0;
264
+
265
+ my proto sub find-headings($node, :$inside-heading){*}
266
+
267
+ multi sub find-headings(Str $s is raw, :$inside-heading){
268
+ $inside-heading ?? $s.trim.&escape_html !! ''
269
+ }
270
+
271
+ multi sub find-headings(Pod::FormattingCode $node is raw where *.type eq 'C', :$inside-heading){
272
+ my $html = $node.contents.map(*.&find-headings(:$inside-heading));
273
+ $inside-heading ?? qq[<code class="pod-code-inline">{$html}</code>] !! ''
274
+ }
275
+
276
+ multi sub find-headings(Pod::Heading $node is raw, :$inside-heading) {
277
+ @levels.splice($node.level) if $node.level < +@levels;
278
+ @levels[$node.level-1]++;
279
+ my $level-hierarchy = @levels.join('.'); # e.g. §4.2.12
280
+ my $text = $node.contents.map(*.&find-headings(inside-heading => True));
281
+ my $link = escape_id(node2text($node.contents));
282
+ qq[<tr class="toc-level-{$node.level}"><td class="toc-number">{$level-hierarchy}</td><td class="toc-text"><a href="#$link">{$text}</a></td></tr>\n];
283
+ }
284
+
285
+ multi sub find-headings(Positional \list, :$inside-heading){
286
+ list.map(*.&find-headings(:$inside-heading))
287
+ }
288
+
289
+ multi sub find-headings(Pod::Block $node is raw, :$inside-heading){
290
+ $node.contents.map(*.&find-headings(:$inside-heading))
291
+ }
292
+
293
+ multi sub find-headings(Pod::Config $node, :$inside-heading){
294
+ ''
295
+ }
296
+
297
+ multi sub find-headings(Pod::Raw $node is raw, :$inside-heading){
298
+ $node.contents.map(*.&find-headings(:$inside-heading))
299
+ }
300
+
301
+ my $html = find-headings($pod);
302
+ $html.trim ??
303
+ qq:to/EOH/
304
+ <nav class="indexgroup">
305
+ <table id="TOC">
306
+ <caption><h2 id="TOC_Title">Table of Contents</h2></caption>
307
+ {$html}
308
+ </table>
309
+ </nav>
310
+ EOH
311
+ !! ''
312
+ }
313
+
314
+ #| Flushes accumulated footnotes since last call. The idea here is that we can stick calls to this
315
+ #| before each C«</section>» tag (once we have those per-header) and have notes that are visually
316
+ #| and semantically attached to the section.
317
+ sub do-footnotes( --> Str ) {
318
+ return '' unless @footnotes;
319
+
320
+ my Int $current-note = $*done-notes + 1;
321
+ my $notes = @footnotes.kv.map(-> $k, $v {
322
+ my $num = $k + $current-note;
323
+ qq{<li><a href="#fn-ref-$num" id="fn-$num">[↑]</a> $v </li>\n}
324
+ }).join;
325
+
326
+ $*done-notes += @footnotes;
327
+ @footnotes = ();
328
+
329
+ return qq[<aside><ol start="$current-note">\n]
330
+ ~ $notes
331
+ ~ qq[</ol></aside>\n];
332
+ }
333
+
334
+ #| block level or below
335
+ proto sub node2html(| --> Str ) is export {*}
336
+ multi sub node2html($node) {
337
+ Debug { note colored("Generic node2html called for ", "bold") ~ $node.perl };
338
+ return node2inline($node);
339
+ }
340
+
341
+ multi sub node2html(Pod::Block::Declarator $node) {
342
+ given $node.WHEREFORE {
343
+ when Routine {
344
+ "<article>\n"
345
+ ~ '<code class="pod-code-inline">'
346
+ ~ node2text($node.WHEREFORE.name ~ $node.WHEREFORE.signature.perl)
347
+ ~ "</code>:\n"
348
+ ~ node2html($node.contents)
349
+ ~ "\n</article>\n";
350
+ }
351
+ default {
352
+ Debug { note "I don't know what {$node.WHEREFORE.WHAT.perl} is. Assuming class..." };
353
+ "<h1>"~ node2html([$node.WHEREFORE.perl, q{: }, $node.contents])~ "</h1>";
354
+ }
355
+ }
356
+ }
357
+
358
+ multi sub node2html(Pod::Block::Code $node) {
359
+ Debug { note colored("Code node2html called for ", "bold") ~ $node.gist };
360
+ if %*POD2HTML-CALLBACKS and %*POD2HTML-CALLBACKS<code> -> &cb {
361
+ return cb :$node, default => sub ($node) {
362
+ return '<pre class="pod-block-code">' ~ node2inline($node.contents) ~ "</pre>\n"
363
+ }
364
+ }
365
+ else {
366
+ return '<pre class="pod-block-code">' ~ node2inline($node.contents) ~ "</pre>\n"
367
+ }
368
+
369
+ }
370
+
371
+ multi sub node2html(Pod::Block::Comment $node) {
372
+ Debug { note colored("Comment node2html called for ", "bold") ~ $node.gist };
373
+ return '';
374
+ }
375
+
376
+ multi sub node2html(Pod::Block::Named $node) {
377
+ Debug { note colored("Named Block node2html called for ", "bold") ~ $node.gist };
378
+ given $node.name {
379
+ when 'config' { return '' }
380
+ when 'nested' {
381
+ return qq{<div class="nested">\n} ~ node2html($node.contents) ~ qq{\n</div>\n};
382
+ }
383
+ when 'output' { return qq[<pre class="pod-block-named-outout">\n] ~ node2inline($node.contents) ~ "</pre>\n"; }
384
+ when 'pod' {
385
+ return qq[<span class="{$node.config<class>}">\n{node2html($node.contents)}</span>\n]
386
+ if $node.config<class>;
387
+ return node2html($node.contents);
388
+ }
389
+ when 'para' { return node2html($node.contents[0]); }
390
+ when 'Image' {
391
+ my $url;
392
+ if $node.contents == 1 {
393
+ my $n = $node.contents[0];
394
+ if $n ~~ Str {
395
+ $url = $n;
396
+ }
397
+ elsif ($n ~~ Pod::Block::Para) && $n.contents == 1 {
398
+ $url = $n.contents[0] if $n.contents[0] ~~ Str;
399
+ }
400
+ }
401
+ unless $url.defined {
402
+ die "Found an Image block, but don't know how to extract the image URL :(";
403
+ }
404
+ return qq[<img src="$url" />];
405
+ }
406
+ when 'Xhtml' | 'Html' {
407
+ unescape_html node2rawhtml $node.contents
408
+ }
409
+ default {
410
+ if $node.name eq 'TITLE' {
411
+ $title = node2text($node.contents);
412
+ return '';
413
+ }
414
+ if $node.name eq 'SUBTITLE' {
415
+ $subtitle = node2text($node.contents);
416
+ return '';
417
+ }
418
+ elsif $node.name ~~ any(<VERSION DESCRIPTION AUTHOR COPYRIGHT SUMMARY>)
419
+ and $node.contents[0] ~~ Pod::Block::Para {
420
+ @meta.push: Pair.new(
421
+ key => $node.name.lc,
422
+ value => $node.contents
423
+ );
424
+ }
425
+
426
+ return '<section>'
427
+ ~ "<h1>{$node.name}</h1>\n"
428
+ ~ node2html($node.contents)
429
+ ~ "</section>\n";
430
+ }
431
+ }
432
+ }
433
+
434
+ sub node2rawhtml(Positional $node) {
435
+ return $node.map({ node2rawtext $_ }).join
436
+ }
437
+
438
+ multi sub node2html(Pod::Block::Para $node) {
439
+ Debug { note colored("Para node2html called for ", "bold") ~ $node.gist };
440
+ return '<p>' ~ node2inline($node.contents) ~ "</p>\n";
441
+ }
442
+
443
+ multi sub node2html(Pod::Block::Table $node) {
444
+ Debug { note colored("Table node2html called for ", "bold") ~ $node.gist };
445
+ my @r = $node.config<class>??'<table class="pod-table '~$node.config<class>~'">'!!'<table class="pod-table">';
446
+
447
+ if $node.caption -> $c {
448
+ @r.push("<caption>{node2inline($c)}</caption>");
449
+ }
450
+
451
+ if $node.headers {
452
+ @r.push(
453
+ '<thead><tr>',
454
+ $node.headers.map(-> $cell {
455
+ "<th>{node2html($cell)}</th>"
456
+ }),
457
+ '</tr></thead>'
458
+ );
459
+ }
460
+
461
+ @r.push(
462
+ '<tbody>',
463
+ $node.contents.map(-> $line {
464
+ '<tr>',
465
+ $line.list.map(-> $cell {
466
+ "<td>{node2html($cell)}</td>"
467
+ }),
468
+ '</tr>'
469
+ }),
470
+ '</tbody>',
471
+ '</table>'
472
+ );
473
+
474
+ return @r.join("\n");
475
+ }
476
+
477
+ multi sub node2html(Pod::Config $node) {
478
+ Debug { note colored("Config node2html called for ", "bold") ~ $node.perl };
479
+ return '';
480
+ }
481
+
482
+ multi sub node2html(Pod::DefnList $node ) {
483
+ return "<dl>\n" ~ node2html($node.contents) ~ "\n</dl>\n";
484
+
485
+ }
486
+ multi sub node2html(Pod::Defn $node) {
487
+
488
+ "<dt>" ~ node2html($node.term) ~ "</dt>\n" ~
489
+ "<dd>" ~ node2html($node.contents) ~ "</dd>\n";
490
+ }
491
+
492
+ # TODO: would like some way to wrap these and the following content in a <section>; this might be
493
+ # the same way we get lists working...
494
+ multi sub node2html(Pod::Heading $node) {
495
+ Debug { note colored("Heading node2html called for ", "bold") ~ $node.gist };
496
+ my $lvl = min($node.level, 6); #= HTML only has 6 levels of numbered headings
497
+ my %escaped = (
498
+ id => escape_id(node2rawtext($node.contents)),
499
+ html => node2inline($node.contents),
500
+ );
501
+
502
+ %escaped<uri> = uri_escape %escaped<id>;
503
+
504
+ @indexes.push: Pair.new(key => $lvl, value => %escaped);
505
+
506
+ my $content;
507
+ if ( %escaped<html> ~~ m{href .+ \<\/a\>} ) {
508
+ $content = %escaped<html>;
509
+ } else {
510
+ $content = qq[<a class="u" href="#___top" title="go to top of document">]
511
+ ~ %escaped<html>
512
+ ~ qq[</a>];
513
+ }
514
+
515
+ return sprintf('<h%d id="%s">', $lvl, %escaped<id>)
516
+ ~ $content ~ qq[</h{$lvl}>\n];
517
+ }
518
+
519
+ # FIXME
520
+ multi sub node2html(Pod::List $node) {
521
+ return '<ul>' ~ node2html($node.contents) ~ "</ul>\n";
522
+ }
523
+ multi sub node2html(Pod::Item $node) {
524
+ Debug { note colored("List Item node2html called for ", "bold") ~ $node.gist };
525
+ return '<li>' ~ node2html($node.contents) ~ "</li>\n";
526
+ }
527
+
528
+ multi sub node2html(Positional $node) {
529
+ return $node.map({ node2html($_) }).join
530
+ }
531
+
532
+ multi sub node2html(Str $node) {
533
+ return escape_html($node);
534
+ }
535
+
536
+
537
+ #| inline level or below
538
+ multi sub node2inline($node --> Str ) {
539
+ Debug { note colored("missing a node2inline multi for ", "bold") ~ $node.gist };
540
+ return node2text($node);
541
+ }
542
+
543
+ multi sub node2inline(Pod::Block::Para $node --> Str ) {
544
+ return node2inline($node.contents);
545
+ }
546
+
547
+ multi sub node2inline(Pod::FormattingCode $node --> Str ) {
548
+ my %basic-html = (
549
+ B => 'strong', #= Basis
550
+ C => 'code', #= Code
551
+ I => 'em', #= Important
552
+ K => 'kbd', #= Keyboard
553
+ R => 'var', #= Replaceable
554
+ T => 'samp', #= Terminal
555
+ U => 'u', #= Unusual (css: text-decoration-line: underline)
556
+ );
557
+
558
+ given $node.type {
559
+ when any(%basic-html.keys) {
560
+ return q{<} ~ %basic-html{$_} ~ q{>}
561
+ ~ node2inline($node.contents)
562
+ ~ q{</} ~ %basic-html{$_} ~ q{>};
563
+ }
564
+
565
+ # Escape
566
+ when 'E' {
567
+ return $node.meta.map({
568
+ when Int { "&#$_;" }
569
+ when Str { "&$_;" }
570
+ }).join;
571
+ }
572
+
573
+ # Note
574
+ when 'N' {
575
+ @footnotes.push(node2inline($node.contents));
576
+
577
+ my $id = +@footnotes;
578
+ return qq{<a href="#fn-$id" id="fn-ref-$id">[$id]</a>};
579
+ }
580
+
581
+ # Links
582
+ when 'L' {
583
+ my $text = node2inline($node.contents);
584
+ my $url = $node.meta[0] || node2text($node.contents);
585
+ if $text ~~ /^'#'/ {
586
+ # if we have an internal-only link, strip the # from the text.
587
+ $text = $/.postmatch
588
+ }
589
+ $url = url(unescape_html($url));
590
+ if $url ~~ /^'#'/ {
591
+ $url = '#' ~ uri_escape( escape_id($/.postmatch) )
592
+ }
593
+ return qq[<a href="$url">{$text}</a>]
594
+ }
595
+
596
+ # zero-width comment
597
+ when 'Z' {
598
+ return '';
599
+ }
600
+
601
+ when 'D' {
602
+ # TODO memorise these definitions (in $node.meta) and display them properly
603
+ my $text = node2inline($node.contents);
604
+ return qq[<defn>{$text}</defn>]
605
+ }
606
+
607
+ when 'X' {
608
+ multi sub recurse-until-str(Str:D $s){ $s }
609
+ multi sub recurse-until-str(Pod::Block $n){ $n.contents>>.&recurse-until-str().join }
610
+
611
+ my $index-text = recurse-until-str($node).join;
612
+ my @indices = $node.meta;
613
+ my $index-name-attr = qq[index-entry{@indices ?? '-' !! ''}{@indices.join('-')}{$index-text ?? '-' !! ''}$index-text].subst('_', '__', :g).subst(' ', '_', :g).subst('%', '%25', :g).subst('#', '%23', :g);
614
+
615
+ my $text = node2inline($node.contents);
616
+ %crossrefs{$_} = $text for @indices;
617
+
618
+ return qq[<a name="$index-name-attr"><span class="index-entry">$text\</span></a>] if $text;
619
+ return qq[<a name="$index-name-attr"></a>];
620
+ }
621
+
622
+ # Stuff I haven't figured out yet
623
+ default {
624
+ Debug { note colored("missing handling for a formatting code of type ", "red") ~ $node.type }
625
+ return qq{<kbd class="pod2html-todo">$node.type()&lt;}
626
+ ~ node2inline($node.contents)
627
+ ~ q{&gt;</kbd>};
628
+ }
629
+ }
630
+ }
631
+
632
+ multi sub node2inline(Positional $node --> Str ) {
633
+ return $node.map({ node2inline($_) }).join;
634
+ }
635
+
636
+ multi sub node2inline(Str $node --> Str ) {
637
+ return escape_html($node);
638
+ }
639
+
640
+
641
+ #| HTML-escaped text
642
+ multi sub node2text($node --> Str ) {
643
+ Debug { note colored("missing a node2text multi for ", "red") ~ $node.perl };
644
+ return escape_html(node2rawtext($node));
645
+ }
646
+
647
+ multi sub node2text(Pod::Block::Para $node --> Str ) {
648
+ return node2text($node.contents);
649
+ }
650
+
651
+ multi sub node2text(Pod::Raw $node --> Str ) {
652
+ my $t = $node.target;
653
+ if $t && lc($t) eq 'html' {
654
+ $node.contents.join
655
+ }
656
+ else {
657
+ '';
658
+ }
659
+ }
660
+
661
+ # FIXME: a lot of these multis are identical except the function name used...
662
+ # there has to be a better way to write this?
663
+ multi sub node2text(Positional $node --> Str ) {
664
+ return $node.map({ node2text($_) }).join;
665
+ }
666
+
667
+ multi sub node2text(Str $node --> Str ) {
668
+ return escape_html($node);
669
+ }
670
+
671
+
672
+ #| plain, unescaped text
673
+ multi sub node2rawtext($node --> Str ) {
674
+ Debug { note colored("Generic node2rawtext called with ", "red") ~ $node.perl };
675
+ return $node.Str;
676
+ }
677
+
678
+ multi sub node2rawtext(Pod::Block $node --> Str ) {
679
+ Debug { note colored("node2rawtext called for ", "bold") ~ $node.gist };
680
+ return node2rawtext($node.contents);
681
+ }
682
+
683
+ multi sub node2rawtext(Positional $node --> Str ) {
684
+ return $node.map({ node2rawtext($_) }).join;
685
+ }
686
+
687
+ multi sub node2rawtext(Str $node --> Str ) {
688
+ return $node;
689
+ }
690
+
691
+ # vim: expandtab shiftwidth=4 ft=perl6