diff --git a/Markdown.pl b/Markdown.pl
index fbcef4c..a62b5bf 100755
--- a/Markdown.pl
+++ b/Markdown.pl
@@ -285,6 +285,27 @@ sub _strip {
$str;
}
+my %_yamlmode;
+BEGIN {%_yamlmode = (
+ disable => 0,
+ reveal => 1,
+ enable => 1,
+ conceal => 1,
+ show => -1,
+ unknown => -1,
+ strip => -1
+)}
+my %_yamlvis;
+BEGIN {%_yamlvis = (
+ disable => 0,
+ reveal => 1,
+ enable => -1,
+ conceal => 0,
+ show => 1,
+ unknown => -1,
+ strip => 0
+)}
+
#### BBEdit/command-line text filter interface ##########################
sub _main {
local *ARGV = \@_;
@@ -333,6 +354,7 @@ sub _main {
'stylesheet|style-sheet' => \$cli_opts{'stylesheet'},
'no-stylesheet|no-style-sheet' => sub {$cli_opts{'stylesheet'} = 0},
'stub' => \$cli_opts{'stub'},
+ 'yaml:s' => \$cli_opts{'yaml'},
);
defined($cli_opts{'raw'}) or $cli_opts{'raw'} = 0;
my $stub = 0;
@@ -401,6 +423,12 @@ sub _main {
$options{show_styles} = $cli_opts{'stylesheet'} if defined($cli_opts{'stylesheet'});
$options{show_styles} = 1 if $stub && !defined($options{show_styles});
$options{tab_width} = 8 unless defined($options{tab_width});
+ my $ym = $cli_opts{'yaml'};
+ defined($ym) && $ym ne "" or $ym = "enable";
+ my $lcym = lc($ym);
+ exists($_yamlmode{$lcym}) or die "invalid --yaml= value '$ym'\n";
+ $options{yamlmode} = $_yamlmode{$lcym};
+ $options{yamlvis} = $_yamlvis{$lcym};
my $hdrf = sub {
my $out = "";
@@ -551,7 +579,7 @@ sub _trimerr {
sub _PrepareInput {
- my $input = shift;
+ my ($input,$parseyaml) = @_;
defined $input or $input = "";
{
use bytes;
@@ -566,7 +594,43 @@ sub _PrepareInput {
# Standardize line endings:
$output =~ s{\r\n}{\n}g; # DOS to Unix
$output =~ s{\r}{\n}g; # Mac to Unix
- return $output;
+
+ # Extract YAML front matter if requested
+ my $yaml = undef;
+ if ($parseyaml) {
+ $yaml = {};
+ if ($output =~ /^---[ \t]*(?:\n|\z)/g) {
+ until ($output =~ /\G(?:(?:(?:---)|(?:\.\.\.))[ \t]*(?:\n|\z)|\z)/gc) {
+ next if $output =~ m"\G[ \t]*(?:#[^\n]*)?\n"gc; # skip comment lines
+ next if $output =~ m"\G[ \t]*(?:#[^\n]*)\z"gc; # skip final no EOL comment
+ last unless $output =~ /\G([^\n]+)(?:\n|\z)/gc;
+ my $yl = $1;
+ if ($yl =~ /^([A-Za-z_][A-Za-z_0-9.-]*):[ \t]+(.*)$/os) {
+ my ($k, $v) = ($1, $2);
+ $yaml->{lc($k)} = _YAMLvalue($2);
+ }
+ }
+ $output = substr($output, pos($output));
+ }
+ }
+ return wantarray ? ($output, $yaml) : $output;
+}
+
+
+sub _YAMLvalue {
+ my $v = shift;
+ $v =~ s/^\s+//;
+ if (substr($v, 0, 1) eq '"') {
+ # only $ and/or @ present issues, map them
+ $v =~ tr/\@\$/\036\037/;
+ eval '{$v='.$v."\n}1" or $v = undef;
+ $v =~ tr/\036\037/\@\$/ if defined($v);
+ } else {
+ $v =~ s"#.*$""os;
+ $v =~ s/\s+$//os;
+ $v ne "" or $v = undef;
+ }
+ return $v;
}
@@ -656,6 +720,22 @@ sub ProcessRaw {
# note that _main actually adds the style sheet (when
# requested); use GenerateStyleSheet to retrieve the
# fancy style sheet when calling Markdown directly.
+# yamlmode => 0 (no YAML processing), > 0 (YAML on), < 0 (YAML ignore)
+# if 0, the YAML front matter processor is completely
+# disabled and any YAML front matter that might be present
+# will be treated as markup. if > 0 any YAML front matter
+# will be processed and any recognized options applied.
+# if < 0 any YAML front matter will be parsed but no
+# options will be applied at all. When != 0 the parsed
+# YAML front matter can be retrieved via the 'yaml' key.
+# yamlvis => 0 (invisible), > 0 (visible), < 0 (vis if unknown)
+# if yamlmode == 0 then yamlvis has no effect. if > 0
+# then any parsed YAML front matter options will be shown
+# in the formatted output. if 0 then NO YAML front
+# matter options will be shown in the formatted output.
+# if < 0 then YAML front matter options will be shown in
+# the formatted output only if there are any unrecognized
+# options present.
# keepabs => any-false-value (no action), any-true-value (keep)
# if true, any absolute path URLs remaining after applying
# any abs_prefix value will be kept and not be subject
@@ -721,7 +801,17 @@ sub ProcessRaw {
# note that literal
...
values are NOT picked up.
# will be left unchanged if no Markdown-style H1 detected.
# note that the value is NOT xml escaped but should be
-# before embedding in an XHTML document.
+# before embedding in an XHTML document. If yamlmode > 0
+# and a 'title' value has been encountered, then this
+# will be set to that 'title' value instead (and the
+# 'title' key and value will still be present in %$yaml).
+#
+# yaml => if yamlmode is != 0 then this will be set to a HASH
+# ref containing any parsed YAML front matter or left
+# unchanged if no YAML front matter was found. If the
+# parsed YAML front matter contains only whitespace and/or
+# comments then this will be set to a HASH ref that has
+# no keys or values.
#
sub _SanitizeOpts {
my $o = shift; # hashref
@@ -775,6 +865,43 @@ sub _SanitizeOpts {
# picked up and stored (which will not happen if the key
# already exists).
delete $o->{h1};
+
+ # Default is to silently strip any YAML front matter
+ # Same comment about "yaml" key as above for "h1" key
+ $o->{yamlmode} = -1 unless looks_like_number($o->{yamlmode});
+ $o->{yamlvis} = 0 unless looks_like_number($o->{yamlvis});
+ delete $o->{yaml};
+}
+
+my %_yamlopts;
+BEGIN {%_yamlopts = map({$_ => 1} qw(
+ display_metadata
+ header_enum
+ title
+))}
+
+
+sub _HasUnknownYAMLOptions {
+ do { return 1 unless exists($_yamlopts{$_}) } foreach keys(%{$_[0]});
+ return 0;
+}
+
+
+sub _ApplyYAMLOpts {
+ my ($yaml, $opt) = @_;
+ if (defined($yaml->{display_metadata}) && $opt->{yamlvis} < 0) {
+ # ignore display_metadata except in --yaml=enable mode
+ $opt->{yamlvis} = _YAMLTrueValue($yaml->{display_metadata}) ? 1 : 0;
+ }
+ $opt->{h1} = $yaml->{title} if defined($yaml->{title});
+}
+
+
+sub _YAMLTrueValue {
+ my $v = shift;
+ defined($v) or $v = "";
+ $v = lc($v);
+ return !($v eq "" || $v eq "0" || $v eq "false" || $v eq "disable" || $v eq "off" || $v eq "no");
}
@@ -806,7 +933,7 @@ sub Markdown {
# _EscapeSpecialChars(), so that any *'s or _'s in the
# and tags get encoded.
#
- my $text = _PrepareInput(shift);
+ my $text = shift;
# Any remaining arguments after the first are options; either a single
# hashref or a list of name, value pairs. See _SanitizeOpts comments.
@@ -832,6 +959,10 @@ sub Markdown {
}
_SanitizeOpts(\%opt);
+ my $yaml;
+ ($text, $yaml) = _PrepareInput($text, $opt{yamlmode});
+ _ApplyYAMLOpts($yaml, \%opt) if ref($yaml) eq "HASH" && $opt{yamlmode} > 0;
+
# Clear the globals. If we don't clear these, you get conflicts
# from other articles when generating a page which contains more than
# one article (e.g. an index page that shows the N most recent
@@ -883,11 +1014,35 @@ sub Markdown {
$text = _SanitizeTags($text, $opt{xmlcheck} == 2, 1) if $opt{sanitize};
utf8::encode($text);
- if (defined($opt{h1}) && $opt{h1} ne "" && ref($_[0]) eq "HASH") {
- utf8::encode($opt{h1});
- ${$_[0]}{h1} = $opt{h1};
+ if (ref($_[0]) eq "HASH") {
+ if (defined($opt{h1}) && $opt{h1}) {
+ utf8::encode($opt{h1});
+ ${$_[0]}{h1} = $opt{h1};
+ }
+ ${$_[0]}{yaml} = $yaml if ref($yaml) eq "HASH";
}
- return $text;
+ my $yamltable = "";
+ if (ref($yaml) eq "HASH" && %$yaml && $opt{yamlmode} && $opt{yamlvis}) {
+ if ($opt{yamlvis} > 0 || _HasUnknownYAMLOptions($yaml)) {
+ my ($hrows, $drows) = ("", "");
+ foreach (sort(keys(%$yaml))) {
+ my $v = $yaml->{$_};
+ my $rspn = '';
+ if (defined($v)) {
+ $v =~ s/&/&/g;
+ $v =~ s/</g;
+ utf8::encode($v);
+ $drows .= "" . $v . " | \n";
+ } else {
+ $rspn = " class=\"$opt{style_prefix}yaml-undef-value\" rowspan=\"2\" valign=\"top\"";
+ }
+ $hrows .= "" . $_ . " | \n";
+ }
+ $yamltable = "\n" .
+ "\n$hrows
\n\n$drows
\n
\n";
+ }
+ }
+ return $yamltable.$text;
}
@@ -3429,6 +3584,19 @@ table.%(base)table, table.%(base)table th, table.%(base)table td {
border-spacing: 0;
border: thin solid;
}
+table.%(base)yaml-table {
+ border-collapse: collapse;
+}
+table.%(base)yaml-table * {
+ border: thin solid;
+}
+table.%(base)yaml-table th {
+ text-align: center;
+}
+table.%(base)yaml-table th, table.%(base)yaml-table td {
+ padding-left: 0.5ex;
+ padding-right: 0.5ex;
+}
ol.%(base)ol {
counter-reset: %(base)item;
@@ -3590,6 +3758,7 @@ B [B<--help>] [B<--html4tags>] [B<--htmlroot>=I]
-r prefix | --htmlroot=prefix append relative non-img URLs to prefix
-i prefix | --imageroot=prefix append relative img URLs to prefix
-w [wikipat] | --wiki[=wikipat] activate wiki links using wikipat
+ --yaml[=(enable|disable|strip|...)] select YAML front matter processing
-V | --version show version, authors, license
and copyright
-s | --shortversion show just the version number
@@ -3979,6 +4148,103 @@ One of the commonly used wiki platforms does something similar to using C<%{%}>
as the placeholder.
+=item B<--yaml>[=I]
+
+Select YAML front matter processing. The optional I value
+must be one of the following:
+
+=over
+
+=item B
+
+Recognize any YAML front matter and apply any options specified
+therein. If any unrecognized options are present, the options will
+also be shown in the formatted output.
+
+This is the default I if omitted.
+
+=item B
+
+No YAML front matter processing at all takes place. If YAML front
+matter is present, it will be treated as regular non-YAML markup
+text to be processed.
+
+=item B
+
+If YAML front matter is present, it will be stripped and completely
+ignored before beginning to process the rest of the input.
+
+In this mode, any options in the YAML front matter that would have
+otherwise been recognized will I
+
+=item B
+
+If YAML front matter is present and contains anything other than
+comments, the non-comments parts will be shown in the formatted
+output.
+
+In this mode, any options in the YAML front matter that would have
+otherwise been recognized will I
+
+This is a show-only mode.
+
+=item B
+
+This mode works just like the B mode except that if the
+YAML front matter contains anything other than comments, then
+I of the non-comments parts will be shown in the formatted
+output.
+
+In this mode, any recognized options in the YAML front matter I
+processed the same way they would be in the B mode except
+that any option to suppress the B mode is ignored.
+
+=item B
+
+This mode works just like the B mode except that no options
+are ever shown in the formatted output regardless of whether or not
+there are any unrecognized options present.
+
+In this mode, any recognized options in the YAML front matter I
+processed the same way they would be in the B mode except
+that any option to suppress the B mode is ignored.
+
+=item B
+
+This mode works just like the B mode if any unrecognized
+YAML front matter options are present. Otherwise it works like
+the B mode.
+
+In this mode, any options in the YAML front matter that would have
+otherwise been recognized will I
+
+=back
+
+If B<--raw>, B<--raw-xml> or B<--raw-html> has been specified then
+the default if no B<--yaml> option has been given is B<--yaml=disable>.
+
+Otherwise the default if no B<--yaml> option has been given is
+B<--yaml=enable>.
+
+Note that only a limited subset of YAML is recognized. Specifically
+only comments, and top-level single-line S> items
+where key must be plain (i.e. non-quoted), start with a letter or
+underscore and contain only letters, underscores, hyphens (C<->),
+periods (C<.>) and digits. Keys are case-insensitive (i.e. converted
+to lowercase). As with YAML, at least one whitespace is required
+between the ":" and the value (unless it's the empty value).
+
+Values may be either plain or double-quoted (single-quoted is not
+recognized). The double-quoted style may use C-style character
+escape codes but may not extend past the end of the line.
+
+For YAML front matter to be recognized, the very first line of the
+document must be exactly three hyphens (C<--->). The YAML terminates
+when a line of three hyphens (C<--->) or a line of three periods
+(C<...>) or the end of the file is encountered. Of course the YAML
+mode must also be something I than B<--yaml=disable>.
+
+
=item B<-V>, B<--version>
Display Markdown's version number and copyright information.