From 85507860b3739acb9d4c1266c0740b6ca49f97d8 Mon Sep 17 00:00:00 2001 From: Christian Walde Date: Fri, 28 Oct 2011 17:48:30 +0200 Subject: [PATCH 1/2] added ftemplate and ymlfront plugins --- .ikiwiki/IkiWiki/Plugin/field.pm | 999 +++++++++++++++++++++++++++ .ikiwiki/IkiWiki/Plugin/ftemplate.pm | 110 +++ .ikiwiki/IkiWiki/Plugin/getfield.pm | 134 ++++ .ikiwiki/IkiWiki/Plugin/pod.pm | 105 +++ .ikiwiki/IkiWiki/Plugin/report.pm | 651 +++++++++++++++++ .ikiwiki/IkiWiki/Plugin/xslt.pm | 100 +++ .ikiwiki/IkiWiki/Plugin/ymlfront.pm | 429 ++++++++++++ PerlTutorialHub.setup | 2 +- 8 files changed, 2529 insertions(+), 1 deletion(-) create mode 100644 .ikiwiki/IkiWiki/Plugin/field.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/ftemplate.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/getfield.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/pod.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/report.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/xslt.pm create mode 100644 .ikiwiki/IkiWiki/Plugin/ymlfront.pm diff --git a/.ikiwiki/IkiWiki/Plugin/field.pm b/.ikiwiki/IkiWiki/Plugin/field.pm new file mode 100644 index 0000000..b5d02cb --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/field.pm @@ -0,0 +1,999 @@ +#!/usr/bin/perl +# Ikiwiki field plugin. +package IkiWiki::Plugin::field; +use warnings; +use strict; +=head1 NAME + +IkiWiki::Plugin::field - middle-end for per-page record fields. + +=head1 VERSION + +This describes version B<1.20110906> of IkiWiki::Plugin::field + +=cut + +our $VERSION = '1.20110906'; + +=head1 DESCRIPTION + +Used by other plugins as an interface; treats each page as +a record which can have multiple fields. + +See doc/plugin/contrib/field.mdwn for documentation. + +=head1 PREREQUISITES + + IkiWiki + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009-2011 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 = (); + +sub field_get_value ($$;@); + +sub import { + hook(type => "getsetup", id => "field", call => \&getsetup); + hook(type => "checkconfig", id => "field", call => \&checkconfig); + hook(type => "needsbuild", id => "field", call => \&needsbuild); + hook(type => "preprocess", id => "field", call => \&preprocess, scan=>1); + 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}); + } + } + # also register the "field" directive + field_register(id=>'field_directive'); + + if (!defined $config{field_allow_config}) + { + $config{field_allow_config} = 0; + } +} # checkconfig + +sub needsbuild (@) { + my ($needsbuild, $deleted) = @_; + + # Non-page files need to have their fields cleared, because they won't + # be re-scanned in the scan pass, and we know at this point + # that they HAVE changed, so their data is out of date. + foreach my $file (@{$needsbuild}) + { + my $page=pagename($file); + my $page_type = pagetype($file); + if (!$page_type) + { + if (exists $pagestate{$page}{field}) + { + delete $pagestate{$page}{field}; + } + } + } +} # needsbuild + +sub preprocess (@) { + my %params= @_; + + # add the content of the field directive to the fields + if (!defined wantarray) # scanning + { + my $page_type = pagetype($pagesources{$params{page}}); + # perform htmlizing on content on HTML pages + $page_type = $config{default_pageext} if $page_type eq 'html'; + + foreach my $key (keys %params) + { + if ($key =~ /^(page|destpage|preview|_raw)$/) # skip non-fieldname things + { + next; + } + my $value = $params{$key}; + if ($value and !$params{_raw}) + { + # HTMLize the text + $value = IkiWiki::htmlize($params{page}, + $params{destpage}, + $page_type, + $value) unless (!$page_type); + + # Preprocess the text to expand any preprocessor directives + # embedded inside it. + # First in scan mode, then in real mode + my $fake_value = IkiWiki::preprocess + ($params{page}, + $params{destpage}, + IkiWiki::filter($params{page}, + $params{destpage}, + $value) + ); + ($value) = IkiWiki::preprocess + ($params{page}, + $params{destpage}, + IkiWiki::filter($params{page}, + $params{destpage}, + $value) + ); + } + $pagestate{$params{page}}{field_directive}{$key} = $value; + } + } + return ''; +} # preprocess + +sub scan (@) { + my %params=@_; + + remember_values(%params); + scan_for_tags(%params); +} # scan + + +sub pagetemplate (@) { + my %params=@_; + + field_set_template_values($params{template}, $params{page}); +} # pagetemplate + +sub deleted (@) { + my @files=@_; + + foreach my $file (@files) + { + my $page=pagename($file); + delete $IkiWiki::pagestate{$page}{field}; + } +} # deleted + +# =============================================== +# Field interface +# --------------------------- + +sub field_register (%) { + my %param=@_; + if (!exists $param{id}) + { + error 'field_register requires id parameter'; + return 0; + } + if (exists $param{all_values} and !ref $param{all_values}) + { + error 'field_register all_values parameter must be function'; + return 0; + } + if (exists $param{call} and !exists $param{all_values}) + { + error 'field_register "call" is obsolete, use "all_values"'; + return 0; + } + + my $id = $param{id}; + $Fields{$id} = \%param; + + # 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($id, $when); + return 1; +} # field_register + +sub field_get_value ($$;@) { + my $field_name = shift; + my $page = shift; + my %params = @_; + + # This expects all values to have been remembered in the scan pass. + # However, non-pages will not have been scanned in the scan pass. + # But non-pages could still have derived values, so check. + + my $pagesource = $pagesources{$page}; + return undef unless $pagesource; + my $page_type = pagetype($pagesource); + + if (!$page_type and !fs_page_is_set($page)) + { + remember_values(%params, page=>$page, content=>''); + } + + my $lc_field_name = lc($field_name); + + if (exists $params{$lc_field_name}) + { + my $value = $params{$lc_field_name}; + return $value; + } + else + { + my $value = fs_get_value($page, $lc_field_name); + return $value; + } + return undef; +} # field_get_value + +sub field_set_template_values ($$;@) { + my $template = shift; + my $page = shift; + my %params = @_; + + # This expects all values to have been remembered in the scan pass. + # However, non-pages will not have been scanned in the scan pass. + # But non-pages could still have derived values, so check. + my $pagesource = $pagesources{$page}; + return undef unless $pagesource; + my $page_type = pagetype($pagesource); + if (!$page_type and !fs_page_is_set($page)) + { + remember_values(%params, page=>$page, content=>''); + } + + my %vals = fs_get_values($page); + if (%vals) + { + my @parameter_names = $template->param(); + foreach my $field (@parameter_names) + { + # Don't redefine if the field already has a value set. + next if ($template->param($field)); + # Passed-in parameters take precedence + my $value = ( + (exists $params{$field} and defined $params{$field}) + ? $params{$field} + : ((exists $vals{$field} and defined $vals{$field}) + ? $vals{$field} + : undef + ) + ); + if (defined $value) + { + $template->param($field => $value); + } + } + } +} # field_set_template_values + +# =============================================== +# Private Functions +# --------------------------- + +sub scan_for_tags (@) { + my %params=@_; + my $page=$params{page}; + my $content=$params{content}; + + # scan for tag fields - the field values should be set now + if ($config{field_tags}) + { + foreach my $field (keys %{$config{field_tags}}) + { + my $lc_field = lc($field); + my $loop_val = field_get_value("${lc_field}_loop", $page); + if ($loop_val) + { + my @loop = @{$loop_val}; + for (my $i = 0; $i < @loop; $i++) + { + my $tag = $loop[$i]->{$lc_field}; + my $link = $config{field_tags}{$field} . '/' + . titlepage($tag); + add_link($page, $link, $lc_field); + } + } + } + } +} # scan_for_tags + +sub remember_values (@) { + my %params=@_; + my $page=$params{page}; + my $content=$params{content}; + + # get all the values for this page + + if (!@FieldsLookupOrder) + { + build_fields_lookup_order(); + } + + my $pagesource = $pagesources{$page}; + return undef unless $pagesource; + my $page_type = pagetype($pagesource); + + add_standard_values($page, $page_type); + + my %values = fs_get_values($page); + foreach my $id (@FieldsLookupOrder) + { + my %vals = (); + if (exists $Fields{$id}{all_values} and exists $pagestate{$page}{$id}) + { + # get both sets of values + my $tvals = $Fields{$id}{all_values}->(%params); + %vals = %{$tvals} if defined $tvals; + foreach my $k (keys %{$pagestate{$page}{$id}}) + { + $vals{$k} = $pagestate{$page}{$id}{$k}; + } + } + elsif (exists $pagestate{$page}{$id}) + { + %vals = %{$pagestate{$page}{$id}}; + } + elsif (exists $Fields{$id}{all_values}) + { + my $tvals = $Fields{$id}{all_values}->(%params); + %vals = %{$tvals} if defined $tvals; + } + # Already-set values have priority + # Remember both scalar and loop values + # Keys are remembered in lower-case + foreach my $key (sort keys %vals) + { + my $lc_key = lc($key); + if (!exists $values{$lc_key}) + { + format_values( + values => \%values, + field=>$lc_key, + value=> $vals{$key}, + page_type=>$page_type, + page=>$page); + } + } + + # Do this here so that later plugins can use the values. + # This is so that one can have values derived from other values. + fs_set_values($page, %values); + + } # for all registered field plugins + + add_derived_values($page, $page_type); +} # remember_values + +# Calculate the lookup order +# 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]$/o) + { + $Fields{$id}{seq} = $when; + } + else + { + my $cmp = '='; + my $seq_field = $when; + if ($when =~ /^([<>])(.+)$/o) + { + $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 + +# Standard values that are always set +# Expects the values for the page NOT to have been figured yet. +sub add_standard_values { + my $page = shift; + my $page_type = shift; + + my %values = (); + + format_values(values=>\%values, + field=>'page_type', + value=>$page_type, + page_type=>$page_type, + page=>$page); + + my @fields = (qw(page parent_page basename)); + foreach my $key (@fields) + { + if (!$values{$key}) + { + my $val = calculated_values($key, $page); + format_values(values=>\%values, + field=>$key, + value=>$val, + page_type=>$page_type, + page=>$page); + } + } + + # config - just remember the scalars + if ($config{field_allow_config}) + { + foreach my $key (keys %config) + { + if ($key =~ /^_/) # private + { + next; + } + my $lc_key = lc($key); + if (!ref $config{$key} and defined $config{$key} and length $config{$key}) + { + $values{"config-${lc_key}"} = $config{$key}; + } + } + } + fs_set_values($page, %values); +} # add_standard_values + +# standard values deduced from other values +# expects the values for the page to be set now +sub add_derived_values { + my $page = shift; + my $page_type = shift; + + my %values = fs_get_values($page); + my @fields = (qw(title titlecaps pagetitle baseurl)); + foreach my $key (@fields) + { + if (!$values{$key}) + { + my $val = calculated_values($key, $page); + format_values(values=>\%values, + field=>$key, + value=>$val, + page_type=>$page_type, + page=>$page); + fs_set_values($page, %values); + } + } + # tagpages + foreach my $key (keys %{$config{field_tags}}) + { + my $lc_key = lc($key); + # Go through the "_loop" variable + # to ensure that arrays are treated properly. + if (exists $values{"${lc_key}_loop"}) + { + my @tagpages = (); + my @loop = (); + my @orig_loop = @{$values{"${lc_key}_loop"}}; + for (my $i = 0; $i < @orig_loop; $i++) + { + my $tag = $orig_loop[$i]->{$lc_key}; + my $link = $config{field_tags}{$key} . '/' . titlepage($tag); + $orig_loop[$i]->{"${lc_key}-tagpage"} = $link; + push @loop, {$lc_key => $link}; + push @tagpages, $link; + } + $values{"${lc_key}-tagpage"} = join(' ', @tagpages); + $values{"${lc_key}-tagpage_loop"} = \@loop; + $values{"${lc_key}_loop"} = \@orig_loop; + } + } + + # set meta values if they haven't been set + foreach my $key (qw{title description copyright author authorurl date updated}) + { + if ((!exists $pagestate{$page}{meta}{$key} + or !defined $pagestate{$page}{meta}{$key}) + and exists $values{$key} + and defined $values{$key} + and $values{$key} + ) + { + if ($key eq 'title' and exists $values{titlesort}) + { + IkiWiki::Plugin::meta::preprocess( + $key=>$values{$key}, + sortas=>$values{titlesort}, + page=>$page); + } + elsif ($key eq 'author' and exists $values{authorsort}) + { + IkiWiki::Plugin::meta::preprocess( + $key=>$values{$key}, + sortas=>$values{authorsort}, + page=>$page); + } + else + { + IkiWiki::Plugin::meta::preprocess( + $key=>$values{$key}, + page=>$page); + } + } + } + + fs_set_values($page, %values); +} # add_derived_values + +# Add values in additional formats +# For example, _loop and _html +sub format_values { + my %params = @_; + + my $values = $params{values}; + my $field = $params{field}; + my $value = $params{value}; + my $page_type = $params{page_type}; + my $page = $params{page}; + + if (ref $value eq 'ARRAY') + { + $values->{${field}} = join(' ', @{$value}); + $values->{"${field}_loop"} = []; + foreach my $v (@{$value}) + { + push @{$values->{"${field}_loop"}}, {$field => $v}; + } + # When HTML-izing an array, make it a list + if ($page_type) + { + $values->{"${field}_html"} = IkiWiki::htmlize($page, $page, + $page_type, + "\n\n* " . join("\n* ", @{$value}) . "\n"); + } + } + elsif (!ref $value) + { + $values->{$field} = $value; + if (defined $value and $value) + { + $values->{"${field}_loop"} = [{$field => $value}]; + if ($page_type) + { + $values->{"${field}_html"} = + IkiWiki::htmlize($page, $page, $page_type, $value); + } + } + } + return $values; +} # format_values + +# standard values deduced from other values +sub calculated_values { + my $field_name = shift; + my $page = shift; + + return undef unless defined $page; + my $value = undef; + + # Exception for titles + # If the title hasn't been found, construct it + if ($field_name eq 'title') + { + $value = pagetitle(IkiWiki::basename($page)); + } + elsif ($field_name eq 'pagetitle') + { + $value = pagetitle(IkiWiki::basename($page)); + } + elsif ($field_name eq 'baseurl') + { + $value = IkiWiki::baseurl($page); + } + elsif ($field_name eq 'titlecaps') + { + $value = field_get_value('title', $page); + $value =~ s/\.\w+$//; # remove extension + $value =~ s/ ( + (^\w) #at the beginning of the line + | # or + (\s\w) #preceded by whitespace + ) + /\U$1/xg; + } + # 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]+$}o) + { + $value = $1; + } + else # top-level page + { + $value = 'index'; + } + } + elsif ($field_name eq 'basename') + { + $value = IkiWiki::basename($page); + } + return (wantarray ? ($value) : $value); +} # calculated_values + +sub field_is_null ($$) { + my $page=shift; + my $field_name=shift; + + my $val = IkiWiki::Plugin::field::field_get_value($field_name, $page); + + # testing if the value is null, undefined etc. + if (defined $val and $val) { + return IkiWiki::FailReason->new("$field_name of $page is not null"); + } + else { + return IkiWiki::SuccessReason->new("$field_name of $page is null"); + } +} # field_is_null + +my %match_a_field_globs = (); + +# 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+(.+)$/o) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + + # turn glob into a safe regexp + if (!exists $match_a_field_globs{$glob}) + { + my $re=IkiWiki::glob2re($glob); + $match_a_field_globs{$glob} = qr/^$re$/i; + } + my $regexp = $match_a_field_globs{$glob}; + + my $val = IkiWiki::Plugin::field::field_get_value($field_name, $page); + + if (defined $val) { + if ($val=~$regexp) { + return IkiWiki::SuccessReason->new("$regexp matches $field_name of $page", $page => $IkiWiki::DEPEND_CONTENT, "" => 1); + } + else { + return IkiWiki::FailReason->new("$regexp does not match $field_name of $page", "" => 1); + } + } + else { + return IkiWiki::FailReason->new("$page does not have a $field_name", "" => 1); + } +} # match_a_field + +my %match_a_field_item_globs = (); + +# 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+(.+)$/o) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + + # turn glob into a safe regexp + if (!exists $match_a_field_globs{$glob}) + { + my $re=IkiWiki::glob2re($glob); + $match_a_field_globs{$glob} = qr/^$re$/i; + } + my $regexp = $match_a_field_globs{$glob}; + + my $val_loop = IkiWiki::Plugin::field::field_get_value("${field_name}_loop", $page); + + if ($val_loop) + { + foreach my $valhash (@{$val_loop}) + { + if (defined $valhash) { + if ($valhash->{lc($field_name)} =~ $regexp) { + return IkiWiki::SuccessReason->new("$regexp matches $field_name of $page", $page => $IkiWiki::DEPEND_CONTENT, "" => 1); + } + } + } + # not found + return IkiWiki::FailReason->new("$regexp 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 + +# =============================================== +# Field Source +# --------------------------- +# are values set for this page? +sub fs_page_is_set { + my ($page) = @_; + + return 0 unless defined $page; + return 0 unless exists $IkiWiki::pagestate{$page}{field}; + return 1; +} # fs_page_is_set + +# get ALL the values for a page +sub fs_get_values { + my ( $page) = @_; + + return () unless defined $page; + return () unless exists $IkiWiki::pagestate{$page}{field}; + return %{$IkiWiki::pagestate{$page}{field}}; +} # fs_get_values + +# set ALL the values for a page +sub fs_set_values { + my ( $page, %values) = @_; + + return 0 unless defined $page; + return 0 unless %values; + + $IkiWiki::pagestate{$page}{field} = \%values; + + return scalar %values; +} # fs_set_values + +sub fs_get_value { + my ( $page, $field ) = @_; + + return undef unless defined $page and defined $field; + return undef unless exists $IkiWiki::pagestate{$page}{field}; + return undef unless exists $IkiWiki::pagestate{$page}{field}{$field}; + return $IkiWiki::pagestate{$page}{field}{$field}; +} # fs_get_value + +# =============================================== +# 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_null ($$;@) { + my $page=shift; + my $wanted=shift; + return IkiWiki::Plugin::field::field_is_null($page, $wanted); +} # match_field_null + +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+(.*)$/o) + { + $field_name = $1; + $glob = $2; + } + else + { + return IkiWiki::FailReason->new("cannot match field"); + } + return match_link($page, $glob, linktype => lc($field_name), @_); +} + +# =============================================== +# 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 {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/.ikiwiki/IkiWiki/Plugin/ftemplate.pm b/.ikiwiki/IkiWiki/Plugin/ftemplate.pm new file mode 100644 index 0000000..9d90ec7 --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/ftemplate.pm @@ -0,0 +1,110 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::ftemplate; +use strict; +=head1 NAME + +IkiWiki::Plugin::ftemplate - field-aware structured template plugin + +=head1 VERSION + +This describes version B<1.20100519> of IkiWiki::Plugin::ftemplate + +=head1 DESCRIPTION + +This uses the "field" plugin to look for values for the template, +as well as the passed-in values. + +See doc/plugins/contrib/ftemplate and ikiwiki/directive/ftemplate for docs. + +=cut + +our $VERSION = '1.20100519'; + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + HTML::Template + Encode + +=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; +use HTML::Template; +use Encode; + +sub import { + hook(type => "getsetup", id => "ftemplate", call => \&getsetup); + hook(type => "preprocess", id => "ftemplate", call => \&preprocess, + scan => 1); + + IkiWiki::loadplugin("field"); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + }, +} + +sub preprocess (@) { + my %params=@_; + + if (! exists $params{id}) { + error gettext("missing id parameter"); + } + + my $template; + eval { + # Do this in an eval because it might fail + # if the template isn't a page in the wiki + $template=template_depends($params{id}, $params{page}, + blind_cache => 1); + }; + if (! $template) { + # look for .tmpl template (in global templates dir) + eval { + $template=template("$params{id}.tmpl", + blind_cache => 1); + }; + if ($@) { + error gettext("failed to process template $params{id}.tmpl:")." $@"; + } + if (! $template) { + + error sprintf(gettext("%s not found"), + htmllink($params{page}, $params{destpage}, + "/templates/$params{id}")); + } + } + delete $params{template}; + + $params{included}=($params{page} ne $params{destpage}); + + IkiWiki::Plugin::field::field_set_template_values($template, $params{page}); + + # This needs to run even in scan mode, in order to process + # links and other metadata includes via the template. + my $scan=! defined wantarray; + + my $output = $template->output; + + return IkiWiki::preprocess($params{page}, $params{destpage}, + IkiWiki::filter($params{page}, $params{destpage}, + $output), $scan); +} + +1; diff --git a/.ikiwiki/IkiWiki/Plugin/getfield.pm b/.ikiwiki/IkiWiki/Plugin/getfield.pm new file mode 100644 index 0000000..d03ff56 --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/getfield.pm @@ -0,0 +1,134 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::getfield; +use strict; +=head1 NAME + +IkiWiki::Plugin::getfield - query the values of fields + +=head1 VERSION + +This describes version B<1.20110906> of IkiWiki::Plugin::getfield + +=cut + +our $VERSION = '1.20110906'; + +=head1 DESCRIPTION + +Ikiwiki getfield plugin. +Substitute field values in the content of the page. + +See plugins/contrib/getfield for documentation. + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009-2011 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} || return $params{content}; + my $page_type=pagetype($page_file); + if (defined $page_type) + { + # substitute {{$var}} variables (source-page) + $params{content} =~ s/(\\?){{\$([-\w]+)}}/get_field_value($1,$2,$page)/eg; + + # substitute {{$page#var}} variables (source-page) + $params{content} =~ s/(\\?){{\$([-\w\/]+)#([-\w]+)}}/get_other_page_field_value($1, $3,$page,$2)/eg; + } + + $page_file=$pagesources{$destpage} || return $params{content}; + $page_type=pagetype($page_file); + if (defined $page_type) + { + # substitute {{+$var+}} variables (dest-page) + $params{content} =~ s/(\\?){{\+\$([-\w]+)\+}}/get_field_value($1,$2,$destpage)/eg; + # substitute {{+$page#var+}} variables (source-page) + $params{content} =~ s/(\\?){{\+\$([-\w\/]+)#([-\w]+)\+}}/get_other_page_field_value($1, $3,$destpage,$2)/eg; + } + + return $params{content}; +} # do_filter + +#--------------------------------------------------------------- +# Private functions +# -------------------------------- +sub get_other_page_field_value ($$$) { + my $escape = shift; + my $field = shift; + my $page = shift; + my $other_page = shift; + + if (length $escape) + { + return "{{\$${other_page}#${field}}}"; + } + my $use_page = bestlink($page, $other_page); + # add a dependency for the page from which we get the value + add_depends($page, $use_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 $escape = shift; + my $field = shift; + my $page = shift; + + if (length $escape) + { + return "{{\$${field}}}"; + } + 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/.ikiwiki/IkiWiki/Plugin/pod.pm b/.ikiwiki/IkiWiki/Plugin/pod.pm new file mode 100644 index 0000000..b76beb6 --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/pod.pm @@ -0,0 +1,105 @@ +#!/usr/bin/perl +# POD as a wiki page type. +# See plugins/contrib/pod for documentation. +package IkiWiki::Plugin::pod; +use warnings; +use strict; +=head1 NAME + +IkiWiki::Plugin::pod - process pages written in POD format. + +=head1 VERSION + +This describes version B<1.20100519> of IkiWiki::Plugin::pod + +=cut + +our $VERSION = '1.20100519'; + +=head1 PREREQUISITES + + IkiWiki + Pod::Xhtml + IO::String + +=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; +use Pod::Xhtml; +use IO::String; + +sub import { + hook(type => "getsetup", id => "pod", call => \&getsetup); + hook(type => "checkconfig", id => "pod", call => \&checkconfig); + hook(type => "htmlize", id => "pod", call => \&htmlize); + hook(type => "htmlize", id => "pm", call => \&htmlize); +} + +sub getsetup () { + return + plugin => { + description => "process pages written in POD format", + safe => 1, + rebuild => undef, + }, + pod_index => { + type => "boolean", + example => "0", + description => "if true, make an index for the page", + safe => 0, + rebuild => 0, + }, + pod_toplink => { + type => "string", + example => "Top", + description => "label for link to top of page", + safe => 0, + rebuild => 0, + }, +} + +sub checkconfig () { + if (!defined $config{pod_index}) + { + $config{pod_index} = 1; + } + if (!defined $config{pod_toplink}) + { + $config{pod_toplink} = ''; + } + return 1; +} + +sub htmlize (@) { + my %params=@_; + my $page = $params{page}; + + my $toplink = $config{pod_toplink} ? + sprintf '

