summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Markdown
diff options
context:
space:
mode:
authorIsrael Madueme <purelogiq@gmail.com>2018-06-15 23:42:19 +0200
committerDylan William Hardison <dylan@hardison.net>2018-06-15 23:42:19 +0200
commit170ec08234e29050c5d78d52e4100207625897d2 (patch)
tree14e8abc9e746dc30f42527b024d85b64f474e001 /Bugzilla/Markdown
parent404dc5496967203c5f99755340f43d712420446a (diff)
downloadbugzilla-170ec08234e29050c5d78d52e4100207625897d2.tar.gz
bugzilla-170ec08234e29050c5d78d52e4100207625897d2.tar.xz
Bug 1456877 - Add a wrapper around libcmark_gfm to Bugzilla
Diffstat (limited to 'Bugzilla/Markdown')
-rw-r--r--Bugzilla/Markdown/GFM.pm92
-rw-r--r--Bugzilla/Markdown/GFM/Node.pm33
-rw-r--r--Bugzilla/Markdown/GFM/Parser.pm109
-rw-r--r--Bugzilla/Markdown/GFM/SyntaxExtension.pm31
-rw-r--r--Bugzilla/Markdown/GFM/SyntaxExtensionList.pm23
5 files changed, 288 insertions, 0 deletions
diff --git a/Bugzilla/Markdown/GFM.pm b/Bugzilla/Markdown/GFM.pm
new file mode 100644
index 000000000..f3f24fc6a
--- /dev/null
+++ b/Bugzilla/Markdown/GFM.pm
@@ -0,0 +1,92 @@
+package Bugzilla::Markdown::GFM;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use Alien::libcmark_gfm;
+use FFI::Platypus;
+use FFI::Platypus::Buffer qw( scalar_to_buffer buffer_to_scalar );
+use Exporter qw(import);
+
+use Bugzilla::Markdown::GFM::SyntaxExtension;
+use Bugzilla::Markdown::GFM::SyntaxExtensionList;
+use Bugzilla::Markdown::GFM::Parser;
+use Bugzilla::Markdown::GFM::Node;
+
+our @EXPORT_OK = qw(cmark_markdown_to_html);
+
+my %OPTIONS = (
+ default => 0,
+ sourcepos => ( 1 << 1 ),
+ hardbreaks => ( 1 << 2 ),
+ safe => ( 1 << 3 ),
+ nobreaks => ( 1 << 4 ),
+ normalize => ( 1 << 8 ),
+ validate_utf8 => ( 1 << 9 ),
+ smart => ( 1 << 10 ),
+ github_pre_lang => ( 1 << 11 ),
+ liberal_html_tag => ( 1 << 12 ),
+ footnotes => ( 1 << 13 ),
+ strikethrough_double_tilde => ( 1 << 14 ),
+ table_prefer_style_attributes => ( 1 << 15 ),
+);
+
+my $FFI = FFI::Platypus->new(
+ lib => [grep { not -l $_ } Alien::libcmark_gfm->dynamic_libs],
+);
+
+$FFI->custom_type(
+ markdown_options_t => {
+ native_type => 'int',
+ native_to_perl => sub {
+ my ($options) = @_;
+ my $result = {};
+ foreach my $key (keys %OPTIONS) {
+ $result->{$key} = ($options & $OPTIONS{$key}) != 0;
+ }
+ return $result;
+ },
+ perl_to_native => sub {
+ my ($options) = @_;
+ my $result = 0;
+ foreach my $key (keys %OPTIONS) {
+ if ($options->{$key}) {
+ $result |= $OPTIONS{$key};
+ }
+ }
+ return $result;
+ }
+ }
+);
+
+$FFI->attach(cmark_markdown_to_html => ['opaque', 'int', 'markdown_options_t'] => 'string',
+ sub {
+ my $c_func = shift;
+ my($markdown, $markdown_length) = scalar_to_buffer $_[0];
+ return $c_func->($markdown, $markdown_length, $_[1]);
+ }
+);
+
+# This has to happen after something from the main lib is loaded
+$FFI->attach('core_extensions_ensure_registered' => [] => 'void');
+
+core_extensions_ensure_registered();
+
+Bugzilla::Markdown::GFM::SyntaxExtension->SETUP($FFI);
+Bugzilla::Markdown::GFM::SyntaxExtensionList->SETUP($FFI);
+Bugzilla::Markdown::GFM::Node->SETUP($FFI);
+Bugzilla::Markdown::GFM::Parser->SETUP($FFI);
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Markdown::GFM - Sets up the FFI to libcmark_gfm.
+
+=head1 DESCRIPTION
+
+This modules mainly just does setup work. See L<Bugzilla::Markdown::GFM::Parser>
+to actually render markdown to html.
diff --git a/Bugzilla/Markdown/GFM/Node.pm b/Bugzilla/Markdown/GFM/Node.pm
new file mode 100644
index 000000000..da5af1a68
--- /dev/null
+++ b/Bugzilla/Markdown/GFM/Node.pm
@@ -0,0 +1,33 @@
+package Bugzilla::Markdown::GFM::Node;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+sub SETUP {
+ my ($class, $FFI) = @_;
+
+ $FFI->custom_type(
+ markdown_node_t => {
+ native_type => 'opaque',
+ native_to_perl => sub {
+ bless \$_[0], $class if $_[0];
+ },
+ perl_to_native => sub { ${ $_[0] } },
+ }
+ );
+
+ $FFI->attach(
+ [ cmark_node_free => 'DESTROY' ],
+ [ 'markdown_node_t' ] => 'void'
+ );
+
+ $FFI->attach(
+ [ cmark_render_html => 'render_html' ],
+ [ 'markdown_node_t', 'markdown_options_t', 'markdown_syntax_extension_list_t'] => 'string',
+ );
+}
+
+1;
+
+__END__
diff --git a/Bugzilla/Markdown/GFM/Parser.pm b/Bugzilla/Markdown/GFM/Parser.pm
new file mode 100644
index 000000000..5307b49c1
--- /dev/null
+++ b/Bugzilla/Markdown/GFM/Parser.pm
@@ -0,0 +1,109 @@
+package Bugzilla::Markdown::GFM::Parser;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+use FFI::Platypus::Buffer qw( scalar_to_buffer buffer_to_scalar );
+
+sub new {
+ my ($class, $options) = @_;
+ my $extensions = delete $options->{extensions} // [];
+ my $parser = $class->_new($options);
+ $parser->{_options} = $options;
+
+ eval {
+ foreach my $name (@$extensions) {
+ my $extension = Bugzilla::Markdown::GFM::SyntaxExtension->find($name)
+ or die "unknown extension: $name";
+ $parser->attach_syntax_extension($extension);
+ }
+ };
+
+ return $parser;
+}
+
+sub render_html {
+ my ($self, $markdown) = @_;
+ $self->feed($markdown);
+ my $node = $self->finish;
+ return $node->render_html($self->{_options}, $self->get_syntax_extensions);
+}
+
+sub SETUP {
+ my ($class, $FFI) = @_;
+
+ $FFI->custom_type(
+ markdown_parser_t => {
+ native_type => 'opaque',
+ native_to_perl => sub {
+ bless { _pointer => $_[0] }, $class;
+ },
+ perl_to_native => sub { $_[0]->{_pointer} },
+ }
+ );
+
+ $FFI->attach(
+ [ cmark_parser_new => '_new' ],
+ [ 'markdown_options_t' ] => 'markdown_parser_t',
+ sub {
+ my $c_func = shift;
+ return $c_func->($_[1]);
+ }
+ );
+
+ $FFI->attach(
+ [ cmark_parser_free => 'DESTROY' ],
+ [ 'markdown_parser_t' ] => 'void'
+ );
+
+ $FFI->attach(
+ [ cmark_parser_feed => 'feed'],
+ ['markdown_parser_t', 'opaque', 'int'] => 'void',
+ sub {
+ my $c_func = shift;
+ $c_func->($_[0], scalar_to_buffer $_[1]);
+ }
+ );
+
+ $FFI->attach(
+ [ cmark_parser_finish => 'finish' ],
+ [ 'markdown_parser_t' ] => 'markdown_node_t',
+ );
+
+ $FFI->attach(
+ [ cmark_parser_attach_syntax_extension => 'attach_syntax_extension' ],
+ [ 'markdown_parser_t', 'markdown_syntax_extension_t' ] => 'void',
+ );
+
+ $FFI->attach(
+ [ cmark_parser_get_syntax_extensions => 'get_syntax_extensions' ],
+ [ 'markdown_parser_t' ] => 'markdown_syntax_extension_list_t',
+ );
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Markdown::GFM::Parser - Transforms markdown into HTML via libcmark_gfm.
+
+=head1 SYNOPSIS
+
+ use Bugzilla::Markdown::GFM;
+ use Bugzilla::Markdown::GFM::Parser;
+
+ my $parser = Bugzilla::Markdown::GFM::Parser->new({
+ extensions => [qw( autolink tagfilter table strikethrough )]
+ });
+
+ say $parser->render_html(<<'MARKDOWN');
+ # My header
+
+ This is **markdown**!
+
+ - list item 1
+ - list item 2
+ MARKDOWN
diff --git a/Bugzilla/Markdown/GFM/SyntaxExtension.pm b/Bugzilla/Markdown/GFM/SyntaxExtension.pm
new file mode 100644
index 000000000..56efa177a
--- /dev/null
+++ b/Bugzilla/Markdown/GFM/SyntaxExtension.pm
@@ -0,0 +1,31 @@
+package Bugzilla::Markdown::GFM::SyntaxExtension;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+sub SETUP {
+ my ($class, $FFI) = @_;
+
+ $FFI->custom_type(
+ markdown_syntax_extension_t => {
+ native_type => 'opaque',
+ native_to_perl => sub {
+ bless \$_[0], $class if $_[0];
+ },
+ perl_to_native => sub { $_[0] ? ${ $_[0] } : 0 },
+ }
+ );
+ $FFI->attach(
+ [ cmark_find_syntax_extension => 'find' ],
+ [ 'string' ] => 'markdown_syntax_extension_t',
+ sub {
+ my $c_func = shift;
+ return $c_func->($_[1]);
+ }
+ );
+}
+
+1;
+
+__END__
diff --git a/Bugzilla/Markdown/GFM/SyntaxExtensionList.pm b/Bugzilla/Markdown/GFM/SyntaxExtensionList.pm
new file mode 100644
index 000000000..06a9798c2
--- /dev/null
+++ b/Bugzilla/Markdown/GFM/SyntaxExtensionList.pm
@@ -0,0 +1,23 @@
+package Bugzilla::Markdown::GFM::SyntaxExtensionList;
+
+use 5.10.1;
+use strict;
+use warnings;
+
+sub SETUP {
+ my ($class, $FFI) = @_;
+
+ $FFI->custom_type(
+ markdown_syntax_extension_list_t => {
+ native_type => 'opaque',
+ native_to_perl => sub {
+ bless \$_[0], $class if $_[0];
+ },
+ perl_to_native => sub { $_[0] ? ${ $_[0] } : 0 },
+ }
+ );
+}
+
+1;
+
+__END__