Merge remote branch 'upstream/master' into prv/po
authorintrigeri <intrigeri@boum.org>
Tue, 29 Jun 2010 13:53:51 +0000 (15:53 +0200)
committerintrigeri <intrigeri@boum.org>
Tue, 29 Jun 2010 13:53:51 +0000 (15:53 +0200)
Conflicts:
doc/plugins/po.mdwn

IkiWiki.pm
IkiWiki/Plugin/editpage.pm
IkiWiki/Plugin/inline.pm
IkiWiki/Plugin/po.pm
IkiWiki/Plugin/sidebar.pm
IkiWiki/Render.pm
doc/ikiwiki/pagespec/po.mdwn
doc/plugins/po.mdwn
doc/plugins/po/discussion.mdwn
doc/plugins/write.mdwn

index 35b38df46bbc4d71116c151688c34d982d24b34f..7b5fd283d6e09782c80f86361dd72e7b7b3fed72 100644 (file)
@@ -1074,6 +1074,13 @@ sub urlto ($$;$) {
        return beautify_urlpath($link);
 }
 
+sub isselflink ($$) {
+       my $page=shift;
+       my $link=shift;
+
+        return $page eq $link;
+}
+
 sub htmllink ($$$;@) {
        my $lpage=shift; # the page doing the linking
        my $page=shift; # the page that will contain the link (different for inline)
@@ -1099,7 +1106,7 @@ sub htmllink ($$$;@) {
        }
        
        return "<span class=\"selflink\">$linktext</span>"
-               if length $bestlink && $page eq $bestlink &&
+               if length $bestlink && isselflink($page, $bestlink) &&
                   ! defined $opts{anchor};
        
        if (! $destsources{$bestlink}) {
@@ -1389,14 +1396,16 @@ sub preprocess ($$$;$$) {
        return $content;
 }
 
-sub filter ($$$) {
+sub filter ($$$;$) {
        my $page=shift;
        my $destpage=shift;
        my $content=shift;
+       my $fullpage=shift;
+       $fullpage = 0 unless defined $fullpage;
 
        run_hooks(filter => sub {
                $content=shift->(page => $page, destpage => $destpage, 
-                       content => $content);
+                       content => $content, fullpage => $fullpage);
        });
 
        return $content;
index 1a04a72b5568f2980d8a7e00d0eaaa06a62e0e0d..70663020318197396412354be7a9d7462df62e4b 100644 (file)
@@ -187,7 +187,7 @@ sub cgi_editpage ($$) {
                my $preview=htmlize($page, $page, $type,
                        linkify($page, $page,
                        preprocess($page, $page,
-                       filter($page, $page, $content), 0, 1)));
+                       filter($page, $page, $content, 'fullpage'), 0, 1)));
                run_hooks(format => sub {
                        $preview=shift->(
                                page => $page,
index 715a3d6523cfcee4e57c125425d19cfdab110298..a04dd6630b9ee3f01ebc1e0887f656416c860fc8 100644 (file)
@@ -403,7 +403,7 @@ sub preprocess_inline (@) {
                                              linkify($page, $params{destpage},
                                              preprocess($page, $params{destpage},
                                              filter($page, $params{destpage},
-                                             readfile(srcfile($file)))));
+                                             readfile(srcfile($file)), 'fullpage')));
                                }
                                else {
                                        $ret.="\n".
@@ -474,7 +474,7 @@ sub get_inline_content ($$) {
                       linkify($page, $destpage,
                       preprocess($page, $destpage,
                       filter($page, $destpage,
-                      readfile(srcfile($file))))));
+                      readfile(srcfile($file)), 'fullpage'))));
                $nested--;
                if (isinternal($page)) {
                        # make inlined text of internal pages searchable
index 86d5087f474f7be108239e3fe79f0c27506cd6e9..552b6b51bea28a676639f91c584a98767238f277 100644 (file)
@@ -28,6 +28,7 @@ use UNIVERSAL;
 my %translations;
 my @origneedsbuild;
 my %origsubs;
+my @slavelanguages; # orderer as in config po_slave_languages
 
 memoize("istranslatable");
 memoize("_istranslation");
@@ -64,6 +65,8 @@ sub import {
                inject(name => "IkiWiki::cgiurl", call => \&mycgiurl);
                $origsubs{'rootpage'}=\&IkiWiki::rootpage;
                inject(name => "IkiWiki::rootpage", call => \&myrootpage);
+               $origsubs{'isselflink'}=\&IkiWiki::isselflink;
+               inject(name => "IkiWiki::isselflink", call => \&myisselflink);
        }
 }
 