%s

', + $config{pod_toplink} : ''; + + my $parser = new Pod::Xhtml( + StringMode => 1, + FragmentOnly => 1, + MakeIndex => $config{pod_index}, + TopLinks => $toplink, + ); + my $io = IO::String->new($params{content}); + $parser->parse_from_filehandle($io); + + return $parser->asString; +} + +1; diff --git a/.ikiwiki/IkiWiki/Plugin/report.pm b/.ikiwiki/IkiWiki/Plugin/report.pm new file mode 100644 index 0000000..5d4e344 --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/report.pm @@ -0,0 +1,651 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::report; +use strict; +=head1 NAME + +IkiWiki::Plugin::report - Produce templated reports from page field data. + +=head1 VERSION + +This describes version B<1.20110906> of IkiWiki::Plugin::report + +=cut + +our $VERSION = '1.20110906'; + +=head1 DESCRIPTION + +Given a template, make a report from the field values of multiple pages. +Depends on the "field" plugin. +Can also make multi-page reports. + +See doc/plugins/contrib/report and doc/ikiwiki/directive/report +for documentation. + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + HTML::Template + Encode + POSIX + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009-2011 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; +use HTML::Template; +use Encode; +use POSIX qw(ceil); + +sub import { + hook(type => "getsetup", id => "report", call => \&getsetup); + hook(type => "preprocess", id => "report", call => \&preprocess, scan=>1); + + IkiWiki::loadplugin("field"); +} + +# ------------------------------------------------------------------- +# Hooks +# ------------------------------------- +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + }, +} + +sub preprocess (@) { + my %params=@_; + + if (! exists $params{template}) { + error gettext("missing template parameter"); + } + + # backwards-compatible (should use maketrail not doscan) + if (!exists $params{maketrail} and exists $params{doscan}) + { + $params{maketrail} = $params{doscan}; + delete $params{doscan}; + } + + if (exists $params{maketrail} and exists $params{trail}) + { + error gettext("maketrail and trail are incompatible"); + } + + # disable scanning if we don't want it + my $scanning=! defined wantarray; + if ($scanning and !$params{maketrail}) + { + return ''; + } + + my $this_page = $params{page}; + my $dest_page = $params{destpage}; + my $pages = (defined $params{pages} ? $params{pages} : '*'); + $pages =~ s/{{\$page}}/$this_page/g; + + if (!defined $params{first_page_is_index}) + { + $params{first_page_is_index} = 0; + } + + my $template; + eval { + # Do this in an eval because it might fail + # if the template isn't a page in the wiki + $template=template_depends($params{template}, $params{page}, + blind_cache => 1); + }; + if (! $template) { + # look for .tmpl template (in global templates dir) + eval { + $template=template("$params{template}.tmpl", + blind_cache => 1); + }; + if ($@) { + error gettext("failed to process template $params{template}.tmpl:")." $@"; + } + if (! $template) { + + error sprintf(gettext("%s not found"), + htmllink($params{page}, $params{destpage}, + "/templates/$params{template}")) + } + } + delete $params{template}; + + my $deptype=deptype($params{quick} ? 'presence' : 'content'); + + my @matching_pages; + my @trailpages = (); + # Don't add the dependencies yet because + # the results could be further filtered below. + if ($params{pagenames}) + { + @matching_pages = + map { bestlink($params{page}, $_) } split ' ', $params{pagenames}; + # Because we used pagenames, we have to sort the pages ourselves. + # This code is cribbed from pagespec_match_list + if ($params{sort}) + { + my $sort=IkiWiki::sortspec_translate($params{sort}, + $params{reverse}); + @matching_pages=IkiWiki::SortSpec::sort_pages($sort, + @matching_pages); + } + } + # "trail" means "all the pages linked to from a given page" + # which is a bit looser than the PmWiki definition + # but it will do + elsif ($params{trail}) + { + @trailpages = split(' ', $params{trail}); + foreach my $tp (@trailpages) + { + foreach my $pn (@{$links{$tp}}) + { + # NEED to use bestlink because the links list + # does not store absolute links + push @matching_pages, bestlink($tp, $pn); + } + } + if ($params{pages}) + { + # Filter out the pages that don't match. + # Don't add the dependencies yet because + # the results could be further filtered below. + # Therefore we should not use pagespec_match_list here + # because it will add premature dependencies. + # However, for the sake of speed, we don't want to call + # pagespec_match repeatedly, as it will have to re-compile + # the pagespec each time; so pull out the code and compile + # the pagespec only once. + my @filtered = (); + my $result=0; + my $sub=IkiWiki::pagespec_translate($params{pages}); + error(sprintf("syntax error in pagespec '%s'", + $params{pages})) if !defined $sub; + foreach my $mp (@matching_pages) + { + $result=$sub->($mp, %params); + if ($result) + { + push @filtered, $mp; + } + } + @matching_pages = @filtered; + } + # Because we used a trail, we have to sort the pages ourselves. + # This code is cribbed from pagespec_match_list + if ($params{sort}) + { + my $sort=IkiWiki::sortspec_translate($params{sort}, + $params{reverse}); + @matching_pages=IkiWiki::SortSpec::sort_pages($sort, + @matching_pages); + } + } + else + { + @matching_pages = pagespec_match_list($params{destpage}, $pages, + %params, + num=>$params{count}, + deptype => 0); + } + + # ------------------------------------------------------------------ + # If we want this report to be in "here_only", that is, + # the current page ($dest_page) and the previous page + # and the next page only, we need to find the current page + # in the list of matching pages, and set the matching + # pages to those three pages. + my @here_only = (); + my $dest_page_ind; + if ($params{here_only}) + { + for (my $i=0; $i < @matching_pages; $i++) + { + if ($matching_pages[$i] eq $dest_page) + { + if ($i > 0) + { + push @here_only, $matching_pages[$i-1]; + push @here_only, $matching_pages[$i]; + $dest_page_ind = 1; + } + else + { + push @here_only, $matching_pages[$i]; + $dest_page_ind = 0; + } + if ($i < $#matching_pages) + { + push @here_only, $matching_pages[$i+1]; + } + last; + } + } # for all matching pages + @matching_pages = @here_only; + # If there is only one match, the dest_page, then + # declare there to be no matches at all + if (@matching_pages == 1 and $matching_pages[0] eq $dest_page) + { + @matching_pages = (); + } + } + + # Only add dependencies IF we found matches + if ($#matching_pages > 0) + { + if ($params{trail} and !$params{here_only}) + { + foreach my $tp (@trailpages) + { + add_depends($dest_page, $tp, deptype("links")); + } + } + foreach my $mp (@matching_pages) + { + add_depends($dest_page, $mp, $deptype); + } + } + ##debug("report($dest_page) found " . scalar @matching_pages . " pages"); + + # ------------------------------------------------------------------ + # If we are scanning, we only care about the list of pages we found. + # If "maketrail" is true, then add the found pages to the list of links + # from this page. + # Note that "maketrail" and "trail" are incompatible because one + # cannot guarantee that the trail page has been scanned before + # this current page. + + if ($scanning) + { + if ($params{maketrail} and !$params{trail}) + { + debug("report ($dest_page) NO MATCHING PAGES") if !@matching_pages; + foreach my $page (@matching_pages) + { + add_link($dest_page, $page); + } + } + return; + } + + # build up the report + # + my @report = (); + + my $start = ($params{here_only} + ? $dest_page_ind + : ($params{start} ? $params{start} : 0)); + my $stop = ($params{here_only} + ? $dest_page_ind + 1 + : ($params{count} + ? (($start + $params{count}) <= @matching_pages + ? $start + $params{count} + : scalar @matching_pages + ) + : scalar @matching_pages) + ); + my $output = ''; + my $num_pages = 1; + if ($params{per_page}) + { + my $num_recs = scalar @matching_pages; + $num_pages = ceil($num_recs / $params{per_page}); + } + # Don't do pagination + # - when there's only one page + # - on included pages + if (($num_pages <= 1) + or ($params{page} ne $params{destpage})) + { + $output = build_report(%params, + start=>$start, + stop=>$stop, + matching_pages=>\@matching_pages, + template=>$template, + scanning=>$scanning, + ); + } + else + { + $output = multi_page_report(%params, + num_pages=>$num_pages, + start=>$start, + stop=>$stop, + matching_pages=>\@matching_pages, + template=>$template, + scanning=>$scanning, + ); + } + + return $output; +} # preprocess + +# ------------------------------------------------------------------- +# Private Functions +# ------------------------------------- + +# Do a multi-page report. +# This assumes that this is not an inlined page. +sub multi_page_report (@) { + my %params = ( + start=>0, + @_ + ); + + my @matching_pages = @{$params{matching_pages}}; + my $template = $params{template}; + my $scanning = $params{scanning}; + my $num_pages = $params{num_pages}; + my $first_page_is_index = $params{first_page_is_index}; + my $report_title = ($params{report_title} + ? sprintf("

%s

", $params{report_title}) + : '' + ); + + my $first_page_out = ''; + for (my $pind = 0; $pind < $num_pages; $pind++) + { + my $page_title = ($pind == 0 ? '' : $report_title); + my $rep_links = create_page_links(%params, + num_pages=>$num_pages, + cur_page=>$pind, + first_page_is_index=>$first_page_is_index); + my $start_at = $params{start} + ($pind * $params{per_page}); + my $end_at = $params{start} + (($pind + 1) * $params{per_page}); + my $pout = build_report(%params, + start=>$start_at, + stop=>$end_at, + matching_pages=>\@matching_pages, + template=>$template, + scanning=>$scanning, + ); + $pout =< +$page_title +$rep_links +$pout +$rep_links + +EOT + if ($pind == 0 and !$first_page_is_index) + { + $first_page_out = $pout; + } + else + { + my $new_page = sprintf("%s_%d", + ($params{report_id} + ? $params{report_id} : 'report'), + $pind + 1); + my $target = targetpage($params{page}, $config{htmlext}, $new_page); + will_render($params{page}, $target); + my $rep = IkiWiki::linkify($params{page}, $new_page, $pout); + + # render as a simple page + $rep = render_simple_page(%params, + new_page=>$new_page, + content=>$rep); + writefile($target, $config{destdir}, $rep); + } + } + if ($first_page_is_index) + { + $first_page_out = create_page_links(%params, + num_pages=>$num_pages, + cur_page=>-1, + first_page_is_index=>$first_page_is_index); + } + + return $first_page_out; +} # multi_page_report + +sub create_page_links { + my %params = ( + num_pages=>0, + cur_page=>0, + @_ + ); + my $first_page_is_index = $params{first_page_is_index}; + + my $prev_link = ''; + my $next_link = ''; + my $report_base = ($params{report_id} + ? $params{report_id} : 'report'); + my @page_links = (); + for (my $pind = ($first_page_is_index ? -1 : 0); + $pind < $params{num_pages}; $pind++) + { + if ($pind == $params{cur_page} + and $pind == -1) + { + push @page_links, + sprintf('[%s]', + IkiWiki::pagetitle(IkiWiki::basename($params{page}))); + } + elsif ($pind == $params{cur_page}) + { + push @page_links, sprintf('[%d]', $pind + 1); + if ($pind + 1 < $params{num_pages}) + { + $next_link = + sprintf(' Next -> ', + $report_base, $pind + 2, $config{htmlext}); + } + if ($pind > 0) + { + if ($pind == 1 and !$first_page_is_index) + { + $prev_link = sprintf(' <- Prev ', + ($config{usedirs} + ? './' + : '../' . IkiWiki::basename($params{page}) + . '.' . $config{htmlext}), + ); + } + else + { + $prev_link = + sprintf(' <- Prev ', + $report_base, $pind, $config{htmlext}); + } + } + } + elsif ($pind == -1) + { + push @page_links, + sprintf('[%s]', + ($config{usedirs} + ? './' + : '../' . IkiWiki::basename($params{page}) + . '.' . $config{htmlext}), + IkiWiki::pagetitle(IkiWiki::basename($params{page}))); + } + elsif ($pind == 0 and !$first_page_is_index) + { + push @page_links, + sprintf('[%d]', + ($config{usedirs} + ? './' + : '../' . IkiWiki::basename($params{page}) + . '.' . $config{htmlext}), + $pind + 1); + } + else + { + push @page_links, + sprintf('[%d]', + $report_base, $pind + 1, $config{htmlext}, + $pind + 1); + } + } + return '
' + . ($prev_link ? $prev_link : '') + . join(' ', @page_links) + . ($next_link ? $next_link : '') + . '
'; +} # create_page_links + +sub render_simple_page (@) { + my %params=@_; + + my $new_page = $params{new_page}; + my $content = $params{content}; + + # render as a simple page + # cargo-culted from IkiWiki::Render::genpage + my $ptmpl = IkiWiki::template('page.tmpl', blind_cache=>1); + $ptmpl->param(%{$pagestate{$params{page}}{field}}); + $ptmpl->param( + title => IkiWiki::pagetitle(IkiWiki::basename($new_page)), + wikiname => $config{wikiname}, + content => $content, + html5 => $config{html5}, + ); + + IkiWiki::run_hooks(pagetemplate => sub { + shift->(%params, + page => $new_page, + destpage => $new_page, + template => $ptmpl); + }); + + $content=$ptmpl->output; + + IkiWiki::run_hooks(format => sub { + $content=shift->(%params, + page => $params{page}, + content => $content, + ); + }); + return $content; +} # render_simple_page + +sub build_report (@) { + my %params = ( + start=>0, + @_ + ); + + my @matching_pages = @{$params{matching_pages}}; + my $template = $params{template}; + my $scanning = $params{scanning}; + my $destpage_baseurl = IkiWiki::baseurl($params{destpage}); + my @report = (); + + my $start = $params{start}; + my $stop = $params{stop}; + my @header_fields = ($params{headers} ? split(' ', $params{headers}) : ()); + my @prev_headers = (); + for (my $j=0; $j < @header_fields; $j++) + { + $prev_headers[$j] = ''; + } + for (my $i=$start; $i < $stop and $i < @matching_pages; $i++) + { + my $page = $matching_pages[$i]; + my $prev_page = ($i > 0 ? $matching_pages[$i-1] : ''); + my $next_page = ($i < $#matching_pages ? $matching_pages[$i+1] : ''); + my $first = ($i == $start); + my $last = (($i == ($stop - 1)) or ($i == $#matching_pages)); + my @header_values = (); + foreach my $fn (@header_fields) + { + my $val = + IkiWiki::Plugin::field::field_get_value($fn, $page); + $val = '' if !defined $val; + push @header_values, $val; + } + my $rowr = do_one_template( + %params, + template=>$template, + page=>$page, + destpage_baseurl=>$destpage_baseurl, + recno=>$i, + prev_page=>$prev_page, + next_page=>$next_page, + destpage=>$params{destpage}, + first=>$first, + last=>$last, + scan=>$scanning, + headers=>\@header_values, + prev_headers=>\@prev_headers, + ); + for (my $j=0; $j < @header_fields; $j++) + { + if ($header_values[$j] ne $prev_headers[$j]) + { + $prev_headers[$j] = $header_values[$j]; + } + } + push @report, $rowr; + } + + if (! @report) { + return ''; + } + my $output = join('', @report); + + return $output; +} # build_report + +sub do_one_template (@) { + my %params=@_; + + my $scan = $params{scan}; + my $template = $params{template}; + delete $params{template}; + + $params{included}=($params{page} ne $params{destpage}); + + $template->clear_params(); # don't accidentally repeat values + IkiWiki::Plugin::field::field_set_template_values($template, + $params{page}, %params); + + # ------------------------------------------------- + # headers + for (my $i=0; $i < @{$params{headers}}; $i++) # clear the headers + { + my $hname = "header" . ($i + 1); + $template->param($hname => ''); + } + for (my $i=0; $i < @{$params{headers}}; $i++) + { + my $hname = "header" . ($i + 1); + if ($params{headers}[$i] ne $params{prev_headers}[$i]) + { + $template->param($hname => $params{headers}[$i]); + # change the lower-level headers also + for (my $j=($i + 1); $j < @{$params{headers}}; $j++) + { + my $hn = "header" . ($j + 1); + $template->param($hn => $params{headers}[$j]); + } + } + } + + my $output = $template->output; + + return IkiWiki::preprocess($params{page}, $params{destpage}, + IkiWiki::filter($params{page}, $params{destpage}, + $output), $scan); + +} # do_one_template + +1; diff --git a/.ikiwiki/IkiWiki/Plugin/xslt.pm b/.ikiwiki/IkiWiki/Plugin/xslt.pm new file mode 100644 index 0000000..c94b62f --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/xslt.pm @@ -0,0 +1,100 @@ +#!/usr/bin/perl +# Ikiwiki xslt plugin. +# See plugins/contrib/xslt and ikiwiki/directive/xslt for documentation. +package IkiWiki::Plugin::xslt; +use warnings; +use strict; +=head1 NAME + +IkiWiki::Plugin::xslt - ikiwiki directive to process an XML file with XSLT + +=head1 VERSION + +This describes version B<1.20100519> of IkiWiki::Plugin::xslt + +=cut + +our $VERSION = '1.20100519'; + +=head1 PREREQUISITES + + IkiWiki + XML::LibXML + XML::LibXSLT + +=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; +use XML::LibXSLT; +use XML::LibXML; + +my $XSLT_parser; +my $XSLT_xslt; + +sub import { + hook(type => "getsetup", id => "xslt", call => \&getsetup); + hook(type => "preprocess", id => "xslt", call => \&preprocess); + $XSLT_parser = XML::LibXML->new(); + $XSLT_xslt = XML::LibXSLT->new(); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => undef, + }, +} + +sub preprocess (@) { + my %params=@_; + + # check the files exist + my %near = (); + foreach my $param (qw{stylesheet file}) { + if (! exists $params{$param}) + { + error sprintf(gettext('%s parameter is required'), $param); + } + if ($param eq 'stylesheet' and $params{$param} !~ /.xsl$/) + { + error sprintf(gettext('%s must have .xsl extension'), $param); + } + if ($param eq 'file' and $params{$param} !~ /.xml$/) + { + error sprintf(gettext('%s must have .xml extension'), $param); + } + $near{$param} = bestlink($params{page}, $params{$param}); + if (! $near{$param}) + { + error sprintf(gettext('cannot find bestlink for "%s"'), + $params{$param}); + } + if (! exists $pagesources{$near{$param}}) + { + error sprintf(gettext('cannot find file "%s"'), $near{$param}); + } + add_depends($params{page}, $near{$param}); + } + + my $source = $XSLT_parser->parse_file(srcfile($near{file})); + my $style_doc = $XSLT_parser->parse_file(srcfile($near{stylesheet})); + my $stylesheet = $XSLT_xslt->parse_stylesheet($style_doc); + my $results = $stylesheet->transform($source); + return $stylesheet->output_string($results); +} + +1; +__END__ diff --git a/.ikiwiki/IkiWiki/Plugin/ymlfront.pm b/.ikiwiki/IkiWiki/Plugin/ymlfront.pm new file mode 100644 index 0000000..5b64b52 --- /dev/null +++ b/.ikiwiki/IkiWiki/Plugin/ymlfront.pm @@ -0,0 +1,429 @@ +#!/usr/bin/perl +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<1.20110610> of IkiWiki::Plugin::ymlfront + +=cut + +our $VERSION = '1.20110610'; + +=head1 DESCRIPTION + +This allows field-data to be defined in YAML format on a page. +This is a back-end for the "field" plugin. + +See doc/plugins/contrib/ymlfront and ikiwiki/directive/ymlfront for docs. + +=head1 PREREQUISITES + + IkiWiki + IkiWiki::Plugin::field + YAML::Any + +=head1 AUTHOR + + Kathryn Andersen (RUBYKAT) + http://github.com/rubykat + +=head1 COPYRIGHT + +Copyright (c) 2009-2011 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', + all_values=>\&yml_get_values, + first=>1); +} + +# ------------------------------------------------------------ +# Hooks +# -------------------------------- +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 1, + }, + ymlfront_delim => { + type => "array", + example => "ymlfront_delim => [qw(--YAML-START-- --YAML-END--)]", + description => "delimiters of YAML data", + safe => 0, + rebuild => undef, + }, + ymlfront_set_content => { + type => "boolean", + example => "ymlfront_set_content => 1", + description => "allow ymlfront to define the content of the page inside the YAML", + safe => 0, + rebuild => undef, + }, +} + +sub checkconfig () { + eval {use YAML::Any}; + eval {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(--- ---)]; + } + if (!defined $config{ymlfront_set_content}) + { + $config{ymlfront_set_content} = 0; + } +} # checkconfig + +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; + } + my $extracted_yml = extract_yml(%params); + if (defined $extracted_yml + and defined $extracted_yml->{yml}) + { + my $parsed_yml = parse_yml(%params, data=>$extracted_yml->{yml}); + if (defined $parsed_yml) + { + # clear the old data + if (exists $pagestate{$page}{ymlfront}) + { + delete $pagestate{$page}{ymlfront}; + } + # save the data to pagestate + foreach my $fn (keys %{$parsed_yml}) + { + my $fval = $parsed_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) + { + # clear the old data + if (exists $pagestate{$page}{ymlfront}) + { + delete $pagestate{$page}{ymlfront}; + } + # save the data to pagestate + foreach my $fn (keys %{$parsed_yml}) + { + my $fval = $parsed_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}; + } + } + else + { + error gettext("ymlfront: data not legal YAML") + } + 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 $extracted_yml = extract_yml(%params); + if (defined $extracted_yml + and defined $extracted_yml->{yml} + and defined $extracted_yml->{content}) + { + $params{content} = $extracted_yml->{content}; + # check for a content value + if ($config{ymlfront_set_content} + and 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 $extracted_yml = extract_yml(%params); + if (defined $extracted_yml + and !defined $extracted_yml->{yml}) + { + debug("ymlfront: Save of $page failed: $@"); + return gettext("YAML data incorrect: $@"); + } + return undef; +} # checkcontent + +# ------------------------------------------------------------ +# Field functions +# -------------------------------- +sub yml_get_values (@) { + my %params=@_; + my $page = $params{page}; + + my $page_file=$pagesources{$page} || return; + my $page_type=pagetype($page_file); + if (!defined $page_type) + { + return; + } + my $extracted_yml = extract_yml(%params); + if (defined $extracted_yml + and defined $extracted_yml->{yml}) + { + my $parsed_yml = parse_yml(%params, data=>$extracted_yml->{yml}); + return $parsed_yml; + } + return undef; +} # yml_get_values + +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 +# -------------------------------- + +# extract the YAML data from the given content +# Expects page, content +# Returns { yml=>$yml_str, content=>$content } or undef +# if undef is returned, there is no YAML +# but if $yml_str is undef then there was YAML but it was not legal +sub extract_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 ($content) + { + my $ystart = $config{ymlfront_delim}[0]; + my $yend = $config{ymlfront_delim}[1]; + if ($ystart eq '---' and $yend eq '---') + { + if ($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; + } + } + if ($yml_str) # possible YAML + { + # if {{$page}} is there, do an immediate substitution + $yml_str =~ s/\{\{\$page\}\}/$page/sg; + + my $ydata; + eval {$ydata = Load($yml_str);}; + if ($@) + { + debug("ymlfront: Load of $page data failed: $@"); + return { yml=>undef, content=>$content }; + } + if (!$ydata) + { + debug("ymlfront: no legal YAML for $page"); + return { yml=>undef, content=>$content }; + } + return { yml=>$yml_str, + content=>$start_of_content . $rest_of_content}; + } + return undef; +} # extract_yml + +# parse the YAML data from the given string +# Expects page, data +# Returns \%yml_data or undef +sub parse_yml { + my %params=@_; + my $page = $params{page}; + my $yml_str = $params{data}; + + if ($yml_str) + { + # if {{$page}} is there, do an immediate substitution + $yml_str =~ s/\{\{\$page\}\}/$page/sg; + + my $ydata; + eval {$ydata = Load($yml_str);}; + if ($@) + { + debug("ymlfront parse: Load of $page data failed: $@"); + return undef; + } + if (!$ydata) + { + debug("ymlfront parse: no legal YAML for $page"); + return undef; + } + if ($ydata) + { + my %lc_data = (); + + # make lower-cased versions of the data + foreach my $fn (keys %{$ydata}) + { + my $fval = $ydata->{$fn}; + $lc_data{lc($fn)} = $fval; + } + return \%lc_data; + } + } + return undef; +} # parse_yml +1; diff --git a/PerlTutorialHub.setup b/PerlTutorialHub.setup index 542d39c..a2e3a27 100644 --- a/PerlTutorialHub.setup +++ b/PerlTutorialHub.setup @@ -30,7 +30,7 @@ use IkiWiki::Setup::Standard { # rcs backend to use rcs => 'git', # plugins to add to the default configuration - add_plugins => [qw{goodstuff websetup highlight google theme plusone}], + add_plugins => [qw{goodstuff websetup highlight google theme plusone ftemplate ymlfront}], # plugins to disable disable_plugins => [qw{search edittemplate rawhtml localstyle pagetemplate}], # additional directory to search for template files From c470810f5ee7ea5f883ae84f5ee340ed35f64a26 Mon Sep 17 00:00:00 2001 From: Perl Tutorial Hub Date: Fri, 28 Oct 2011 16:50:07 +0000 Subject: [PATCH 2/2] fix yaml extraction stupidity --- .ikiwiki/IkiWiki/Plugin/ymlfront.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ikiwiki/IkiWiki/Plugin/ymlfront.pm b/.ikiwiki/IkiWiki/Plugin/ymlfront.pm index 5b64b52..7a57ddc 100644 --- a/.ikiwiki/IkiWiki/Plugin/ymlfront.pm +++ b/.ikiwiki/IkiWiki/Plugin/ymlfront.pm @@ -396,6 +396,8 @@ sub parse_yml { if ($yml_str) { + $yml_str .= "\n"; # whatever gets the data here strips all trailing whitespace, so this needs to be added + # if {{$page}} is there, do an immediate substitution $yml_str =~ s/\{\{\$page\}\}/$page/sg; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy