Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 19 additions & 102 deletions lib/Locale/Po4a/Text.pm
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,9 @@ use 5.16.0;
use strict;
use warnings;

use parent qw(Locale::Po4a::TransTractor);
use parent qw(Locale::Po4a::TransTractor Locale::Po4a::YamlFrontMatter);

use Locale::Po4a::Common qw(wrap_mod dgettext);
use YAML::Tiny;
use Syntax::Keyword::Try;

=head1 OPTIONS ACCEPTED BY THIS MODULE
Expand Down Expand Up @@ -151,44 +150,26 @@ match. If B<yfm_paths> and B<yfm_keys> are used together, values are included if
they are matched by at least one of the options. Array values are always translated,
unless the B<yfm_skip_array> option is provided.

=cut

my %yfm_keys = ();

=item B<yfm_lenient> (markdown only)

Allow the YAML Front Matter parser to fail on malformated headers. This is
particularly helpful when your file starts with a horizontal ruler instead
of a YAML Front Matter, but you insist on using three dashes only for your
ruler.

=cut

my $yfm_lenient = 0;

=item B<yfm_paths> (markdown only)

=item B<yfm_paths>

Comma-separated list of hash paths to process for extraction in the YAML
Front Matter section, all other paths are skipped. Paths are matched with a
case-sensitive match. If B<yfm_paths> and B<yfm_keys> are used together,
values are included if they are matched by at least one of the options.
Arrays values are always returned unless the B<yfm_skip_array> option is
provided.

=cut

my %yfm_paths = ();

=item B<yfm_skip_array> (markdown-only)

Do not translate array values in the YAML Front Matter section.

=cut

my $yfm_skip_array = 0;

=item B<control>[B<=>I<field_list>]