@@ -102,11 +105,11 @@ sub getsetup () {
                },
                po_slave_languages => {
                        type => "string",
-                       example => {
+                       example => [
                                'fr' => 'Français',
                                'es' => 'Español',
                                'de' => 'Deutsch'
-                       },
+                       ],
                        description => "slave languages (PO files)",
                        safe => 1,
                        rebuild => 1,
@@ -135,6 +138,21 @@ sub checkconfig () {
                                      $field, 'po'));
                }
        }
+
+       if (ref $config{po_slave_languages} eq 'ARRAY') {
+               my %slaves;
+               for (my $i=0; $i<@{$config{po_slave_languages}}; $i = $i + 2) {
+                       $slaves{$config{po_slave_languages}->[$i]} = $config{po_slave_languages}->[$i + 1];
+                       push @slavelanguages, $config{po_slave_languages}->[$i];
+                }
+               $config{po_slave_languages} = \%slaves;
+        }
+       elsif (ref $config{po_slave_languages} eq 'HASH') {
+               @slavelanguages = sort {
+                       $config{po_slave_languages}->{$a} cmp $config{po_slave_languages}->{$b};
+               } keys %{$config{po_slave_languages}};
+        }
+
        delete $config{po_slave_languages}{$config{po_master_language}{code}};;
 
        map {
@@ -195,7 +213,7 @@ sub needsbuild () {
 
        # make existing translations depend on the corresponding master page
        foreach my $master (keys %translations) {
-               map add_depends($_, $master), values %{otherlanguages($master)};
+               map add_depends($_, $master), values %{otherlanguages_pages($master)};
        }
 }
 
@@ -227,7 +245,7 @@ sub scan (@) {
                                # make sure any destpage's translations has
                                # $page in its backlinks
                                push @{$links{$page}},
-                                       values %{otherlanguages($destpage)};
+                                       values %{otherlanguages_pages($destpage)};
                        }
                }
        }
