2014-01-24 23:19:58 +01:00
|
|
|
|
#! /usr/bin/perl
|
2019-06-24 10:19:33 +02:00
|
|
|
|
# Copyright (C) 2014–2019 Alex Schroeder <alex@gnu.org>
|
2014-01-24 23:19:58 +01:00
|
|
|
|
|
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify it under
|
|
|
|
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
|
|
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
|
|
|
|
# version.
|
|
|
|
|
|
#
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
|
|
#
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License along with
|
|
|
|
|
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
2015-03-27 03:01:01 +02:00
|
|
|
|
use strict;
|
2015-08-18 10:48:03 +02:00
|
|
|
|
use v5.10;
|
2015-03-27 03:01:01 +02:00
|
|
|
|
|
2014-08-21 22:23:23 +02:00
|
|
|
|
AddModuleDescription('markdown-rule.pl', 'Markdown Rule Extension');
|
2014-01-24 23:19:58 +01:00
|
|
|
|
|
2019-09-19 10:38:47 +02:00
|
|
|
|
our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack, $Fragment);
|
2015-03-27 03:01:01 +02:00
|
|
|
|
|
2014-01-24 23:19:58 +01:00
|
|
|
|
push(@MyRules, \&MarkdownRule);
|
|
|
|
|
|
# Since we want this package to be a simple add-on, we try and avoid
|
|
|
|
|
|
# all conflicts by going *last*. The use of # for numbered lists by
|
|
|
|
|
|
# Usemod conflicts with the use of # for headings, for example.
|
|
|
|
|
|
$RuleOrder{\&MarkdownRule} = 200;
|
|
|
|
|
|
|
|
|
|
|
|
# http://daringfireball.net/projects/markdown/syntax
|
|
|
|
|
|
# https://help.github.com/articles/markdown-basics
|
|
|
|
|
|
# https://help.github.com/articles/github-flavored-markdown
|
|
|
|
|
|
|
|
|
|
|
|
sub MarkdownRule {
|
2019-09-19 10:38:47 +02:00
|
|
|
|
my $alignment;
|
2017-05-18 00:48:43 +02:00
|
|
|
|
# \escape
|
|
|
|
|
|
if (m/\G\\([-#>*`=])/cg) {
|
|
|
|
|
|
return $1;
|
|
|
|
|
|
}
|
2014-01-24 23:19:58 +01:00
|
|
|
|
# atx headers
|
2017-05-18 00:48:43 +02:00
|
|
|
|
elsif ($bol and m~\G(\s*\n)*(#{1,6})[ \t]*~cg) {
|
2014-01-24 23:19:58 +01:00
|
|
|
|
my $header_depth = length($2);
|
|
|
|
|
|
return CloseHtmlEnvironments()
|
|
|
|
|
|
. AddHtmlEnvironment("h" . $header_depth);
|
|
|
|
|
|
}
|
|
|
|
|
|
# end atx header at a newline
|
|
|
|
|
|
elsif ((InElement('h1') or InElement('h2') or InElement('h3') or
|
2017-07-07 17:11:04 +02:00
|
|
|
|
InElement('h4') or InElement('h5') or InElement('h6'))
|
|
|
|
|
|
and m/\G\n/cg) {
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return CloseHtmlEnvironments()
|
|
|
|
|
|
. AddHtmlEnvironment("p");
|
|
|
|
|
|
}
|
|
|
|
|
|
# > blockquote
|
|
|
|
|
|
# with continuation
|
2019-06-24 10:29:02 +02:00
|
|
|
|
elsif ($bol and m/\G((?:>.*\n?)+)/cg) {
|
|
|
|
|
|
Clean(CloseHtmlEnvironments());
|
|
|
|
|
|
Dirty($1);
|
|
|
|
|
|
my $text = $1;
|
|
|
|
|
|
my ($oldpos, $old_) = ((pos), $_);
|
|
|
|
|
|
print '<blockquote>';
|
|
|
|
|
|
$text =~ s/^> ?//gm;
|
|
|
|
|
|
ApplyRules($text, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
|
|
|
|
|
|
print '</blockquote>';
|
|
|
|
|
|
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
|
|
|
|
|
|
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
2019-06-21 23:51:50 +02:00
|
|
|
|
# """ = blockquote, too
|
2019-06-24 10:19:33 +02:00
|
|
|
|
elsif ($bol and m/\G("""[ \t]*\n(.*?)\n"""[ \t]*(?:\n|$))/cgs) {
|
2019-06-21 23:51:50 +02:00
|
|
|
|
Clean(CloseHtmlEnvironments());
|
|
|
|
|
|
Dirty($1);
|
|
|
|
|
|
my ($oldpos, $old_) = ((pos), $_);
|
|
|
|
|
|
print '<blockquote>';
|
2019-06-24 10:19:33 +02:00
|
|
|
|
ApplyRules($2, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
|
2019-06-21 23:51:50 +02:00
|
|
|
|
print '</blockquote>';
|
|
|
|
|
|
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
|
|
|
|
|
|
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
|
|
|
|
|
|
}
|
2017-05-18 00:48:43 +02:00
|
|
|
|
# ``` = code
|
|
|
|
|
|
elsif ($bol and m/\G```[ \t]*\n(.*?)\n```[ \t]*(\n|$)/cgs) {
|
|
|
|
|
|
return CloseHtmlEnvironments() . $q->pre($1)
|
|
|
|
|
|
. AddHtmlEnvironment("p");
|
|
|
|
|
|
}
|
2017-12-21 23:28:56 +01:00
|
|
|
|
# ` = code may not start with a newline
|
|
|
|
|
|
elsif (m/\G`([^\n`][^`]*)`/cg) {
|
2017-05-18 00:48:43 +02:00
|
|
|
|
return $q->code($1);
|
|
|
|
|
|
}
|
2014-01-24 23:19:58 +01:00
|
|
|
|
# ***bold and italic***
|
|
|
|
|
|
elsif (not InElement('strong') and not InElement('em') and m/\G\*\*\*/cg) {
|
|
|
|
|
|
return AddHtmlEnvironment('em') . AddHtmlEnvironment('strong');
|
|
|
|
|
|
}
|
2017-05-22 18:20:53 +02:00
|
|
|
|
elsif (InElement('strong') and InElement('em') and m/\G\*\*\*/cg) {
|
|
|
|
|
|
return CloseHtmlEnvironment('strong') . CloseHtmlEnvironment('em');
|
|
|
|
|
|
}
|
2014-01-24 23:19:58 +01:00
|
|
|
|
# **bold**
|
|
|
|
|
|
elsif (m/\G\*\*/cg) {
|
|
|
|
|
|
return AddOrCloseHtmlEnvironment('strong');
|
|
|
|
|
|
}
|
2017-08-28 11:42:02 +02:00
|
|
|
|
# *italic* (closing before adding environment!)
|
2017-05-18 00:48:43 +02:00
|
|
|
|
elsif (InElement('em') and m/\G\*/cg) {
|
|
|
|
|
|
return CloseHtmlEnvironment('em');
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
2017-08-28 11:42:02 +02:00
|
|
|
|
elsif ($bol and m/\G\*/cg or m/\G(?<=\P{Word})\*/cg) {
|
|
|
|
|
|
return AddHtmlEnvironment('em');
|
|
|
|
|
|
}
|
2014-01-24 23:19:58 +01:00
|
|
|
|
# ~~strikethrough~~ (deleted)
|
|
|
|
|
|
elsif (m/\G~~/cg) {
|
|
|
|
|
|
return AddOrCloseHtmlEnvironment('del');
|
|
|
|
|
|
}
|
2017-07-07 17:11:04 +02:00
|
|
|
|
# indented lists = nested lists
|
2018-01-05 08:50:00 +01:00
|
|
|
|
elsif ($bol and m/\G(\s*\n)*()([*-]|\d+\.)[ \t]+/cg
|
|
|
|
|
|
or InElement('li') && m/\G(\s*\n)+( *)([*-]|\d+\.)[ \t]+/cg) {
|
2017-07-07 17:11:04 +02:00
|
|
|
|
my $nesting_goal = int(length($2)/4) + 1;
|
|
|
|
|
|
my $tag = ($3 eq '*' or $3 eq '-') ? 'ul' : 'ol';
|
|
|
|
|
|
my $nesting_current = 0;
|
|
|
|
|
|
my @nesting = grep(/^[uo]l$/, @HtmlStack);
|
|
|
|
|
|
my $html = CloseHtmlEnvironmentUntil('li'); # but don't close li element
|
|
|
|
|
|
# warn "\@nesting is (@nesting)\n";
|
|
|
|
|
|
# warn " goal is $nesting_goal\n";
|
|
|
|
|
|
# warn " tag is $3 > $tag\n";
|
|
|
|
|
|
while (@nesting > $nesting_goal) {
|
|
|
|
|
|
$html .= CloseHtmlEnvironment(pop(@nesting));
|
|
|
|
|
|
# warn " pop\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
# if have the correct nesting level, but the wrong type, close it
|
|
|
|
|
|
if (@nesting == $nesting_goal
|
|
|
|
|
|
and $nesting[$#nesting] ne $tag) {
|
|
|
|
|
|
$html .= CloseHtmlEnvironment(pop(@nesting));
|
|
|
|
|
|
# warn " switch\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
# now add a list of the appropriate type
|
|
|
|
|
|
if (@nesting < $nesting_goal) {
|
|
|
|
|
|
$html .= AddHtmlEnvironment($tag);
|
|
|
|
|
|
# warn " add $tag\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
# and a new list item
|
|
|
|
|
|
if (InElement('li')) {
|
|
|
|
|
|
$html .= CloseHtmlEnvironmentUntil($nesting[$#nesting]);
|
|
|
|
|
|
# warn " close li\n";
|
|
|
|
|
|
}
|
|
|
|
|
|
$html .= AddHtmlEnvironment('li');
|
|
|
|
|
|
# warn " add li\n";
|
|
|
|
|
|
return $html;
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
# beginning of a table
|
|
|
|
|
|
elsif ($bol and !InElement('table') and m/\G\|/cg) {
|
|
|
|
|
|
# warn pos . " beginning of a table";
|
2019-09-19 10:38:47 +02:00
|
|
|
|
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
|
|
|
|
|
|
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
|
|
|
|
|
|
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return OpenHtmlEnvironment('table',1)
|
|
|
|
|
|
. AddHtmlEnvironment('tr')
|
2019-09-19 10:38:47 +02:00
|
|
|
|
. AddHtmlEnvironment('th', $alignment);
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
# end of a row and beginning of a new row
|
|
|
|
|
|
elsif (InElement('table') and m/\G\|?\n\|/cg) {
|
|
|
|
|
|
# warn pos . " end of a row and beginning of a new row";
|
2019-09-19 10:38:47 +02:00
|
|
|
|
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
|
|
|
|
|
|
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
|
|
|
|
|
|
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return CloseHtmlEnvironment('tr')
|
|
|
|
|
|
. AddHtmlEnvironment('tr')
|
2019-09-19 10:38:47 +02:00
|
|
|
|
. AddHtmlEnvironment('td', $alignment);
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
# otherwise the table ends
|
|
|
|
|
|
elsif (InElement('table') and m/\G\|?(\n|$)/cg) {
|
|
|
|
|
|
# warn pos . " otherwise the table ends";
|
2019-09-19 10:38:47 +02:00
|
|
|
|
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return CloseHtmlEnvironment('table')
|
|
|
|
|
|
. AddHtmlEnvironment('p');
|
|
|
|
|
|
}
|
|
|
|
|
|
# continuation of the first row
|
|
|
|
|
|
elsif (InElement('th') and m/\G\|/cg) {
|
|
|
|
|
|
# warn pos . " continuation of the first row";
|
2019-09-19 10:38:47 +02:00
|
|
|
|
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
|
|
|
|
|
|
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
|
|
|
|
|
|
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return CloseHtmlEnvironment('th')
|
2019-09-19 10:38:47 +02:00
|
|
|
|
. AddHtmlEnvironment('th', $alignment);
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
# continuation of other rows
|
|
|
|
|
|
elsif (InElement('td') and m/\G\|/cg) {
|
|
|
|
|
|
# warn pos . " continuation of other rows";
|
2019-09-19 10:38:47 +02:00
|
|
|
|
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
|
|
|
|
|
|
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
|
|
|
|
|
|
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
|
2014-01-24 23:19:58 +01:00
|
|
|
|
return CloseHtmlEnvironment('td')
|
2019-09-19 10:38:47 +02:00
|
|
|
|
. AddHtmlEnvironment('td', $alignment);
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
|
|
|
|
|
# whitespace indentation = code
|
2015-08-23 21:22:12 +03:00
|
|
|
|
elsif ($bol and m/\G(\s*\n)*( .+)\n?/cg) {
|
2014-01-24 23:19:58 +01:00
|
|
|
|
my $str = substr($2, 4);
|
2015-08-23 21:22:12 +03:00
|
|
|
|
while (m/\G( .*)\n?/cg) {
|
2014-01-24 23:19:58 +01:00
|
|
|
|
$str .= "\n" . substr($1, 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
return OpenHtmlEnvironment('pre',1) . $str; # always level 1
|
|
|
|
|
|
}
|
2019-06-21 23:51:50 +02:00
|
|
|
|
# link: [an example](http://example.com/ "Title")
|
|
|
|
|
|
elsif (m/\G\[((?:[^]\n]+\n?)+)\]\($FullUrlPattern(\s+"(.+?)")?\)/cg) {
|
2014-01-24 23:19:58 +01:00
|
|
|
|
my ($text, $url, $title) = ($1, $2, $4);
|
|
|
|
|
|
$url =~ /^($UrlProtocols)/;
|
|
|
|
|
|
my %params;
|
|
|
|
|
|
$params{-href} = $url;
|
|
|
|
|
|
$params{-class} = "url $1";
|
|
|
|
|
|
$params{-title} = $title if $title;
|
|
|
|
|
|
return $q->a(\%params, $text);
|
|
|
|
|
|
}
|
2017-12-17 22:24:48 +01:00
|
|
|
|
# setext headers (must come after block quotes)
|
|
|
|
|
|
elsif ($bol and m/\G((\s*\n)*(.+?)[ \t]*\n(-+|=+)[ \t]*\n)/cg) {
|
|
|
|
|
|
return CloseHtmlEnvironments()
|
|
|
|
|
|
. (substr($4,0,1) eq '=' ? $q->h2($3) : $q->h3($3))
|
|
|
|
|
|
. AddHtmlEnvironment('p');
|
|
|
|
|
|
}
|
2015-02-27 12:10:18 +02:00
|
|
|
|
return;
|
2014-01-24 23:19:58 +01:00
|
|
|
|
}
|
2017-05-18 00:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
push(@MyRules, \&MarkdownExtraRule);
|
|
|
|
|
|
|
|
|
|
|
|
sub MarkdownExtraRule {
|
|
|
|
|
|
# __italic underline__
|
|
|
|
|
|
if (m/\G__/cg) {
|
2017-05-22 17:59:34 +02:00
|
|
|
|
return AddOrCloseHtmlEnvironment('em', 'style="font-style: normal; text-decoration: underline"');
|
2017-05-18 00:48:43 +02:00
|
|
|
|
}
|
2017-08-28 11:42:02 +02:00
|
|
|
|
# _underline_ (closing before adding environment!)
|
2017-06-11 23:40:13 +02:00
|
|
|
|
elsif (InElement('em', 'style="font-style: normal; text-decoration: underline"') and m/\G_/cg) {
|
|
|
|
|
|
return CloseHtmlEnvironment('em');
|
2017-05-18 00:48:43 +02:00
|
|
|
|
}
|
2018-01-11 11:09:10 +01:00
|
|
|
|
elsif ($bol and m/\G_/cg or m/\G(?<=\P{Word})_(?=\S)/cg) {
|
2017-08-28 11:42:02 +02:00
|
|
|
|
return AddHtmlEnvironment('em', 'style="font-style: normal; text-decoration: underline"');
|
|
|
|
|
|
}
|
2017-05-18 00:48:43 +02:00
|
|
|
|
# //italic//
|
|
|
|
|
|
elsif (m/\G\/\//cg) {
|
|
|
|
|
|
return AddOrCloseHtmlEnvironment('em');
|
|
|
|
|
|
}
|
2017-08-28 11:42:02 +02:00
|
|
|
|
# /italic/ (closing before adding environment!)
|
2017-05-18 00:48:43 +02:00
|
|
|
|
elsif (InElement('em') and m/\G\//cg) {
|
|
|
|
|
|
return CloseHtmlEnvironment('em');
|
|
|
|
|
|
}
|
2018-01-11 11:09:10 +01:00
|
|
|
|
elsif ($bol and m/\G\//cg or m/\G(?<=[|[:space:]])\/(?=\S)/cg) {
|
2017-08-28 11:42:02 +02:00
|
|
|
|
return AddHtmlEnvironment('em');
|
|
|
|
|
|
}
|
2017-05-18 00:48:43 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|