Handle Debian's control files.
Expand Down Expand Up @@ -250,18 +231,20 @@ sub initialize {
if ( defined $options{'markdown'} ) {
$parse_func = \&parse_markdown;
$markdown = 1;

my %yfm_keys;
map {
$_ =~ s/^\s+|\s+$//g; # Trim the keys before using them
$yfm_keys{$_} = 1
} ( split( ',', $self->{options}{'yfm_keys'} ) );
$self->{options}{yfm_keys} = \%yfm_keys;

my %yfm_paths;
map {
$_ =~ s/^\s+|\s+$//g; # Trim the keys before using them
$yfm_paths{$_} = 1
} ( split( ',', $self->{options}{'yfm_paths'} ) );

# map { print STDERR "key $_\n"; } (keys %yfm_keys);
$yfm_skip_array = $self->{options}{'yfm_skip_array'};
$yfm_lenient = $self->{options}{'yfm_lenient'};
$self->{options}{yfm_paths} = \%yfm_paths;
} else {
foreach my $opt (qw(yfm_keys yfm_lenient yfm_skip_array)) {
die wrap_mod( "po4a::text", dgettext( "po4a", "Option %s is only valid when parsing markdown files." ),
Expand Down Expand Up @@ -596,83 +579,6 @@ sub parse_markdown_bibliographic_information {
}
}

# Support YAML Front Matter in Markdown documents
#
# If the text starts with a YAML ---\n separator, the full text until
# the next YAML ---\n separator is considered YAML metadata. The ...\n
# "end of document" separator can be used at the end of the YAML
# block.
#
sub parse_markdown_yaml_front_matter {
my ( $self, $line, $blockref ) = @_;
my $yfm;
my @saved_ctn;
my ( $nextline, $nextref ) = $self->shiftline();
push @saved_ctn, ( $nextline, $nextref );
while ( defined($nextline) ) {
last if ( $nextline =~ /^(---|\.\.\.)$/ );
$yfm .= $nextline;
( $nextline, $nextref ) = $self->shiftline();
if ( $nextline =~ /: [\[\{]/ ) {
die wrap_mod(
"po4a::text",
dgettext(
"po4a",
"Inline lists and dictionaries on a single line are not correctly handled the parser we use (YAML::Tiny): they are interpreted as regular strings. "
. "Please use multi-lines definitions instead. Offending line:\n %s"
),
$nextline
);

}
push @saved_ctn, ( $nextline, $nextref );
}

my $yamlarray; # the parsed YFM content
my $yamlres; # containing the parse error, if any
try {
$yamlarray = YAML::Tiny->read_string($yfm);
} catch {
$yamlres = $@;
}

if ( defined($yamlres) ) {
if ($yfm_lenient) {
$yamlres =~ s/ at .*$//; # Remove the error localisation in YAML::Tiny die message, if any (for our test)
warn wrap_mod(
"po4a::text",
dgettext(
"po4a",
"Proceeding even if the YAML Front Matter could not be parsed. Remove the 'yfm_lenient' option for a stricter behavior.\nIgnored error: %s"
),
$yamlres
);
my $len = ( scalar @saved_ctn ) - 1;
while ( $len >= 0 ) {
$self->unshiftline( $saved_ctn[ $len - 1 ], $saved_ctn[$len] );

# print STDERR "Unshift ".$saved_ctn[ $len - 1] ." | ". $saved_ctn[$len] ."\n";
$len -= 2;
}
return 0; # Not a valid YAML
} else {
die wrap_mod(
"po4a::text",
dgettext(
"po4a",
"Could not get the YAML Front Matter from the file. If you did not intend to add a YAML front matter "
. "but an horizontal ruler, please use '----' instead, or pass the 'yfm_lenient' option.\nError: %s\nContent of the YFM: %s"
),
$yamlres, $yfm
);
}
}

$self->handle_yaml( 1, $blockref, $yamlarray, \%yfm_keys, $yfm_skip_array, \%yfm_paths );
$self->pushline("---\n");
return 1; # Valid YAML
}

sub parse_markdown {
my ( $self, $line, $ref, $paragraph, $wrapped_mode, $expect_header, $end_of_paragraph ) = @_;
if ($expect_header) {
Expand All @@ -685,7 +591,18 @@ sub parse_markdown {
parse_markdown_bibliographic_information( $self, $line, $ref );
return ( $paragraph, $wrapped_mode, $expect_header, $end_of_paragraph );
} elsif ( $line =~ /^---$/ ) {
if ( parse_markdown_yaml_front_matter( $self, $line, $ref ) ) { # successfully parsed
if (
$self->parse_yaml_front_matter(
$ref,
{
keys => $self->{options}{yfm_keys},
skip_array => $self->{options}{yfm_skip_array},
paths => $self->{options}{yfm_paths},
lenient => $self->{options}{yfm_lenient},
}
)
)
{ # successfully parsed
return ( $paragraph, $wrapped_mode, $expect_header, $end_of_paragraph );
}

Expand Down
16 changes: 10 additions & 6 deletions lib/Locale/Po4a/TransTractor.pm
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,10 @@ sub get_out_charset {
return 'UTF-8';
}

# See also "2.2 Structures" section[1].
# [1] https://yaml.org/spec/1.2.2/#22-structures
use constant YAML_SEPARATOR => "---";

# Push the translation of a Yaml document or Yaml Front-Matter header, parsed by YAML::Tiny in any case
# $is_yfm is a boolean indicating whether we are dealing with a Front Matter (true value) or whole document (false value)
sub handle_yaml {
Expand All @@ -1180,13 +1184,13 @@ sub handle_yaml {

# An empty document
if ( !defined $cursor ) {
$self->pushline("---\n");
$self->pushline( YAML_SEPARATOR . "\n" );

# Do nothing

# A scalar document
} elsif ( !ref $cursor ) {
$self->pushline("---\n");
$self->pushline( YAML_SEPARATOR . "\n" );
$self->pushline(
format_scalar(
$self->translate(
Expand All @@ -1200,19 +1204,19 @@ sub handle_yaml {
# A list at the root
} elsif ( ref $cursor eq 'ARRAY' ) {
if (@$cursor) {
$self->pushline("---\n");
$self->pushline( YAML_SEPARATOR . "\n" );
do_array( $self, $is_yfm, $blockref, $cursor, $indent, $ctx, $yfm_keys, $yfm_skip_array, $yfm_paths );
} else {
$self->pushline("---[]\n");
$self->pushline( YAML_SEPARATOR . "[]\n" );
}

# A hash at the root
} elsif ( ref $cursor eq 'HASH' ) {
if (%$cursor) {
$self->pushline("---\n");
$self->pushline( YAML_SEPARATOR . "\n" );
do_hash( $self, $is_yfm, $blockref, $cursor, $indent, $ctx, $yfm_keys, $yfm_skip_array, $yfm_paths );
} else {
$self->pushline("--- {}\n");
$self->pushline( YAML_SEPARATOR . " {}\n" );
}

} else {
Expand Down
Loading
Loading