diff options
author | Thomas Schwinge <thomas@schwinge.name> | 2010-09-09 14:06:35 +0200 |
---|---|---|
committer | Thomas Schwinge <thomas@schwinge.name> | 2010-09-09 14:06:35 +0200 |
commit | 3d846b2d27e80386e058b0ca9ef2739674ef7736 (patch) | |
tree | e186aedf577300ad9e97f45d3bb694351a57a336 /.library/IkiWiki/Plugin | |
parent | c65b9994127092b64c3a61e669db16d81a9d2a1c (diff) |
Add (part of) YAML field infrastructure.
New files copied from a2482475a7ed0b6f54df10a87a8665e2361f3598 of
<git://github.com/rubykat/ikiplugins.git> by Kathryn Andersen
<perlkat@katspace.org>.
Diffstat (limited to '.library/IkiWiki/Plugin')
-rw-r--r-- | .library/IkiWiki/Plugin/field.pm | 662 | ||||
-rw-r--r-- | .library/IkiWiki/Plugin/getfield.pm | 123 | ||||
-rw-r--r-- | .library/IkiWiki/Plugin/ymlfront.pm | 426 |
3 files changed, 1211 insertions, 0 deletions
diff --git a/.library/IkiWiki/Plugin/field.pm b/.library/IkiWiki/Plugin/field.pm new file mode 100644 index 00000000..e53474e9 --- /dev/null +++ b/.library/IkiWiki/Plugin/field.pm @@ -0,0 +1,662 @@ +#!/usr/bin/perl +# Ikiwiki field plugin. +# See doc/plugin/contrib/field.mdwn for documentation. +package IkiWiki::Plugin::field; +use warnings; +use strict; +=head1 NAME + +IkiWiki::Plugin::field - front-end for per-page record fields. + +=head1 VERSION + +This describes version B<0.05> of IkiWiki::Plugin::field + +=cut + +our $VERSION = '0.05'; + +=head1 PREREQUISITES + + IkiWiki + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009-2010 Kathryn Andersen + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + +use IkiWiki 3.00; + +my %Fields = ( + _first => { + id => '_first', + seq => 'BB', + }, + _last => { + id => '_last', + seq => 'YY', + }, + _middle => { + id => '_middle', + seq => 'MM', + }, +); +my @FieldsLookupOrder = (); + +my %Cache = (); + +sub field_get_value ($$); + +sub import { + hook(type => "getsetup", id => "field", call => \&getsetup); + hook(type => "checkconfig", id => "field", call => \&checkconfig); + hook(type => "scan", id => "field", call => \&scan, last=>1); + hook(type => "pagetemplate", id => "field", call => \&pagetemplate); +} + +# =============================================== +# Hooks +# --------------------------- +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + }, + field_register => { + type => "hash", + example => "field_register => {meta => 'last'}", + description => "simple registration of fields by plugin", + safe => 0, + rebuild => undef, + }, + field_allow_config => { + type => "boolean", + example => "field_allow_config => 1", + description => "allow config settings to be queried", + safe => 0, + rebuild => undef, + }, + field_tags => { + type => "hash", + example => "field_tags => {BookAuthor => '/books/authors'}", + description => "fields flagged as tag-fields", + safe => 0, + rebuild => undef, + }, +} + +sub checkconfig () { + # use the simple by-plugin pagestatus method for + # those plugins registered with the field_register config option. + if (defined $config{field_register}) + { + if (ref $config{field_register} eq 'ARRAY') + { + foreach my $id (@{$config{field_register}}) + { + field_register(id=>$id); + } + } + elsif (ref $config{field_register} eq 'HASH') + { + foreach my $id (keys %{$config{field_register}}) + { + field_register(id=>$id, order=>$config{field_register}->{$id}); + } + } + else + { + field_register(id=>$config{field_register}); + } + } + if (!defined $config{field_allow_config}) + { + $config{field_allow_config} = 0; + } +} # checkconfig + +sub scan (@) { + my %params=@_; + my $page=$params{page}; + my $content=$params{content}; + + # scan for tag fields + if ($config{field_tags}) + { + foreach my $field (sort keys %{$config{field_tags}}) + { + my @values = field_get_value($field, $page); + if (@values) + { + foreach my $tag (@values) + { + if ($tag) + { + my $link = $config{field_tags}{$field} . '/' + . titlepage($tag); + add_link($page, $link, lc($field)); + } + } + } + } + } +} # scan + +sub pagetemplate (@) { + my %params=@_; + my $page=$params{page}; + my $template=$params{template}; + + field_set_template_values($template, $page); +} # pagetemplate + +# =============================================== +# Field interface +# --------------------------- + +sub field_register (%) { + my %param=@_; + if (!exists $param{id}) + { + error 'field_register requires id parameter'; + return 0; + } + if (exists $param{call} and !ref $param{call}) + { + error 'field_register call parameter must be function'; + return 0; + } + + $Fields{$param{id}} = \%param; + if (!exists $param{call}) + { + # closure to get the data from the pagestate hash + $Fields{$param{id}}->{call} = sub { + my $field_name = shift; + my $page = shift; + if (exists $pagestate{$page}{$param{id}}{$field_name}) + { + return (wantarray + ? ($pagestate{$page}{$param{id}}{$field_name}) + : $pagestate{$page}{$param{id}}{$field_name}); + } + elsif (exists $pagestate{$page}{$param{id}}{lc($field_name)}) + { + return (wantarray + ? ($pagestate{$page}{$param{id}}{lc($field_name)}) + : $pagestate{$page}{$param{id}}{lc($field_name)}); + } + return undef; + }; + } + # add this to the ordering hash + # first, last, order; by default, middle + my $when = ($param{first} + ? '_first' + : ($param{last} + ? '_last' + : ($param{order} + ? ($param{order} eq 'first' + ? '_first' + : ($param{order} eq 'last' + ? '_last' + : ($param{order} eq 'middle' + ? '_middle' + : $param{order} + ) + ) + ) + : '_middle' + ) + )); + add_lookup_order($param{id}, $when); + return 1; +} # field_register + +sub field_get_value ($$) { + my $field_name = shift; + my $page = shift; + + # This will return the first value it finds + # where the value returned is not undefined. + # This will return an array of values if wantarray is true. + + # The reason why it checks every registered plugin rather than have + # plugins declare which fields they know about, is that it is quite + # possible that a plugin doesn't know, ahead of time, what fields + # will be available; for example, a YAML format plugin would return + # any field that happens to be defined in a YAML page file, which + # could be anything! + + my $value = undef; + my @array_value = undef; + + # check the cache first + if (exists $Cache{$page}{$field_name} + and defined $Cache{$page}{$field_name}) + { + return (wantarray + ? @{$Cache{$page}{$field_name}{array}} + : $Cache{$page}{$field_name}{scalar}); + } + + if (!@FieldsLookupOrder) + { + build_fields_lookup_order(); + } + foreach my $id (@FieldsLookupOrder) + { + $value = $Fields{$id}{call}->($field_name, $page); + @array_value = $Fields{$id}{call}->($field_name, $page); + if (defined $value) + { + last; + } + } + + # extra definitions + if (!defined $value) + { + # Exception for titles + # If the title hasn't been found, construct it + if ($field_name eq 'title') + { + $value = pagetitle(IkiWiki::basename($page)); + } + # and set "page" if desired + elsif ($field_name eq 'page') + { + $value = $page; + } + # the page above this page; aka the current directory + elsif ($field_name eq 'parent_page') + { + if ($page =~ m{^(.*)/[-\.\w]+$}) + { + $value = $1; + } + } + elsif ($field_name eq 'basename') + { + $value = IkiWiki::basename($page); + } + elsif ($config{field_allow_config} + and $field_name =~ /^config-(.*)$/i) + { + my $cfield = $1; + if (exists $config{$cfield}) + { + $value = $config{$cfield}; + } + } + elsif ($field_name =~ /^(.*)-tagpage$/) + { + my $real_fn = $1; + if (exists $config{field_tags}{$real_fn} + and defined $config{field_tags}{$real_fn}) + { + my @values = field_get_value($real_fn, $page); + if (@values) + { + foreach my $tag (@values) + { + if ($tag) + { + my $link = $config{field_tags}{$real_fn} . '/' . $tag; + push @array_value, $link; + } + } + $value = join(",", @array_value); + } + } + } + } + if (defined $value) + { + if (!@array_value) + { + @array_value = ($value); + } + # cache the value + $Cache{$page}{$field_name}{scalar} = $value; + $Cache{$page}{$field_name}{array} = \@array_value; + } + return (wantarray ? @array_value : $value); +} # field_get_value + +# set the values for the given HTML::Template template +sub field_set_template_values ($$;@) { + my $template = shift; + my $page = shift; + my %params = @_; + + my $get_value_fn = (exists $params{value_fn} + ? $params{value_fn} + : \&field_get_value); + + # Find the parameter names in this template + # and see if you can find their values. + + # The reason we check the template for field names is because we + # don't know what fields the registered plugins provide; and this is + # reasonable because for some plugins (e.g. a YAML data plugin) they + # have no way of knowing, ahead of time, what fields they might be + # able to provide. + + my @parameter_names = $template->param(); + foreach my $field (@parameter_names) + { + my $type = $template->query(name => $field); + if ($type eq 'LOOP' and $field =~ /_LOOP$/i) + { + # Loop fields want arrays. + # Figure out what field names to look for: + # * names are from the enclosed loop fields + my @loop_fields = $template->query(loop => $field); + + my @loop_vals = (); + my %loop_field_arrays = (); + foreach my $fn (@loop_fields) + { + if ($fn !~ /^__/) # not a special loop variable + { + my @ival_array = $get_value_fn->($fn, $page); + if (@ival_array) + { + $loop_field_arrays{$fn} = \@ival_array; + } + } + } + foreach my $fn (sort keys %loop_field_arrays) + { + my $i = 0; + foreach my $v (@{$loop_field_arrays{$fn}}) + { + if (!defined $loop_vals[$i]) + { + $loop_vals[$i] = {}; + } + $loop_vals[$i]{$fn} = $v; + $i++; + } + } + $template->param($field => \@loop_vals); + } + else # not a loop field + { + my $value = $get_value_fn->($field, $page); + if (defined $value) + { + $template->param($field => $value); + } + } + } +} # field_set_template_values + +# =============================================== +# Private Functions +# --------------------------- + +# Calculate the lookup order +# <module, >module, AZ +# This is crabbed from the PmWiki Markup function +sub add_lookup_order { + my $id = shift; + my $when = shift; + + # may have given an explicit ordering + if ($when =~ /^[A-Z][A-Z]$/) + { + $Fields{$id}{seq} = $when; + } + else + { + my $cmp = '='; + my $seq_field = $when; + if ($when =~ /^([<>])(.+)$/) + { + $cmp = $1; + $seq_field = $2; + } + $Fields{$seq_field}{dep}{$id} = $cmp; + if (exists $Fields{$seq_field}{seq} + and defined $Fields{$seq_field}{seq}) + { + $Fields{$id}{seq} = $Fields{$seq_field}{seq} . $cmp; + } + } + if ($Fields{$id}{seq}) + { + foreach my $i (keys %{$Fields{$id}{dep}}) + { + my $m = $Fields{$id}{dep}{$i}; + add_lookup_order($i, "$m$id"); + } + delete $Fields{$id}{dep}; + } +} + +sub build_fields_lookup_order { + + # remove the _first, _last and _middle dummy fields + # because we don't need them anymore + delete $Fields{_first}; + delete $Fields{_last}; + delete $Fields{_middle}; + my %lookup_spec = (); + # Make a hash of the lookup sequences + foreach my $id (sort keys %Fields) + { + my $seq = ($Fields{$id}{seq} + ? $Fields{$id}{seq} + : 'MM'); + if (!exists $lookup_spec{$seq}) + { + $lookup_spec{$seq} = {}; + } + $lookup_spec{$seq}{$id} = 1; + } + + # get the field-lookup order by (a) sorting by lookup_spec + # and (b) sorting by field-name for the fields that registered + # the same field-lookup order + foreach my $ord (sort keys %lookup_spec) + { + push @FieldsLookupOrder, sort keys %{$lookup_spec{$ord}}; + } +} # build_fields_lookup_order + +# match field funcs +# page-to-check, wanted +sub match_a_field ($$) { + my $page=shift; + my $wanted=shift; + + # The field name is first; the rest is the match + my $field_name; + my $glob; + if ($wanted =~ /^(\w+)\s+(.*)$/) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + + # turn glob into a safe regexp + my $re=IkiWiki::glob2re($glob); + + my $val = IkiWiki::Plugin::field::field_get_value($field_name, $page); + + if (defined $val) { + if ($val=~/^$re$/i) { + return IkiWiki::SuccessReason->new("$re matches $field_name of $page", $page => $IkiWiki::DEPEND_CONTENT, "" => 1); + } + else { + return IkiWiki::FailReason->new("$re does not match $field_name of $page", "" => 1); + } + } + else { + return IkiWiki::FailReason->new("$page does not have a $field_name", "" => 1); + } +} # match_a_field + +# check against individual items of a field +# (treat the field as an array) +# page-to-check, wanted +sub match_a_field_item ($$) { + my $page=shift; + my $wanted=shift; + + # The field name is first; the rest is the match + my $field_name; + my $glob; + if ($wanted =~ /^(\w+)\s+(.*)$/) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + + # turn glob into a safe regexp + my $re=IkiWiki::glob2re($glob); + + my @val_array = IkiWiki::Plugin::field::field_get_value($field_name, $page); + + if (@val_array) + { + foreach my $val (@val_array) + { + if (defined $val) { + if ($val=~/^$re$/i) { + return IkiWiki::SuccessReason->new("$re matches $field_name of $page", $page => $IkiWiki::DEPEND_CONTENT, "" => 1); + } + } + } + # not found + return IkiWiki::FailReason->new("$re does not match $field_name of $page", "" => 1); + } + else { + return IkiWiki::FailReason->new("$page does not have a $field_name", "" => 1); + } +} # match_a_field_item + +# =============================================== +# PageSpec functions +# --------------------------- + +package IkiWiki::PageSpec; + +sub match_field ($$;@) { + my $page=shift; + my $wanted=shift; + return IkiWiki::Plugin::field::match_a_field($page, $wanted); +} # match_field + +sub match_destfield ($$;@) { + my $page=shift; + my $wanted=shift; + my %params=@_; + + return IkiWiki::FailReason->new("cannot match destpage") unless exists $params{destpage}; + + # Match the field on the destination page, not the source page + return IkiWiki::Plugin::field::match_a_field($params{destpage}, $wanted); +} # match_destfield + +sub match_field_item ($$;@) { + my $page=shift; + my $wanted=shift; + return IkiWiki::Plugin::field::match_a_field_item($page, $wanted); +} # match_field + +sub match_destfield_item ($$;@) { + my $page=shift; + my $wanted=shift; + my %params=@_; + + return IkiWiki::FailReason->new("cannot match destpage") unless exists $params{destpage}; + + # Match the field on the destination page, not the source page + return IkiWiki::Plugin::field::match_a_field_item($params{destpage}, $wanted); +} # match_destfield + +sub match_field_tagged ($$;@) { + my $page=shift; + my $wanted=shift; + my %params=@_; + + # The field name is first; the rest is the match + my $field_name; + my $glob; + if ($wanted =~ /^(\w+)\s+(.*)$/) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + return match_link($page, $glob, linktype => lc($field_name), @_); +} + +sub match_destfield_tagged ($$;@) { + my $page=shift; + my $wanted=shift; + my %params=@_; + + return IkiWiki::FailReason->new("cannot match destpage") unless exists $params{destpage}; + + # Match the field on the destination page, not the source page + return IkiWiki::Plugin::field::match_field_tagged($params{destpage}, $wanted); +} + +# =============================================== +# SortSpec functions +# --------------------------- +package IkiWiki::SortSpec; + +sub cmp_field { + my $field = shift; + error(gettext("sort=field requires a parameter")) unless defined $field; + + my $left = IkiWiki::Plugin::field::field_get_value($field, $a); + my $right = IkiWiki::Plugin::field::field_get_value($field, $b); + + $left = "" unless defined $left; + $right = "" unless defined $right; + return $left cmp $right; +} + +sub cmp_field_natural { + my $field = shift; + error(gettext("sort=field requires a parameter")) unless defined $field; + + eval q{use Sort::Naturally}; + error $@ if $@; + + my $left = IkiWiki::Plugin::field::field_get_value($field, $a); + my $right = IkiWiki::Plugin::field::field_get_value($field, $b); + + $left = "" unless defined $left; + $right = "" unless defined $right; + return Sort::Naturally::ncmp($left, $right); +} + +1; diff --git a/.library/IkiWiki/Plugin/getfield.pm b/.library/IkiWiki/Plugin/getfield.pm new file mode 100644 index 00000000..3a967d8e --- /dev/null +++ b/.library/IkiWiki/Plugin/getfield.pm @@ -0,0 +1,123 @@ +#!/usr/bin/perl +# Ikiwiki getfield plugin. +# Substitute field values in the content of the page. +# See plugin/contrib/getfield for documentation. +package IkiWiki::Plugin::getfield; +use strict; +=head1 NAME + +IkiWiki::Plugin::getfield - query the values of fields + +=head1 VERSION + +This describes version B<0.02> of IkiWiki::Plugin::getfield + +=cut + +our $VERSION = '0.02'; + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009 Kathryn Andersen + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut + +use IkiWiki 3.00; + +sub import { + hook(type => "getsetup", id => "getfield", call => \&getsetup); + hook(type => "filter", id => "getfield", call => \&do_filter, last=>1); + + IkiWiki::loadplugin("field"); +} + +#--------------------------------------------------------------- +# Hooks +# -------------------------------- + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + }, +} + +sub do_filter (@) { + my %params=@_; + my $page = $params{page}; + my $destpage = ($params{destpage} ? $params{destpage} : $params{page}); + + my $page_file=$pagesources{$page}; + my $page_type=pagetype($page_file); + if (defined $page_type) + { + while ($params{content} =~ /{{\$([-\w\/]+#)?[-\w]+}}/) + { + # substitute {{$var}} variables (source-page) + $params{content} =~ s/{{\$([-\w]+)}}/get_field_value($1,$page)/eg; + + # substitute {{$page#var}} variables (source-page) + $params{content} =~ s/{{\$([-\w\/]+)#([-\w]+)}}/get_other_page_field_value($2,$page,$1)/eg; + } + } + + $page_file=$pagesources{$destpage}; + $page_type=pagetype($page_file); + if (defined $page_type) + { + while ($params{content} =~ /{{\+\$([-\w\/]+#)?[-\w]+\+}}/) + { + # substitute {{+$var+}} variables (dest-page) + $params{content} =~ s/{{\+\$([-\w]+)\+}}/get_field_value($1,$destpage)/eg; + # substitute {{+$page#var+}} variables (source-page) + $params{content} =~ s/{{\+\$([-\w\/]+)#([-\w]+)\+}}/get_other_page_field_value($2,$destpage,$1)/eg; + } + } + + return $params{content}; +} # do_filter + +#--------------------------------------------------------------- +# Private functions +# -------------------------------- +sub get_other_page_field_value ($$$) { + my $field = shift; + my $page = shift; + my $other_page = shift; + + my $use_page = bestlink($page, $other_page); + my $val = get_field_value($field, $use_page); + if ($val eq $field) + { + return "${other_page}#$field"; + } + return $val; + +} # get_other_page_field_value + +sub get_field_value ($$) { + my $field = shift; + my $page = shift; + + my $value = IkiWiki::Plugin::field::field_get_value($field,$page); + return $value if defined $value; + + # if there is no value, return the field name. + return $field; +} # get_field_value + +1; diff --git a/.library/IkiWiki/Plugin/ymlfront.pm b/.library/IkiWiki/Plugin/ymlfront.pm new file mode 100644 index 00000000..3811591b --- /dev/null +++ b/.library/IkiWiki/Plugin/ymlfront.pm @@ -0,0 +1,426 @@ +#!/usr/bin/perl +# YAML format for structured data +# See plugins/contrib/ymlfront for documentation. +package IkiWiki::Plugin::ymlfront; +use warnings; +use strict; +=head1 NAME + +IkiWiki::Plugin::ymlfront - add YAML-format data to a page + +=head1 VERSION + +This describes version B<0.03> of IkiWiki::Plugin::ymlfront + +=cut + +our $VERSION = '0.03'; + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + YAML::Any + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009 Kathryn Andersen + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +=cut +use IkiWiki 3.00; + +sub import { + hook(type => "getsetup", id => "ymlfront", call => \&getsetup); + hook(type => "checkconfig", id => "ymlfront", call => \&checkconfig); + hook(type => "filter", id => "ymlfront", call => \&filter, first=>1); + hook(type => "preprocess", id => "ymlfront", call => \&preprocess, scan=>1); + hook(type => "scan", id => "ymlfront", call => \&scan); + hook(type => "checkcontent", id => "ymlfront", call => \&checkcontent); + + IkiWiki::loadplugin('field'); + IkiWiki::Plugin::field::field_register(id=>'ymlfront', + call=>\&yml_get_value, + first=>1); +} + +# ------------------------------------------------------------ +# Hooks +# -------------------------------- +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 1, + }, + ymlfront_delim => { + type => "array", + example => "ymlfront_sep => [qw(--YAML-START-- --YAML-END--)]", + description => "delimiters of YAML data", + safe => 0, + rebuild => undef, + }, +} + +sub checkconfig () { + eval q{use YAML::Any}; + eval q{use YAML} if $@; + if ($@) + { + return error ("ymlfront: failed to use YAML::Any or YAML"); + } + + $YAML::UseBlock = 1; + $YAML::Syck::ImplicitUnicode = 1; + + if (!defined $config{ymlfront_delim}) + { + $config{ymlfront_delim} = [qw(--- ---)]; + } +} # checkconfig + +# scan gets called before filter +sub scan (@) { + my %params=@_; + my $page = $params{page}; + + my $page_file=$pagesources{$page} || return; + my $page_type=pagetype($page_file); + if (!defined $page_type) + { + return; + } + # clear the old data + if (exists $pagestate{$page}{ymlfront}) + { + delete $pagestate{$page}{ymlfront}; + } + my $parsed_yml = parse_yml(%params); + if (defined $parsed_yml + and defined $parsed_yml->{yml}) + { + # save the data to pagestate + foreach my $fn (keys %{$parsed_yml->{yml}}) + { + my $fval = $parsed_yml->{yml}->{$fn}; + $pagestate{$page}{ymlfront}{$fn} = $fval; + } + } + # update meta hash + if (exists $pagestate{$page}{ymlfront}{title} + and $pagestate{$page}{ymlfront}{title}) + { + $pagestate{$page}{meta}{title} = $pagestate{$page}{ymlfront}{title}; + } + if (exists $pagestate{$page}{ymlfront}{description} + and $pagestate{$page}{ymlfront}{description}) + { + $pagestate{$page}{meta}{description} = $pagestate{$page}{ymlfront}{description}; + } + if (exists $pagestate{$page}{ymlfront}{author} + and $pagestate{$page}{ymlfront}{author}) + { + $pagestate{$page}{meta}{author} = $pagestate{$page}{ymlfront}{author}; + } +} # scan + +# use this for data in a [[!ymlfront ...]] directive +sub preprocess (@) { + my %params=@_; + my $page = $params{page}; + + if (! exists $params{data} + or ! defined $params{data} + or !$params{data}) + { + error gettext("missing data parameter") + } + # All the work of this is done in scan mode; + # when in preprocessing mode, just return an empty string. + my $scan=! defined wantarray; + + if (!$scan) + { + return ''; + } + + # clear the old data + if (exists $pagestate{$page}{ymlfront}) + { + delete $pagestate{$page}{ymlfront}; + } + my $parsed_yml = parse_yml(%params); + if (defined $parsed_yml + and defined $parsed_yml->{yml}) + { + # save the data to pagestate + foreach my $fn (keys %{$parsed_yml->{yml}}) + { + my $fval = $parsed_yml->{yml}->{$fn}; + $pagestate{$page}{ymlfront}{$fn} = $fval; + } + } + # update meta hash + if (exists $pagestate{$page}{ymlfront}{title} + and $pagestate{$page}{ymlfront}{title}) + { + $pagestate{$page}{meta}{title} = $pagestate{$page}{ymlfront}{title}; + } + if (exists $pagestate{$page}{ymlfront}{description} + and $pagestate{$page}{ymlfront}{description}) + { + $pagestate{$page}{meta}{description} = $pagestate{$page}{ymlfront}{description}; + } + if (exists $pagestate{$page}{ymlfront}{author} + and $pagestate{$page}{ymlfront}{author}) + { + $pagestate{$page}{meta}{author} = $pagestate{$page}{ymlfront}{author}; + } + return ''; +} # preprocess + +sub filter (@) { + my %params=@_; + my $page = $params{page}; + + my $page_file=$pagesources{$page} || return $params{content}; + my $page_type=pagetype($page_file); + if (!defined $page_type) + { + return $params{content}; + } + my $parsed_yml = parse_yml(%params); + if (defined $parsed_yml + and defined $parsed_yml->{yml} + and defined $parsed_yml->{content}) + { + $params{content} = $parsed_yml->{content}; + # also check for a content value + if (exists $pagestate{$page}{ymlfront}{content} + and defined $pagestate{$page}{ymlfront}{content} + and $pagestate{$page}{ymlfront}{content}) + { + $params{content} .= $pagestate{$page}{ymlfront}{content}; + } + } + + return $params{content}; +} # filter + +# check the correctness of the YAML code before saving a page +sub checkcontent { + my %params=@_; + my $page = $params{page}; + + my $page_file=$pagesources{$page}; + if ($page_file) + { + my $page_type=pagetype($page_file); + if (!defined $page_type) + { + return undef; + } + } + my $parsed_yml = parse_yml(%params); + if (!defined $parsed_yml) + { + debug("ymlfront: Save of $page failed: $@"); + return gettext("YAML data incorrect: $@"); + } + return undef; +} # checkcontent + +# ------------------------------------------------------------ +# Field functions +# -------------------------------- +sub yml_get_value ($$) { + my $field_name = shift; + my $page = shift; + + my $value = undef; + if (exists $pagestate{$page}{ymlfront}{$field_name}) + { + $value = $pagestate{$page}{ymlfront}{$field_name}; + } + elsif (exists $pagestate{$page}{ymlfront}{lc($field_name)}) + { + $value = $pagestate{$page}{ymlfront}{lc($field_name)}; + } + if (defined $value) + { + if (ref $value) + { + my @value_array = @{$value}; + return (wantarray + ? @value_array + : join(",", @value_array)); + } + else + { + return (wantarray ? ($value) : $value); + } + } + return undef; +} # yml_get_value + +# ------------------------------------------------------------ +# Helper functions +# -------------------------------- + +# parse the YAML data from the given content +# Expects page, content +# Returns { yml=>%yml_data, content=>$content } or undef +sub parse_yml { + my %params=@_; + my $page = $params{page}; + my $content = $params{content}; + + my $page_file=$pagesources{$page}; + if ($page_file) + { + my $page_type=pagetype($page_file); + if (!defined $page_type) + { + return undef; + } + } + my $start_of_content = ''; + my $yml_str = ''; + my $rest_of_content = ''; + if ($params{data}) + { + $yml_str = $params{data}; + } + elsif ($content) + { + my $regex = qr{ + (\\?) # 1: escape? + \[\[(!) # directive open; 2: prefix + (ymlfront) # 3: command + ( # 4: the parameters.. + \s+ # Must have space if parameters present + (?: + (?:[-\w]+=)? # named parameter key? + (?: + """.*?""" # triple-quoted value + | + "[^"]*?" # single-quoted value + | + [^"\s\]]+ # unquoted value + ) + \s* # whitespace or end + # of directive + ) + *)? # 0 or more parameters + \]\] # directive closed + }sx; + my $ystart = $config{ymlfront_delim}[0]; + my $yend = $config{ymlfront_delim}[1]; + if ($ystart eq '---' + and $yend eq '---' + and $content =~ /^---[\n\r](.*?[\n\r])---[\n\r](.*)$/s) + { + $yml_str = $1; + $rest_of_content = $2; + } + elsif ($content =~ /^(.*?)${ystart}[\n\r](.*?[\n\r])${yend}([\n\r].*)$/s) + { + $yml_str = $2; + $rest_of_content = $1 . $3; + } + elsif ($content =~ /$regex/) + { + my $escape=$1; + my $prefix=$2; + my $command=$3; + my $params=$4; + if ($escape) + { + $rest_of_content = $content; + } + else + { + my %phash = (); + while ($params =~ m{ + (?:([-\w]+)=)? # 1: named parameter key? + (?: + """(.*?)""" # 2: triple-quoted value + | + "([^"]*?)" # 3: single-quoted value + | + (\S+) # 4: unquoted value + ) + (?:\s+|$) # delimiter to next param + }sgx) { + my $key=$1; + my $val; + if (defined $2) { + $val=$2; + $val=~s/\r\n/\n/mg; + $val=~s/^\n+//g; + $val=~s/\n+$//g; + } + elsif (defined $3) { + $val=$3; + } + elsif (defined $4) { + $val=$4; + } + + if (defined $key) { + $phash{$key} = $val; + } + else { + $phash{''} = $val; + } + } + if (defined $phash{data}) + { + $yml_str = $phash{data}; + $content =~ /^(.*?)\[\[!ymlfront.*?\]\](.*?)$/s; + $start_of_content = $1; + $rest_of_content = $2; + } + } + } + } + if ($yml_str) + { + # if {{$page}} is there, do an immediate substitution + $yml_str =~ s/\{\{\$page\}\}/$page/sg; + + my $ydata; + eval q{$ydata = Load($yml_str);}; + if ($@) + { + debug("ymlfront: Load of $page failed: $@"); + return undef; + } + if (!$ydata) + { + debug("ymlfront: no YAML for $page"); + return undef; + } + my %lc_data = (); + if ($ydata) + { + # make lower-cased versions of the data + foreach my $fn (keys %{$ydata}) + { + my $fval = $ydata->{$fn}; + $lc_data{lc($fn)} = $fval; + } + } + return { yml=>\%lc_data, + content=>$start_of_content . $rest_of_content}; + } + return { yml=>undef, content=>$content }; +} # parse_yml +1; |