From 120161a920acfe20ba78a1dc99175b2a6d155ec0 Mon Sep 17 00:00:00 2001 From: Adriaan Date: Sun, 27 Oct 2024 23:15:48 +0100 Subject: [PATCH 1/7] Implement yaml metadata hook --- lib/Markdown/Perl.pm | 4 +++- lib/Markdown/Perl/BlockParser.pm | 3 +++ t/501-hooks-yaml-metadata.t | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 t/501-hooks-yaml-metadata.t diff --git a/lib/Markdown/Perl.pm b/lib/Markdown/Perl.pm index cb8d777..e59617a 100644 --- a/lib/Markdown/Perl.pm +++ b/lib/Markdown/Perl.pm @@ -50,7 +50,7 @@ sub set_mode { return; } -Readonly::Array my @VALID_HOOKS => qw(resolve_link_ref); +Readonly::Array my @VALID_HOOKS => qw(resolve_link_ref yaml_metadata); sub set_hooks { my ($this, %hooks) = &_get_this_and_args; ## no critic (ProhibitAmpersandSigils) @@ -324,6 +324,8 @@ optionally a C key containing the title of the key. The hash-ref can also contain a C<content> key, in which case its value should be a span of HTML which will replace whatever would have been used for the link content. +C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in the file and will give you a YAML::Tiny object as an argument. + =back =head1 AUTHOR diff --git a/lib/Markdown/Perl/BlockParser.pm b/lib/Markdown/Perl/BlockParser.pm index f72de83..ae0eefa 100644 --- a/lib/Markdown/Perl/BlockParser.pm +++ b/lib/Markdown/Perl/BlockParser.pm @@ -374,6 +374,9 @@ sub _parse_yaml_metadata { pos($this->{md}) = 0; return; } + if(exists($this->{pmarkdown}) && exists($this->{pmarkdown}->{hooks}->{yaml_metadata})) { + my $hook_result = $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); + } } return; diff --git a/t/501-hooks-yaml-metadata.t b/t/501-hooks-yaml-metadata.t new file mode 100644 index 0000000..8884f24 --- /dev/null +++ b/t/501-hooks-yaml-metadata.t @@ -0,0 +1,32 @@ +use strict; +use warnings; +use utf8; + +use Markdown::Perl 'convert', 'set_hooks'; +use Test2::V0; + +my $p = Markdown::Perl->new(); +my $page = <<EOF; +--- +name: Mark is down +draft: false +number: 42 +--- +# Mark is down! + +I repeat: "Mark is down!" +EOF + +# Test 1: Check if we can get a string value +{ + sub hook_is_name_mark { + my $x = shift; + ok(exists($x->[0]->{name}) && $x->[0]->{name} eq 'Mark is down', "key 'name' was retrieved and validated as being 'Mark is down'"); + } + $p->set_hooks(yaml_metadata => \&hook_is_name_mark); + $p->convert($page); +} + +# Test 2: Validate that hook is not called if yaml is invalid + +done_testing; From 8ec313a83ae31afd6da6d821809df1f654af512e Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Sun, 27 Oct 2024 23:38:02 +0100 Subject: [PATCH 2/7] Add conditional parsing --- lib/Markdown/Perl.pm | 2 +- lib/Markdown/Perl/BlockParser.pm | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/Markdown/Perl.pm b/lib/Markdown/Perl.pm index e59617a..a3918f4 100644 --- a/lib/Markdown/Perl.pm +++ b/lib/Markdown/Perl.pm @@ -324,7 +324,7 @@ optionally a C<title> key containing the title of the key. The hash-ref can also contain a C<content> key, in which case its value should be a span of HTML which will replace whatever would have been used for the link content. -C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in the file and will give you a YAML::Tiny object as an argument. +C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in the file and will give you a YAML::Tiny object as an argument. If the hook returns a falsy value, the Markdown parsing will stop. This allows for conditional parsing based on values in the metadata section. =back diff --git a/lib/Markdown/Perl/BlockParser.pm b/lib/Markdown/Perl/BlockParser.pm index ae0eefa..ec2ac53 100644 --- a/lib/Markdown/Perl/BlockParser.pm +++ b/lib/Markdown/Perl/BlockParser.pm @@ -137,8 +137,10 @@ sub process { # Done at a later stage, as escaped characters don’t have their Markdown # meaning, we need a way to represent that. - # Note: for now, nothing is done with the extracted metadata. - $this->_parse_yaml_metadata() if $this->get_parse_file_metadata eq 'yaml'; + if($this->get_parse_file_metadata eq 'yaml') { + my $hook_result = $this->_parse_yaml_metadata() if $this->get_parse_file_metadata eq 'yaml'; + return if !$hook_result; + } while (defined (my $l = $this->next_line())) { # This field might be set to true at the beginning of the processing, while @@ -372,14 +374,13 @@ sub _parse_yaml_metadata { my $metadata = eval { YAML::Tiny->read_string($+{YAML}) }; if ($EVAL_ERROR) { pos($this->{md}) = 0; - return; + return -1; } if(exists($this->{pmarkdown}) && exists($this->{pmarkdown}->{hooks}->{yaml_metadata})) { - my $hook_result = $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); + return $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); } } - - return; + return 1; } # https://spec.commonmark.org/0.30/#atx-headings From 2f90811560ab0445e0e3dddd74d4b478006c99da Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Mon, 28 Oct 2024 21:51:08 +0100 Subject: [PATCH 3/7] Carping our way out of errors. --- Changes | 4 ++++ lib/Markdown/Perl.pm | 7 ++++++- lib/Markdown/Perl/BlockParser.pm | 16 +++++++++++----- t/501-hooks-yaml-metadata.t | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Changes b/Changes index c5dd6a0..65eab37 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for pmarkdown and the Markdown::Perl module. +1.08 - ? + + - Add a new yaml_metadata hook to receive the parsed YAML. + 1.07 - 2024-05-25 - Allow to specify a link content through the resolve_link_ref hook. diff --git a/lib/Markdown/Perl.pm b/lib/Markdown/Perl.pm index a3918f4..eae9b27 100644 --- a/lib/Markdown/Perl.pm +++ b/lib/Markdown/Perl.pm @@ -324,7 +324,12 @@ optionally a C<title> key containing the title of the key. The hash-ref can also contain a C<content> key, in which case its value should be a span of HTML which will replace whatever would have been used for the link content. -C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in the file and will give you a YAML::Tiny object as an argument. If the hook returns a falsy value, the Markdown parsing will stop. This allows for conditional parsing based on values in the metadata section. +=item * + +C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in +the file and will give you a YAML::Tiny object as an argument. If the hook throws +a die(), the Markdown parsing will stop. This allows for conditional parsing +based on values in the metadata section. =back diff --git a/lib/Markdown/Perl/BlockParser.pm b/lib/Markdown/Perl/BlockParser.pm index ec2ac53..6c5f455 100644 --- a/lib/Markdown/Perl/BlockParser.pm +++ b/lib/Markdown/Perl/BlockParser.pm @@ -138,8 +138,13 @@ sub process { # meaning, we need a way to represent that. if($this->get_parse_file_metadata eq 'yaml') { - my $hook_result = $this->_parse_yaml_metadata() if $this->get_parse_file_metadata eq 'yaml'; - return if !$hook_result; + my $hook_result = eval { + $this->_parse_yaml_metadata(); + }; + if(!defined($hook_result)) { # eval returns undef on die(), syntax error, .. + carp "yaml_metadata hook died. Not parsing the Markdown.\n"; + return; + } } while (defined (my $l = $this->next_line())) { @@ -374,10 +379,11 @@ sub _parse_yaml_metadata { my $metadata = eval { YAML::Tiny->read_string($+{YAML}) }; if ($EVAL_ERROR) { pos($this->{md}) = 0; - return -1; + carp 'YAML Metadata (Markdown frontmatter) is invalid.'; + return 1; } - if(exists($this->{pmarkdown}) && exists($this->{pmarkdown}->{hooks}->{yaml_metadata})) { - return $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); + if(exists($this->{pmarkdown}->{hooks}->{yaml_metadata})) { + $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); } } return 1; diff --git a/t/501-hooks-yaml-metadata.t b/t/501-hooks-yaml-metadata.t index 8884f24..ca3465d 100644 --- a/t/501-hooks-yaml-metadata.t +++ b/t/501-hooks-yaml-metadata.t @@ -17,6 +17,17 @@ number: 42 I repeat: "Mark is down!" EOF +my $invalid_page = <<EOF; +--- +name: Mark is down + draft: false + number: 42 +--- +# Mark is down! + +I repeat: "Mark is down!" +EOF + # Test 1: Check if we can get a string value { sub hook_is_name_mark { @@ -28,5 +39,14 @@ EOF } # Test 2: Validate that hook is not called if yaml is invalid +{ + my $hook_called = 0; + sub hook_called { + $hook_called = 1; + } + $p->set_hooks(yaml_metadata => \&hook_called); + ok(!$hook_called, "Hook was not called because metadata was invalid."); + $p->convert($invalid_page); +} done_testing; From 0e4f15ee864aeddf12964f16756f3dca41f65201 Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Mon, 28 Oct 2024 21:52:44 +0100 Subject: [PATCH 4/7] Spacing --- lib/Markdown/Perl/BlockParser.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Markdown/Perl/BlockParser.pm b/lib/Markdown/Perl/BlockParser.pm index 6c5f455..0af4370 100644 --- a/lib/Markdown/Perl/BlockParser.pm +++ b/lib/Markdown/Perl/BlockParser.pm @@ -139,7 +139,7 @@ sub process { if($this->get_parse_file_metadata eq 'yaml') { my $hook_result = eval { - $this->_parse_yaml_metadata(); + $this->_parse_yaml_metadata(); }; if(!defined($hook_result)) { # eval returns undef on die(), syntax error, .. carp "yaml_metadata hook died. Not parsing the Markdown.\n"; From 1466695dfa4f73c1c7775e513b0be37de4c635ec Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Tue, 29 Oct 2024 20:28:25 +0100 Subject: [PATCH 5/7] Small improvement on the way we return from the yaml metadata hook --- lib/Markdown/Perl.pm | 6 +++--- lib/Markdown/Perl/BlockParser.pm | 14 +++----------- t/501-hooks-yaml-metadata.t | 24 ++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/lib/Markdown/Perl.pm b/lib/Markdown/Perl.pm index eae9b27..1722666 100644 --- a/lib/Markdown/Perl.pm +++ b/lib/Markdown/Perl.pm @@ -327,9 +327,9 @@ will replace whatever would have been used for the link content. =item * C<yaml_metadata>: this hook will trigger if there is valid (!) YAML metadata in -the file and will give you a YAML::Tiny object as an argument. If the hook throws -a die(), the Markdown parsing will stop. This allows for conditional parsing -based on values in the metadata section. +the file and will give you a hashref as an argument. If the hook throws a die(), +the Markdown parsing will stop as the die() needs to be handled by your code. +This allows for conditional parsing based on values in the metadata section. =back diff --git a/lib/Markdown/Perl/BlockParser.pm b/lib/Markdown/Perl/BlockParser.pm index 0af4370..7374ad5 100644 --- a/lib/Markdown/Perl/BlockParser.pm +++ b/lib/Markdown/Perl/BlockParser.pm @@ -137,15 +137,7 @@ sub process { # Done at a later stage, as escaped characters don’t have their Markdown # meaning, we need a way to represent that. - if($this->get_parse_file_metadata eq 'yaml') { - my $hook_result = eval { - $this->_parse_yaml_metadata(); - }; - if(!defined($hook_result)) { # eval returns undef on die(), syntax error, .. - carp "yaml_metadata hook died. Not parsing the Markdown.\n"; - return; - } - } + $this->_parse_yaml_metadata() if $this->get_parse_file_metadata eq 'yaml'; while (defined (my $l = $this->next_line())) { # This field might be set to true at the beginning of the processing, while @@ -382,8 +374,8 @@ sub _parse_yaml_metadata { carp 'YAML Metadata (Markdown frontmatter) is invalid.'; return 1; } - if(exists($this->{pmarkdown}->{hooks}->{yaml_metadata})) { - $this->{pmarkdown}->{hooks}->{yaml_metadata}->($metadata); + if(exists($this->{pmarkdown}{hooks}{yaml_metadata})) { + $this->{pmarkdown}{hooks}{yaml_metadata}->($metadata->[0]); } } return 1; diff --git a/t/501-hooks-yaml-metadata.t b/t/501-hooks-yaml-metadata.t index ca3465d..0185179 100644 --- a/t/501-hooks-yaml-metadata.t +++ b/t/501-hooks-yaml-metadata.t @@ -3,7 +3,8 @@ use warnings; use utf8; use Markdown::Perl 'convert', 'set_hooks'; -use Test2::V0; +use Test::More; +use Test2::Tools::Warnings; my $p = Markdown::Perl->new(); my $page = <<EOF; @@ -32,7 +33,7 @@ EOF { sub hook_is_name_mark { my $x = shift; - ok(exists($x->[0]->{name}) && $x->[0]->{name} eq 'Mark is down', "key 'name' was retrieved and validated as being 'Mark is down'"); + ok(exists($x->{name}) && $x->{name} eq 'Mark is down', "key 'name' was retrieved and validated as being 'Mark is down'"); } $p->set_hooks(yaml_metadata => \&hook_is_name_mark); $p->convert($page); @@ -49,4 +50,23 @@ EOF $p->convert($invalid_page); } +# Test 3: Validate that invalid yaml causes a carp() +{ + sub hook { + } + $p->set_hooks(yaml_metadata => \&hook); + like(warning { $p->convert($invalid_page) }, qr/invalid/, "Got expected warning"); +} + +# Test 4: What happens if inside the hook we die() +{ + sub hook_die { + die "last words"; + } + $p->set_hooks(yaml_metadata => \&hook_die); + my $eval_result = eval { $p->convert($page) }; + ok(!defined($eval_result) && $@, "The code died correctly"); + ok($@ =~ /^last words/, "Code died with the correct message"); +} + done_testing; From a4116d8cf52c554e500bd802fe11b1734856fc39 Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Wed, 30 Oct 2024 19:05:14 +0100 Subject: [PATCH 6/7] Use Test2::Tools::Exception for catching die --- t/501-hooks-yaml-metadata.t | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/t/501-hooks-yaml-metadata.t b/t/501-hooks-yaml-metadata.t index 0185179..17c6955 100644 --- a/t/501-hooks-yaml-metadata.t +++ b/t/501-hooks-yaml-metadata.t @@ -5,6 +5,7 @@ use utf8; use Markdown::Perl 'convert', 'set_hooks'; use Test::More; use Test2::Tools::Warnings; +use Test2::Tools::Exception; my $p = Markdown::Perl->new(); my $page = <<EOF; @@ -64,9 +65,7 @@ EOF die "last words"; } $p->set_hooks(yaml_metadata => \&hook_die); - my $eval_result = eval { $p->convert($page) }; - ok(!defined($eval_result) && $@, "The code died correctly"); - ok($@ =~ /^last words/, "Code died with the correct message"); + like( dies { $p->convert($page) }, qr/last words/, "The hook correctly died."); } done_testing; From 78cb74026cfce0ec8cd3701fc8c0c75e38e94869 Mon Sep 17 00:00:00 2001 From: Adriaan <adriaan.dens@gmail.com> Date: Wed, 30 Oct 2024 19:09:50 +0100 Subject: [PATCH 7/7] Small update to Markdown::Perl::Options pod documentation --- lib/Markdown/Perl/Options.pm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/Markdown/Perl/Options.pm b/lib/Markdown/Perl/Options.pm index 127c771..bde11c4 100644 --- a/lib/Markdown/Perl/Options.pm +++ b/lib/Markdown/Perl/Options.pm @@ -181,9 +181,8 @@ sub _word_list { =head3 B<parse_file_metadata> I<(enum, default: yaml)> This option controls whether the parser accepts optional metadata at the -beginning of the file. Currently, it does nothing with these metadata, even when -they are accepted. In the future they might be used to build complete HTML file -instead of just fragment. +beginning of the file. The module does nothing with the metadata itself but you +can configure a hook to intercept the YAML. The possible values are: