markdown-rule.pl 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #! /usr/bin/perl
  2. # Copyright (C) 2014–2017 Alex Schroeder <alex@gnu.org>
  3. # This program is free software: you can redistribute it and/or modify it under
  4. # the terms of the GNU General Public License as published by the Free Software
  5. # Foundation, either version 3 of the License, or (at your option) any later
  6. # version.
  7. #
  8. # This program is distributed in the hope that it will be useful, but WITHOUT
  9. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  10. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License along with
  13. # this program. If not, see <http://www.gnu.org/licenses/>.
  14. use strict;
  15. use v5.10;
  16. AddModuleDescription('markdown-rule.pl', 'Markdown Rule Extension');
  17. our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack);
  18. push(@MyRules, \&MarkdownRule);
  19. # Since we want this package to be a simple add-on, we try and avoid
  20. # all conflicts by going *last*. The use of # for numbered lists by
  21. # Usemod conflicts with the use of # for headings, for example.
  22. $RuleOrder{\&MarkdownRule} = 200;
  23. # http://daringfireball.net/projects/markdown/syntax
  24. # https://help.github.com/articles/markdown-basics
  25. # https://help.github.com/articles/github-flavored-markdown
  26. sub MarkdownRule {
  27. # \escape
  28. if (m/\G\\([-#>*`=])/cg) {
  29. return $1;
  30. }
  31. # atx headers
  32. elsif ($bol and m~\G(\s*\n)*(#{1,6})[ \t]*~cg) {
  33. my $header_depth = length($2);
  34. return CloseHtmlEnvironments()
  35. . AddHtmlEnvironment("h" . $header_depth);
  36. }
  37. # end atx header at a newline
  38. elsif ((InElement('h1') or InElement('h2') or InElement('h3') or
  39. InElement('h4') or InElement('h5') or InElement('h6'))
  40. and m/\G\n/cg) {
  41. return CloseHtmlEnvironments()
  42. . AddHtmlEnvironment("p");
  43. }
  44. # > blockquote
  45. # with continuation
  46. elsif ($bol and m/\G&gt;/cg) {
  47. return CloseHtmlEnvironments()
  48. . AddHtmlEnvironment('blockquote');
  49. }
  50. # ``` = code
  51. elsif ($bol and m/\G```[ \t]*\n(.*?)\n```[ \t]*(\n|$)/cgs) {
  52. return CloseHtmlEnvironments() . $q->pre($1)
  53. . AddHtmlEnvironment("p");
  54. }
  55. # ` = code may not start with a newline
  56. elsif (m/\G`([^\n`][^`]*)`/cg) {
  57. return $q->code($1);
  58. }
  59. # ***bold and italic***
  60. elsif (not InElement('strong') and not InElement('em') and m/\G\*\*\*/cg) {
  61. return AddHtmlEnvironment('em') . AddHtmlEnvironment('strong');
  62. }
  63. elsif (InElement('strong') and InElement('em') and m/\G\*\*\*/cg) {
  64. return CloseHtmlEnvironment('strong') . CloseHtmlEnvironment('em');
  65. }
  66. # **bold**
  67. elsif (m/\G\*\*/cg) {
  68. return AddOrCloseHtmlEnvironment('strong');
  69. }
  70. # *italic* (closing before adding environment!)
  71. elsif (InElement('em') and m/\G\*/cg) {
  72. return CloseHtmlEnvironment('em');
  73. }
  74. elsif ($bol and m/\G\*/cg or m/\G(?<=\P{Word})\*/cg) {
  75. return AddHtmlEnvironment('em');
  76. }
  77. # ~~strikethrough~~ (deleted)
  78. elsif (m/\G~~/cg) {
  79. return AddOrCloseHtmlEnvironment('del');
  80. }
  81. # indented lists = nested lists
  82. elsif ($bol and m/\G(\s*\n)*()([*-]|\d+\.)[ \t]+/cg
  83. or InElement('li') && m/\G(\s*\n)+( *)([*-]|\d+\.)[ \t]+/cg) {
  84. my $nesting_goal = int(length($2)/4) + 1;
  85. my $tag = ($3 eq '*' or $3 eq '-') ? 'ul' : 'ol';
  86. my $nesting_current = 0;
  87. my @nesting = grep(/^[uo]l$/, @HtmlStack);
  88. my $html = CloseHtmlEnvironmentUntil('li'); # but don't close li element
  89. # warn "\@nesting is (@nesting)\n";
  90. # warn " goal is $nesting_goal\n";
  91. # warn " tag is $3 > $tag\n";
  92. while (@nesting > $nesting_goal) {
  93. $html .= CloseHtmlEnvironment(pop(@nesting));
  94. # warn " pop\n";
  95. }
  96. # if have the correct nesting level, but the wrong type, close it
  97. if (@nesting == $nesting_goal
  98. and $nesting[$#nesting] ne $tag) {
  99. $html .= CloseHtmlEnvironment(pop(@nesting));
  100. # warn " switch\n";
  101. }
  102. # now add a list of the appropriate type
  103. if (@nesting < $nesting_goal) {
  104. $html .= AddHtmlEnvironment($tag);
  105. # warn " add $tag\n";
  106. }
  107. # and a new list item
  108. if (InElement('li')) {
  109. $html .= CloseHtmlEnvironmentUntil($nesting[$#nesting]);
  110. # warn " close li\n";
  111. }
  112. $html .= AddHtmlEnvironment('li');
  113. # warn " add li\n";
  114. return $html;
  115. }
  116. # beginning of a table
  117. elsif ($bol and !InElement('table') and m/\G\|/cg) {
  118. # warn pos . " beginning of a table";
  119. return OpenHtmlEnvironment('table',1)
  120. . AddHtmlEnvironment('tr')
  121. . AddHtmlEnvironment('th');
  122. }
  123. # end of a row and beginning of a new row
  124. elsif (InElement('table') and m/\G\|?\n\|/cg) {
  125. # warn pos . " end of a row and beginning of a new row";
  126. return CloseHtmlEnvironment('tr')
  127. . AddHtmlEnvironment('tr')
  128. . AddHtmlEnvironment('td');
  129. }
  130. # otherwise the table ends
  131. elsif (InElement('table') and m/\G\|?(\n|$)/cg) {
  132. # warn pos . " otherwise the table ends";
  133. return CloseHtmlEnvironment('table')
  134. . AddHtmlEnvironment('p');
  135. }
  136. # continuation of the first row
  137. elsif (InElement('th') and m/\G\|/cg) {
  138. # warn pos . " continuation of the first row";
  139. return CloseHtmlEnvironment('th')
  140. . AddHtmlEnvironment('th');
  141. }
  142. # continuation of other rows
  143. elsif (InElement('td') and m/\G\|/cg) {
  144. # warn pos . " continuation of other rows";
  145. return CloseHtmlEnvironment('td')
  146. . AddHtmlEnvironment('td');
  147. }
  148. # whitespace indentation = code
  149. elsif ($bol and m/\G(\s*\n)*( .+)\n?/cg) {
  150. my $str = substr($2, 4);
  151. while (m/\G( .*)\n?/cg) {
  152. $str .= "\n" . substr($1, 4);
  153. }
  154. return OpenHtmlEnvironment('pre',1) . $str; # always level 1
  155. }
  156. # [an example](http://example.com/ "Title")
  157. elsif (m/\G\[(.+?)\]\($FullUrlPattern(\s+"(.+?)")?\)/cg) {
  158. my ($text, $url, $title) = ($1, $2, $4);
  159. $url =~ /^($UrlProtocols)/;
  160. my %params;
  161. $params{-href} = $url;
  162. $params{-class} = "url $1";
  163. $params{-title} = $title if $title;
  164. return $q->a(\%params, $text);
  165. }
  166. # setext headers (must come after block quotes)
  167. elsif ($bol and m/\G((\s*\n)*(.+?)[ \t]*\n(-+|=+)[ \t]*\n)/cg) {
  168. return CloseHtmlEnvironments()
  169. . (substr($4,0,1) eq '=' ? $q->h2($3) : $q->h3($3))
  170. . AddHtmlEnvironment('p');
  171. }
  172. return;
  173. }
  174. push(@MyRules, \&MarkdownExtraRule);
  175. sub MarkdownExtraRule {
  176. # __italic underline__
  177. if (m/\G__/cg) {
  178. return AddOrCloseHtmlEnvironment('em', 'style="font-style: normal; text-decoration: underline"');
  179. }
  180. # _underline_ (closing before adding environment!)
  181. elsif (InElement('em', 'style="font-style: normal; text-decoration: underline"') and m/\G_/cg) {
  182. return CloseHtmlEnvironment('em');
  183. }
  184. elsif ($bol and m/\G_/cg or m/\G(?<=\P{Word})_(?=\S)/cg) {
  185. return AddHtmlEnvironment('em', 'style="font-style: normal; text-decoration: underline"');
  186. }
  187. # //italic//
  188. elsif (m/\G\/\//cg) {
  189. return AddOrCloseHtmlEnvironment('em');
  190. }
  191. # /italic/ (closing before adding environment!)
  192. elsif (InElement('em') and m/\G\//cg) {
  193. return CloseHtmlEnvironment('em');
  194. }
  195. elsif ($bol and m/\G\//cg or m/\G(?<=[|[:space:]])\/(?=\S)/cg) {
  196. return AddHtmlEnvironment('em');
  197. }
  198. return;
  199. }