@@ -241,6 +259,12 @@ sub filter (@) {
        my $page = $params{page};
        my $destpage = $params{destpage};
        my $content = $params{content};
+       my $fullpage = $params{fullpage};
+
+       unless ($fullpage) {
+               return $content;
+       }
+
        if (istranslation($page) && ! alreadyfiltered($page, $destpage)) {
                $content = po_to_markup($page, $content);
                setalreadyfiltered($page, $destpage);
@@ -285,7 +309,7 @@ sub pagetemplate (@) {
        }
        if ($template->query(name => "otherlanguages")) {
                $template->param(otherlanguages => [otherlanguagesloop($page)]);
-               map add_depends($page, $_), (values %{otherlanguages($page)});
+               map add_depends($page, $_), (values %{otherlanguages_pages($page)});
        }
        if ($config{discussion} && istranslation($page)) {
                if ($page !~ /.*\/\Q$config{discussionpage}\E$/i &&
@@ -338,12 +362,12 @@ sub renamepages (@) {
        return () unless istranslatable($torename{src});
 
        my @ret;
-       my %otherpages=%{otherlanguages($torename{src})};
+       my %otherpages=%{otherlanguages_pages($torename{src})};
        while (my ($lang, $otherpage) = each %otherpages) {
                push @ret, {
                        src => $otherpage,
                        srcfile => $pagesources{$otherpage},
-                       dest => otherlanguage($torename{dest}, $lang),
+                       dest => otherlanguage_page($torename{dest}, $lang),
                        destfile => $torename{dest}.".".$lang.".po",
                        required => 0,
                };
@@ -675,6 +699,17 @@ sub myrootpage (@) {
        return $rootpage;
 }
 
+sub myisselflink ($$) {
+       my $page=shift;
+       my $link=shift;
+
+       return 1 if $origsubs{'isselflink'}->($page, $link);
+       if (istranslation($page)) {
+               return $origsubs{'isselflink'}->(masterpage($page), $link);
+        }
+       return;
+}
+
 # ,----
 # | Blackboxes for private data
 # `----
@@ -799,7 +834,7 @@ sub islanguagecode ($) {
        return $code =~ /^[a-z]{2}$/;
 }
 
-sub otherlanguage ($$) {
+sub otherlanguage_page ($$) {
        my $page=shift;
        my $code=shift;
 
@@ -807,17 +842,31 @@ sub otherlanguage ($$) {
        return masterpage($page) . '.' . $code;
 }
 
-sub otherlanguages ($) {
+# Returns the list of other languages codes: the master language comes first,
+# then the codes are ordered the same way as in po_slave_languages, if it is
+# an array, or in the language name lexical order, if it is a hash.
+sub otherlanguages_codes ($) {
        my $page=shift;
 
-       my %ret;
-       return \%ret unless istranslation($page) || istranslatable($page);
+       my @ret;
+       return \@ret unless istranslation($page) || istranslatable($page);
        my $curlang=lang($page);
        foreach my $lang
-               ($config{po_master_language}{code}, keys %{$config{po_slave_languages}}) {
+               ($config{po_master_language}{code}, @slavelanguages) {
                next if $lang eq $curlang;
-               $ret{$lang}=otherlanguage($page, $lang);
+               push @ret, $lang;
        }
+       return \@ret;
+}
+
+sub otherlanguages_pages ($) {
+       my $page=shift;
+
+        my %ret;
+       map {
+               $ret{$_} = otherlanguage_page($page, $_)
+       } otherlanguages_codes($page);
+
        return \%ret;
 }
 
@@ -848,18 +897,18 @@ sub refreshpot ($) {
        my $masterfile=shift;
 
        my $potfile=potfile($masterfile);
-       my %options = ("markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0);
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->{TT}{utf_mode} = 1;
-       $doc->{TT}{file_in_charset} = 'utf-8';
-       $doc->{TT}{file_out_charset} = 'utf-8';
+       $doc->{TT}{file_in_charset} = 'UTF-8';
+       $doc->{TT}{file_out_charset} = 'UTF-8';
        $doc->read($masterfile);
        # let's cheat a bit to force porefs option to be passed to
        # Locale::Po4a::Po; this is undocument use of internal
        # Locale::Po4a::TransTractor's data, compulsory since this module
        # prevents us from using the porefs option.
        $doc->{TT}{po_out}=Locale::Po4a::Po->new({ 'porefs' => 'none' });
-       $doc->{TT}{po_out}->set_charset('utf-8');
+       $doc->{TT}{po_out}->set_charset('UTF-8');
        # do the actual work
        $doc->parse;
        IkiWiki::prep_writefile(basename($potfile),dirname($potfile));
@@ -940,15 +989,13 @@ sub percenttranslated ($) {
        return gettext("N/A") unless istranslation($page);
        my $file=srcfile($pagesources{$page});
        my $masterfile = srcfile($pagesources{masterpage($page)});
-       my %options = (
-               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-       );
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->process(
                'po_in_name'    => [ $file ],
                'file_in_name'  => [ $masterfile ],
-               'file_in_charset'  => 'utf-8',
-               'file_out_charset' => 'utf-8',
+               'file_in_charset'  => 'UTF-8',
+               'file_out_charset' => 'UTF-8',
        ) or error("po(percenttranslated) ".
                   sprintf(gettext("failed to translate %s"), $page));
        my ($percent,$hit,$queries) = $doc->stats();
@@ -970,30 +1017,25 @@ sub otherlanguagesloop ($) {
        my $page=shift;
 
        my @ret;
-       my %otherpages=%{otherlanguages($page)};
-       while (my ($lang, $otherpage) = each %otherpages) {
-               if (istranslation($page) && masterpage($page) eq $otherpage) {
-                       push @ret, {
-                               url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
-                               code => $lang,
-                               language => languagename($lang),
-                               master => 1,
-                       };
-               }
-               elsif (istranslation($otherpage)) {
-                       push @ret, {
-                               url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
-                               code => $lang,
-                               language => languagename($lang),
-                               percent => percenttranslated($otherpage),
-                       }
+       if (istranslation($page)) {
+               push @ret, {
+                       url => urlto_with_orig_beautiful_urlpath(masterpage($page), $page),
+                       code => $config{po_master_language}{code},
+                       language => $config{po_master_language}{name},
+                       master => 1,
+               };
+       }
+       foreach my $lang (@{otherlanguages_codes($page)}) {
+               next if $lang eq $config{po_master_language}{code};
+               my $otherpage = otherlanguage_page($page, $lang);
+               push @ret, {
+                       url => urlto_with_orig_beautiful_urlpath($otherpage, $page),
+                       code => $lang,
+                       language => languagename($lang),
+                       percent => percenttranslated($otherpage),
                }
        }
-       return sort {
-               return -1 if $a->{code} eq $config{po_master_language}{code};
-               return 1 if $b->{code} eq $config{po_master_language}{code};
-               return $a->{language} cmp $b->{language};
-       } @ret;
+       return @ret;
 }
 
 sub homepageurl (;$) {
@@ -1094,15 +1136,13 @@ sub po_to_markup ($$) {
                or return $fail->(sprintf(gettext("failed to write %s"), $infile));
 
        my $masterfile = srcfile($pagesources{masterpage($page)});
-       my %options = (
-               "markdown" => (pagetype($masterfile) eq 'mdwn') ? 1 : 0,
-       );
-       my $doc=Locale::Po4a::Chooser::new('text',%options);
+       my $doc=Locale::Po4a::Chooser::new(po4a_type($masterfile),
+                                          po4a_options($masterfile));
        $doc->process(
                'po_in_name'    => [ $infile ],
                'file_in_name'  => [ $masterfile ],
-               'file_in_charset'  => 'utf-8',
-               'file_out_charset' => 'utf-8',
+               'file_in_charset'  => 'UTF-8',
+               'file_out_charset' => 'UTF-8',
        ) or return $fail->(gettext("failed to translate"));
        $doc->write($outfile)
                or return $fail->(sprintf(gettext("failed to write %s"), $outfile));
@@ -1160,6 +1200,37 @@ sub isvalidpo ($) {
                                        "to previous page to continue edit"));
 }
 
+sub po4a_type ($) {
+       my $file = shift;
+
+       my $pagetype = pagetype($file);
+       if ($pagetype eq 'html') {
+               return 'xhtml';
+        }
+       return 'text';
+}
+
+sub po4a_options($) {
+       my $file = shift;
+
+       my %options;
+       my $pagetype = pagetype($file);
+
+       if ($pagetype eq 'html') {
+               # how to disable options is not consistent across po4a modules
+               $options{includessi} = '';
+               $options{includeexternal} = 0;
+        }
+       elsif ($pagetype eq 'mdwn') {
+               $options{markdown} = 1;
+        }
+        else {
+               $options{markdown} = 0;
+        }
+
+       return %options;
+}
+
 # ,----
 # | PageSpecs
 # `----
@@ -1220,4 +1291,19 @@ sub match_currentlang ($$;@) {
        }
 }
 
+sub match_needstranslation ($$;@) {
+       my $page=shift;
+
+       my $percenttranslated=IkiWiki::Plugin::po::percenttranslated($page);
+       if ($percenttranslated eq 'N/A') {
+               return IkiWiki::FailReason->new("file is not a translation page");
+       }
+       elsif ($percenttranslated < 100) {
+               return IkiWiki::SuccessReason->new("file has $percenttranslated translated");
+        }
+       else {
+               return IkiWiki::FailReason->new("file is fully translated");
+       }
+}
+
 1
index 2d495db2cc0d02e1bc1abbc3653627374a8179ba..100015ceeeeb4f035e165e64713e907ee4d47eb6 100644 (file)
@@ -89,7 +89,7 @@ sub sidebar_content ($) {
                return IkiWiki::htmlize($sidebar_page, $page, $sidebar_type,
                       IkiWiki::linkify($sidebar_page, $page,
                       IkiWiki::preprocess($sidebar_page, $page,
-                      IkiWiki::filter($sidebar_page, $page, $content))));
+                      IkiWiki::filter($sidebar_page, $page, $content, 'fullpage'))));
        }
 
 }
index a653ab2da02e542828349c3d9f3ac200340608f4..233d093eda602387902cfade75573ee62760a3ed 100644 (file)
@@ -232,7 +232,7 @@ sub render ($$) {
                        linkify($page, $page,
                        preprocess($page, $page,
                        filter($page, $page,
-                       readfile($srcfile)))));
+                       readfile($srcfile), 'fullpage'))));
                
                my $output=htmlpage($page);
                writefile($output, $config{destdir}, genpage($page, $content));
@@ -837,7 +837,7 @@ sub commandline_render () {
        my $content=readfile($srcfile);
        my $page=pagename($file);
        $pagesources{$page}=$file;
-       $content=filter($page, $page, $content);
+       $content=filter($page, $page, $content, 'fullpage');
        $content=preprocess($page, $page, $content);
        $content=linkify($page, $page, $content);
        $content=htmlize($page, $page, $type, $content);
index 40ed9efb663be68814f68e46b66887dbd9b4aa55..f8b398575bd56ec952999e97ab3c47435082a225 100644 (file)
@@ -11,6 +11,8 @@ wiki:
   specified as a ISO639-1 (two-letter) language code.
 * "`currentlang()`" - tests whether a page is written in the same
   language as the current page.
+* "`needstranslation()`" - tests whether a page needs translation
+  work. Only slave pages match this PageSpec.
 
 Note that every non-po page is considered to be written in
 `po_master_language`, as specified in `ikiwiki.setup`.
index dd85edf3dd8b1313f2a2d097483ef2f612aee1a6..30bac7068e5436b177ee9915f65783fcbd72b175 100644 (file)
@@ -54,10 +54,10 @@ Supported languages
 `po_slave_languages` is used to set the list of supported "slave"
 languages, such as:
 
-        po_slave_languages => { 'fr' => 'Français',
+        po_slave_languages => [ 'fr' => 'Français',
                                 'es' => 'Español',
                                 'de' => 'Deutsch',
-        }
+        ]
 
 Decide which pages are translatable
 -----------------------------------
@@ -130,7 +130,7 @@ lighttpd
 --------
 
 Recent versions of lighttpd should be able to use
-`$HTTP["language"]` to configure the translatted pages to be served.
+`$HTTP["language"]` to configure the translated pages to be served.
 
 See [Lighttpd Issue](http://redmine.lighttpd.net/issues/show/1119)
 
@@ -213,16 +213,16 @@ preferred `$EDITOR`, without needing to be online.
 Markup languages support
 ------------------------
 
-[[Markdown|mdwn]] is well supported. Some other markup languages supported
-by ikiwiki mostly work, but some pieces of syntax are not rendered
-correctly on the slave pages:
+[[Markdown|mdwn]] and [[html]] are well supported. Some other markup
+languages supported by ikiwiki mostly work, but some pieces of syntax
+are not rendered correctly on the slave pages:
 
 * [[reStructuredText|rst]]: anonymous hyperlinks and internal
   cross-references
 * [[wikitext]]: conversion of newlines to paragraphs
 * [[creole]]: verbatim text is wrapped, tables are broken
-* [[html]] and LaTeX: not supported yet; the dedicated po4a modules
-  could be used to support them, but they would need a security audit
+* LaTeX: not supported yet; the dedicated po4a module
+  could be used to support it, but it would need a security audit
 * other markup languages have not been tested.
 
 Security
@@ -254,18 +254,6 @@ once [[intrigeri]]'s `meta` branch is merged.
 An integration branch, called `meta-po`, merges [[intrigeri]]'s `po`
 and `meta` branches, and thus has this additional features.
 
-Language display order
-----------------------
-
-Jonas pointed out that one might want to control the order that links to
-other languages are listed, for various reasons. Currently, there is no
-order, as `po_slave_languages` is a hash. It would need to be converted
-to an array to support this. (If twere done, twere best done quickly.)
---[[Joey]] 
-
-> Done in my po branch, preserving backward compatibility. Please
-> review :) --[[intrigeri]]
-
 Pagespecs
 ---------
 
index 27683f1eae22ddce2fca8c01155fee976f35c273..73858c8189972ce700b9088e09bd0a19799d715c 100644 (file)
@@ -150,6 +150,23 @@ The following analysis was done with his help.
   variables; according to [[Joey]], this is "Freaky code, but seems ok
   due to use of `quotementa`".
 
+##### Locale::Po4a::Xhtml
+
+* does not run any external program
+* does not build regexp's from untrusted variables
+
+=> Seems safe as far as the `includessi` option is disabled; the po
+plugin explicitly disables it.
+
+Relies on Locale::Po4a::Xml` to do most of the work.
+
+##### Locale::Po4a::Xml
+
+* does not run any external program
+* the `includeexternal` option makes it able to read external files;
+  the po plugin explicitly disables it
+* untrusted variables are escaped when used to build regexp's
+
 ##### Text::WrapI18N
 
 `Text::WrapI18N` can cause DoS
index a921b9a0272098e191987c80066c6e7931cafa2e..15ed08d82b698baece572f1e845e13fe1808bc35 100644 (file)
@@ -200,7 +200,9 @@ value is ignored.
 
 Runs on the raw source of a page, before anything else touches it, and can
 make arbitrary changes. The function is passed named parameters "page",
-"destpage", and "content". It should return the filtered content.
+"destpage", "content" and "fullpage". "fullpage" is a true value if,
+and only if, a full page's content is being filtered, e.g. as opposed
+to a directive parameter. It should return the filtered content.
 
 ### preprocess