diff --git a/IkiWiki.pm b/IkiWiki.pm index 1c87831..5193a29 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -28,6 +28,7 @@ use Memoize; memoize("abs2rel"); memoize("pagespec_translate"); memoize("file_pruned"); +memoize("convpath"); sub defaultconfig () { #{{{ return @@ -251,7 +252,7 @@ sub dirname ($) { #{{{ sub pagetype ($) { #{{{ my $page=shift; - if ($page =~ /\.([^.]+)$/) { + if ($page =~ /\.([^.,_+]+)$/) { return $1 if exists $hooks{htmlize}{$1}; } return; @@ -554,8 +555,13 @@ sub htmllink ($$$;@) { #{{{ else { $linktext=pagetitle(basename($link)); } + + my %link=(type => "_link", content => $linktext); + my $type=$opts{type}; + $type=pagetype($pagesources{$lpage}) unless $type || !$lpage; + $type="html" unless $type; - return "$linktext" + return convert($type, %link, url => $bestlink, selflink => 1) if length $bestlink && $page eq $bestlink; if (! $destsources{$bestlink}) { @@ -563,13 +569,12 @@ sub htmllink ($$$;@) { #{{{ if (! $destsources{$bestlink}) { return $linktext unless length $config{cgiurl}; - return " 1, + url => cgiurl( do => "create", page => pagetitle(lc($link), 1), from => $lpage - ). - "\">?$linktext" + )); } } @@ -577,63 +582,119 @@ sub htmllink ($$$;@) { #{{{ $bestlink=beautify_url($bestlink); if (! $opts{noimageinline} && isinlinableimage($bestlink)) { - return "\"$linktext\""; + return convert($type, %link, type => "_image", url => $bestlink); } if (defined $opts{anchor}) { $bestlink.="#".$opts{anchor}; } - my @attrs; - if (defined $opts{rel}) { - push @attrs, ' rel="'.$opts{rel}.'"'; + $link{attrs} = { + defined $opts{rel} ? (rel => $opts{rel}) : (), + defined $opts{class} ? (class => $opts{class}) : (), + }; + + return convert($type, %link, url => $bestlink); +} #}}} + +sub convert_link { #{{{ + my %params=@_; + my $attrs=""; + + if($params{selflink}) { + return "$params{content}"; } - if (defined $opts{class}) { - push @attrs, ' class="'.$opts{class}.'"'; + if($params{attrs}) { + $attrs=join('', map(" $_='$params{attrs}{$_}'", keys %{$params{attrs}})); } + #if (defined $opts{class}) { + # push @attrs, ' class="'.$opts{class}.'"'; + #} - return "$linktext"; + return "" . + ($params{create} + ? "?$params{content}" + : "$params{content}") . ""; } #}}} -sub htmlize ($$$) { #{{{ - my $page=shift; - my $type=shift; - my $content=shift; +sub convert_image { #{{{ + my %params=@_; + return "\"$params{content}\""; +} #}}} + +INIT { #{{{ + hook(type => "convert", id => "html,_link", call => \&convert_link); + hook(type => "convert", id => "html,_image", call => \&convert_image); +} #}}} + +sub convpath ($$) { #{{{ + my $outtype=shift; + my $srctype=shift; + my %vhooks; + + # breadth-first search + my @q=",$outtype"; + while (my $conv = shift @q) { + my ($src) = $conv=~/,(.*)/; + + next if exists $vhooks{$src}; + $vhooks{$src} = $hooks{convert}{$conv}; - if (exists $hooks{htmlize}{$type}) { - $content=$hooks{htmlize}{$type}{call}->( - page => $page, - content => $content, - ); + last if $src eq $srctype; + push @q, grep m/^$src,/, keys %{$hooks{convert}}; } - else { - error("htmlization of $type not supported"); + if (! exists $vhooks{$srctype}) { + #print STDERR "conversion of $srctype to $outtype unknown\n"; + return &convpath("html", $srctype) unless $outtype eq "html"; + error "${outtype}ization of $srctype not supported"; } - run_hooks(sanitize => sub { - $content=shift->( - page => $page, - content => $content, - ); - }); + # walk back + my @path; + while ($srctype ne $outtype) { + push @path, $vhooks{$srctype}; + ($srctype) = $vhooks{$srctype}{id}=~/^(.*?),/; + } + return @path; +} #}}} - return $content; +sub convert ($@) { #{{{ + my $outtype=shift; + my %params=@_; + my $h; + + foreach $h (convpath($outtype, $params{type})) { + %params=(type => ($h->{id} =~ /^([^,]*)/), + content => $h->{call}->(%params)); + } + + return $params{content}; } #}}} -sub linkify ($$$) { #{{{ +sub htmlize ($$$) { #{{{ + my %page = (page => shift, type => shift, content => shift, @_); + + $page{content} = convert("html", %page); + run_hooks(sanitize => sub { $page{content}=shift->(%page); }); + + return $page{content}; +} #}}} + +sub linkify ($$$;$) { #{{{ my $lpage=shift; # the page containing the links my $page=shift; # the page the link will end up on (different for inline) my $content=shift; + my %type = @_ ? (type => shift) : (); $content =~ s{(\\?)$config{wiki_link_regexp}}{ defined $2 ? ( $1 ? "[[$2|$3".($4 ? "#$4" : "")."]]" - : htmllink($lpage, $page, linkpage($3), + : htmllink($lpage, $page, linkpage($3), %type, anchor => $4, linktext => pagetitle($2))) : ( $1 ? "[[$3".($4 ? "#$4" : "")."]]" - : htmllink($lpage, $page, linkpage($3), + : htmllink($lpage, $page, linkpage($3), %type, anchor => $4)) }eg; @@ -642,12 +703,13 @@ sub linkify ($$$) { #{{{ my %preprocessing; our $preprocess_preview=0; -sub preprocess ($$$;$$) { #{{{ +sub preprocess ($$$;$$$) { #{{{ my $page=shift; # the page the data comes from my $destpage=shift; # the page the data will appear in (different for inline) my $content=shift; my $scan=shift; my $preview=shift; + my $type=shift || pagetype($pagesources{$page}); # Using local because it needs to be set within any nested calls # of this function. @@ -709,14 +771,23 @@ sub preprocess ($$$;$$) { #{{{ $command, $page, $preprocessing{$page}). "]]"; } - my $ret=$hooks{preprocess}{$command}{call}->( - @params, + my %info=( page => $page, destpage => $destpage, preview => $preprocess_preview, ); + my @ret=$hooks{preprocess}{$command}{call}->( + @params, + %info + ); $preprocessing{$page}--; - return $ret; + + if (@ret==1) { + return $ret[0] unless $ret[0]=~/[<>]/; + #print STDERR "undeclared html from [[$command]]\n"; + @ret=(type => "html", content => $ret[0]); + } + return convert($type, %info, @ret); } else { return "[[$command $params]]"; @@ -748,16 +819,9 @@ sub preprocess ($$$;$$) { #{{{ } #}}} sub filter ($$$) { #{{{ - my $page=shift; - my $destpage=shift; - my $content=shift; - - run_hooks(filter => sub { - $content=shift->(page => $page, destpage => $destpage, - content => $content); - }); - - return $content; + my %params=(page => shift, destpage => shift, content => shift); + run_hooks(filter => sub { $params{content}=shift->(%params); }); + return $params{content}; } #}}} sub indexlink () { #{{{ @@ -947,6 +1011,10 @@ sub hook (@) { # {{{ error 'hook requires type, call, and id parameters'; } + if ($param{type} eq "htmlize") { + &hook(%param, type => "convert", id => "html,$param{id}"); + } + return if $param{no_override} && exists $hooks{$param{type}}{$param{id}}; $hooks{$param{type}}{$param{id}}=\%param; diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm index 155010a..529a73a 100644 --- a/IkiWiki/CGI.pm +++ b/IkiWiki/CGI.pm @@ -415,7 +415,7 @@ sub cgi_editpage ($$) { #{{{ htmlize($page, $type, linkify($page, "", preprocess($page, $page, - filter($page, $page, $content), 0, 1)))); + filter($page, $page, $content), 0, 1, $type), $type))); } elsif ($form->submitted eq "Save Page") { $form->tmpl_param("page_preview", ""); diff --git a/IkiWiki/Plugin/rst.pm b/IkiWiki/Plugin/rst.pm index 30f5d16..44a9a48 100644 --- a/IkiWiki/Plugin/rst.pm +++ b/IkiWiki/Plugin/rst.pm @@ -36,9 +36,25 @@ print html[html.find('')+6:html.find('')].strip(); "; sub import { #{{{ + hook(type => "convert", id => "rst,_link", call => \&convert_link); + hook(type => "convert", id => "rst,html", call => \&convert_html); hook(type => "htmlize", id => "rst", call => \&htmlize); } # }}} +sub convert_link { #{{{ + my %params=@_; + return $params{content} if $params{selflink}; + return $params{create} ? + "\\ `? <$params{url}>`__\\ $params{content} " : + "\\ `$params{content}\\ <$params{url}>`_ "; +} #}}} + +sub convert_html { #{{{ + my %params=@_; + $params{content} =~ s/^/ /mg; + return "\n\n.. raw:: html\n\n".$params{content}."\n\n"; +} #}}} + sub htmlize (@) { #{{{ my %params=@_; my $content=$params{content}; diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm index 35d663a..c153356 100644 --- a/IkiWiki/Render.pm +++ b/IkiWiki/Render.pm @@ -100,7 +100,10 @@ sub genpage ($$$) { #{{{ if ($page !~ /.*\/\Q$discussionlink\E$/ && (length $config{cgiurl} || exists $links{$page."/".$discussionlink})) { - $template->param(discussionlink => htmllink($page, $page, gettext("Discussion"), noimageinline => 1, forcesubpage => 1)); + $template->param(discussionlink => + htmllink($page, $page, gettext("Discussion"), + type => "html", noimageinline => 1, + forcesubpage => 1)); $actions++; } } diff --git a/doc/plugins/rst.mdwn b/doc/plugins/rst.mdwn index 7250e46..5e772ef 100644 --- a/doc/plugins/rst.mdwn +++ b/doc/plugins/rst.mdwn @@ -10,9 +10,11 @@ it. Note that this plugin does not interoperate very well with the rest of ikiwiki. Limitations include: -* There are issues with inserting raw html into documents, as ikiwiki - does with [[WikiLinks|WikiLink]] and many - [[PreprocessorDirectives|PreprocessorDirective]]. +* Many [[PreprocessorDirectives|PreprocessorDirective]] include raw html + in pages, but this works correctly only as a standalone paragraph. +* [[WikiLinks|WikiLink]] are expanded with a trailing space, and converted + to rst hyperlink references, rather than raw html, which means their + styling is lost. * It's slow; it forks a copy of python for each page. While there is a perl version of the reStructuredText processor, it is not being kept in sync with the standard version, so is not used. diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn index 0e1839c..2c333d7 100644 --- a/doc/plugins/write.mdwn +++ b/doc/plugins/write.mdwn @@ -116,8 +116,22 @@ directive, while a "destpage" parameter gives the name of the page the content is going to (different for inlined pages), and a "preview" parameter is set to a true value if the page is being previewed. All parameters included in the directive are included as named parameters as -well. Whatever the function returns goes onto the page in place of the -directive. +well. + +Whatever the function returns goes onto the page in place of the directive. +If a hash is returned it is considered as an intermediate form of the directive +output. The result will be passed through `convert` before it is embedded in +the page. You must at least specify a type and content: + + return (type => "html", content => $content); + + return (type => "_link", content => "download here", url => "http://..."); + +Thanks to the leading underscore, "fake" types such as `"_link"` are not +identified with filename extensions, but `htmlize` and `convert` hooks can be +registered with them nonetheless. If you introduce a new type, you must make +sure it can at least be converted to html, probably by providing a +corresponding htmlize hook. An optional "scan" parameter, if set to a true value, makes the hook be called during the preliminary scan that ikiwiki makes of updated pages, @@ -137,12 +151,45 @@ the page) along with the rest of the page. hook(type => "htmlize", id => "ext", call => \&htmlize); Runs on the raw source of a page and turns it into html. The id parameter -specifies the filename extension that a file must have to be htmlized using -this plugin. This is how you can add support for new and exciting markup -languages to ikiwiki. - -The function is passed named parameters: "page" and "content" and should -return the htmlized content. +usually specifies the filename extension that a file must have to be htmlized +using this plugin. This is how you can add support for new and exciting markup +languages to ikiwiki. See also the `convert` hook below for specifying a way +to embed links, raw html, or other elements inserted by preprocessor directives +into your newly added language. + +The function is passed named parameters: "type", "page" and "content" and +should return the htmlized content. + +### convert + + hook(type => "convert", id => "out,src", call => \&convert_src) + +Perform a conversion from `src` to `out`, as specified in the "id" parameter. +The function is passed named parameters: "content" is the input to be +converted, and "type" its type (which will be `src`). The converted output must +be returned. + +Formats specified in the id parameters of htmlize and convert hooks are usually +filename extensions, but extra source types can be defined. Their name must be +prefixed with an underscore to distinguish them. They may provide additional +parameters to their associated convert hooks. + +When adding a new markup language, you will want to register functions for +converting elements inserted by proprocessor directives to your new language, +as well as an htmlize hook for converting it to html. It is a bug if a +preprocessor directive inserts an element which cannot be converted into html. +This means an `id => "ext,html"` convert hook, able to embed raw html into the +new language, will usually be sufficient. + +Additionnally, if your language is able to encode hyperlinks, you can provide +an `id => "ext,_link"` hook as well. The convert hook will be passed the link +text in the "content" parameter, and the remaining information as additionnal +ones: + + - "selflink" will be set to 1 if the link refers to the page it will end + up on; otherwise, + - "url" will contain the target url, + - "create" is set to one if the link points to a page creation form. ### pagetemplate diff --git a/t/htmlize_rst.t b/t/htmlize_rst.t new file mode 100755 index 0000000..1307533 --- /dev/null +++ b/t/htmlize_rst.t @@ -0,0 +1,60 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More; + +BEGIN { + if (system('python -c "from docutils.core import publish_string"')) { + plan skip_all => "Python docutils module appears not to be installed"; + #exits + } + else { + plan tests => 5; + use_ok("IkiWiki"); + } +} + + +# Initialize htmlscrubber plugin +%config=IkiWiki::defaultconfig(); + +push @{$config{plugin}}, "rst"; +$config{srcdir}=$config{destdir}="/dev/null"; +IkiWiki::loadplugins(); +IkiWiki::checkconfig(); + +is(IkiWiki::htmlize("foo", "rst", "foo\n\nbar\n"), < +

foo

+

bar

+ +END + +is(IkiWiki::htmlize("foo", "rst", "< & >"), < +

< & >

+ +END + +## +# some fudging to make linkify work right; copied from linkify.t +$IkiWiki::pagecase{foo}="foo"; +$links{foo} = []; +$renderedfiles{'foo.mdwn'} = [ 'foo' ]; +$destsources{'foo'} = 'foo.mdwn'; +$IkiWiki::pagecase{link}="link"; +$links{link} = []; +$renderedfiles{'link.mdwn'} = [ 'link' ]; +$destsources{'link'} = 'link.mdwn'; +$config{usedirs}=0; + +is(IkiWiki::linkify('foo','foo','a [[link]] b', "rst"), + 'a \\ `link\\ `_ b', "linkify RST"); + +is(IkiWiki::htmlize("foo", "rst", + IkiWiki::linkify("foo", "foo", "foo [[link]] bar\n\nbaz\n", "rst")), < HTML"); +
+

foo link bar

+

baz

+
+END