initial FAQ 2 wiki script
authorNigel Metheringham <nigel@exim.org>
Fri, 12 Nov 2004 14:48:08 +0000 (14:48 +0000)
committerNigel Metheringham <nigel@exim.org>
Fri, 12 Nov 2004 14:48:08 +0000 (14:48 +0000)
doc/doc-scripts/f2wiki [new file with mode: 0644]

diff --git a/doc/doc-scripts/f2wiki b/doc/doc-scripts/f2wiki
new file mode 100644 (file)
index 0000000..253f9d8
--- /dev/null
@@ -0,0 +1,457 @@
+#!/usr/bin/perl
+#
+# Script to convert Exim FAQ into wiki markup - moin flavour
+#
+# $Cambridge: exim/doc/doc-scripts/f2wiki,v 1.1 2004/11/12 14:48:08 nm4 Exp $
+#
+use strict;
+use integer;
+use bytes;
+use Data::Dumper;
+use IO::File;
+
+sub mkwikiname (@) {
+    my @in = @_;
+
+    my @bits;
+    foreach my $str (@in) {
+       $str =~ tr/0-9A-Za-z _//cd;
+       push(@bits, $str);
+    }
+    return join('/', @bits);
+}
+
+\f
+
+sub mkwikifilename ($) {
+    my $wn = shift;
+
+    $wn =~ s/([^A-Za-z0-9])/sprintf("_%02x",ord($1))/eg;
+
+    return $wn;
+}
+
+\f
+
+sub format_wiki_question_header ($) {
+    my $lp = shift;
+
+    my @lines = @{$lp};
+    my $i;
+    for($i =0; ($i <= $#lines); $i++) {
+       unless (defined($lines[$i])) {
+           splice(@lines, $i);
+           last;
+       }
+       $lines[$i] =~ s/^\s+//;
+    }
+    return('= ' . join(' ', @lines) . ' =');
+}
+
+\f
+
+sub wikiref ($$) {
+    my $meta = shift;
+    my $qref = shift;
+
+    my $qtag = $meta->qtags->{$qref};
+    unless ($qtag) {
+       warn "Unknown qtag $qref\n";
+       return $qref;
+    }
+
+    return join(':', '[', $qref->{wikiname}, $qref . ']');
+}
+
+\f
+
+sub wiki_markup ($$) {
+    my $meta = shift;
+    my $s = shift;
+
+    $s =~ s/@\\/\@\@backslash\@\@/g;           # @\ temporarily hidden
+
+    $s =~ s/\\#/ /g;                            # \# is a hard space
+
+    $s =~ s/\\\*\*([^*]*)\*\*\\/'''$1'''/g;    # \**...**\   => bold
+    $s =~ s/\\\*([^*]*)\*\\/''$1''/g;          # \*.....*\   => italic
+    $s =~ s/\\"([^"]*)"\\/`$1`/g;              # \"....."\   => fixed pitch
+    $s =~ s/\\\$([^\$]*)\$\\/''\$$1''/g;       # \$.....$\   => $italic
+    $s =~ s/\\\\([^\\]*)\\\\/<small>$1<\/small>/g; # \\.....\\   => small
+    $s =~ s/\\\(([^)]*)\)\\/''$1''/g;          # \(.....)\   => italic
+    $s =~ s/\\-([^\\]*)-\\/'''-$1'''/g;                # \-.....-\   => -bold
+    $s =~ s/\\\[([^]]*)\]\\/''$1''/gx;         # \[.....]\   => <italic>
+    $s =~ s/\\\?(.*?)\?\\/$1/g;                        # \?.....?\   => URL
+    $s =~ s/\\\^\^([^^]*)\^\^\\/''$1''/g;      # \^^...^^\   => italic
+    $s =~ s/\\\^([^^]*)\^\\/''$1''/g;          # \^.....^\   => italic
+    $s =~ s/\\%([^%]*)%\\/'''$1'''/g;          # \%.....%\   => bold
+    $s =~ s/\\\/([^\/]*)\/\\/''$1''/g;         # \/...../\   => italic
+    $s =~ s/\\([^\\]+)\\/`$1`/g;               # \.......\   => fixed pitch
+
+    $s =~ s"//([^/\"]*)//"''$1</i>"g;          # //.....//   => italic
+    $s =~ s/::([^:]*)::/''$1:''/g;             # ::.....::   => italic:
+
+    $s =~ s/``(.*?)''/&#147;$1&#148;/g;                          # ``.....''   => quoted text
+
+    #$s =~ s/\s*\[\[br\]\]\s*/<br>/g;          # [[br]]      => <br>
+
+    $s =~ s/\@\@backslash\@\@/\\/g;                    # Put back single backslash
+
+    $s =~ s/^(\s*\(\d\)\s)/$1&nbsp;/;          # Extra space after (1), etc.
+
+    # Cross references within paragraphs
+
+    $s =~ s/Q(\d{4})(?!:)/wikiref($meta, $1)/xg;
+
+    # References to configuration samples
+
+    ##$s =~ s/\b([CFLS]\d\d\d)\b/<a href="$1.txt">$1<\/a>/g;
+
+    # Remove white space preceding a newline in the middle of paragraphs,
+    # to keep the file smaller (and for human reading when debugging).
+
+    ##$s =~ s/^\s+//mg;
+
+    return $s;
+}
+
+\f
+
+sub clip_paragraph ($) {
+    my $lines = shift;
+
+    my $ret;
+    my $flags;
+    my $offlen;
+
+    # split off and throw initial para breaks
+    while (($#{$lines} >= 0) && (!defined($lines->[0]))) {
+       shift @{$lines};
+    }
+
+    # if nothing else return
+    return('', 'empty')
+      unless ($#{$lines} >= 0);
+
+    # deal with example chunks
+    if ($lines->[0] =~ /^(\=\=\>\s+)\S/) {
+       $offlen = length($1);
+       while (($#{$lines} >= 0) && (defined($lines->[0]))) {
+           my $txt = substr(shift @{$lines}, $offlen);
+           $ret .= (defined($ret)) ? "\n$txt" : $txt;
+       }
+       return ($ret, 'code');
+    }
+
+    my $skipone;
+    # deal with rest - numeric lines first
+    if ($lines->[0] =~ /^(\s+\(\d+\)\s*)/) {
+       $offlen = length($1);
+       $flags = 'numlist';
+       $skipone = 0;
+    } elsif ($lines->[0] =~ /^(\s+)\S/) {
+       $offlen = length($1);
+       $flags = 'normal';
+       $skipone = 0;
+    } else {
+       $offlen = 7;
+       $flags = 'normal';
+       $skipone = 1;
+    }
+
+    while (($#{$lines} >= 0) && (defined($lines->[0]))) {
+       my $txt = $skipone ?
+         shift @{$lines} :
+           substr(shift @{$lines}, $offlen);
+       $ret .= $txt;
+       $ret .= ' ';
+       $skipone = 0;
+    }
+    return ($ret, $flags);
+}
+
+\f
+
+sub format_wiki_text ($$) {
+    my $meta = shift;
+    my $lp = shift;
+
+    my @lines = @{$lp};
+
+    my $out;
+    while ($#lines >= 0) {
+       my($para, $flags) = clip_paragraph(\@lines);
+       if ($flags eq 'code') {
+           $out .= "{{{\n" . $para . "\n}}}\n";
+       } elsif ($flags eq 'numlist') {
+           $out .= ' 1. ' . wiki_markup($meta, $para) . "\n";
+       } elsif ($flags eq 'empty') {
+       } else {
+           $out .= wiki_markup($meta, $para) . "\n";
+       }
+    }
+    return $out;
+}
+
+\f
+
+sub output_wiki_header ($$$) {
+    my $fh = shift;
+    my $meta = shift;
+    my $qset = shift;
+
+    $fh->print(join("\n",
+                   '##language:en',
+                   '#pragma section-numbers off',
+                   '## Autogenerated by f2wiki',
+                   join('', '["FAQ"] / [:', 
+                        $qset->{section}->{wikiname},
+                        ':',
+                        $qset->{section}->{title},
+                        '] / ',
+                        $qset->{qtag}),
+                   '----',
+                   '[[Navigation(siblings)]]',
+                   '----',
+                   ''));
+}
+
+\f
+
+sub output_wiki_question ($$$$) {
+    my $fh = shift;
+    my $meta = shift;
+    my $qset = shift;
+    my $lines = shift;
+
+    $fh->print(join("\n",
+                   ('= ' . $qset->{qtag} . ' ='),
+                   '',
+                   '=== Question ===',
+                   '##qstart',
+                   format_wiki_text($meta, $lines),
+                   '##qend',
+                   ''));
+}
+
+\f
+
+sub output_wiki_answer ($$$$) {
+    my $fh = shift;
+    my $meta = shift;
+    my $qset = shift;
+    my $lines = shift;
+
+    $fh->print(join("\n",
+                   '=== Answer ===',
+                   format_wiki_text($meta, $lines),
+                   ''));
+}
+
+\f
+
+sub output_wiki_trailer ($$$) {
+    my $fh = shift;
+    my $meta = shift;
+    my $qset = shift;
+
+    $fh->print(join("\n",
+                   '----',
+                   '[[Navigation(siblings)]]',
+                   '----',
+                   join('', '["FAQ"] / [:', 
+                        $qset->{section}->{wikiname},
+                        ':',
+                        $qset->{section}->{title},
+                        '] / ',
+                        $qset->{qtag}),
+                   '----',
+                   'CategoryFrequentlyAskedQuestions',
+                   ''));
+}
+
+\f
+
+sub build_tocs ($) {
+    my $meta = shift;
+
+    my $tfh = IO::File->new('FAQ', 'w');
+    foreach my $sect (values %{$meta->{sections}}) {
+       my $fh = IO::File->new($sect->{wikifile}, 'w');
+       $fh->print(join("\n",
+                       '##language:en',
+                       '#pragma section-numbers off',
+                       '## Autogenerated by f2wiki',
+                       join('', '["FAQ"] / [:', 
+                            $sect->{wikiname},
+                            ':',
+                            $sect->{title},
+                            '] '),
+                       '----',
+                       '[[Navigation(siblings,1)]]',
+                       '----',
+                       '[[Navigation(children)]]',
+                       '----',
+                       '',
+                       '',
+                      '= ' . $sect->{title} . ' =',
+                      '',
+                      join('',
+                           '[[Include(^',
+                           $sect->{wikiname},
+                           '/.*,,2,from="##qstart",to="##qend")]]'),
+                       '',
+                       '----',
+                       '[[Navigation(siblings,1)]]',
+                       '----',
+                       '[[Navigation(children)]]',
+                       '----',
+                       join('', '["FAQ"] / [:', 
+                            $sect->{wikiname},
+                            ':',
+                            $sect->{title},
+                            '] '),
+                       '----',
+                       'CategoryFrequentlyAskedQuestions',
+                       ''));
+
+       $tfh->print(' * [:', $sect->{wikiname}, ':', $sect->{title}, "]\n");
+    }
+}
+
+\f
+
+sub process_qset ($$$$) {
+    my $meta = shift;
+    my $qset = shift;
+    my $qlines = shift;
+    my $alines = shift;
+
+    unless ($qset->{wikifile}) {
+       print(join("\n#",
+                  $qset->{qtag},
+                  $qset->{wikiname},
+                  $qset->{wikifile}),
+             "\n");
+       return;
+    }
+    my $fh = IO::File->new($qset->{wikifile}, 'w') ||
+      die "$qset->{wikifile} OUT $!";
+    output_wiki_header($fh, $meta, $qset);
+    output_wiki_question($fh, $meta, $qset, $qlines);
+    output_wiki_answer($fh, $meta, $qset, $alines);
+    output_wiki_trailer($fh, $meta, $qset);
+}
+
+\f
+
+sub parse_faqsrc ($$) {
+    my $fh = shift;
+    my $meta = shift;
+
+    my $section;
+    my $sect;
+
+    while(<$fh>) {
+       chomp;
+       unless(defined($section)) {
+           unless (/^\d+\.\s/) {
+               if (/^\s+\d+\./) {
+                   my($junk,
+                      $secnum,
+                      $sectitle) = split(/\s+/, $_, 3);
+                   $secnum =~ tr/0-9//cd;
+                   my $wikiname = mkwikiname('FAQ', $sectitle);
+                   my $wikifile = mkwikifilename($wikiname);
+                   $meta->{sections}->{$secnum} =
+                     {title    => $sectitle,
+                      num      => $secnum,
+                      wikiname => $wikiname,
+                      wikifile => $wikifile,
+                      qtags    => []};
+               }
+               next;
+           }
+       }
+       if (/^(\d+)\.\s/) {
+           $section = $1;
+           $sect = $meta->{sections}->{$section};
+           $sect->{seen}++;
+       } elsif (/^(Q\d+):/) {
+           my $qtag = $1;
+           my $wikiname = mkwikiname('FAQ', $sect->{title}, $qtag);
+           my $wikifile = mkwikifilename($wikiname);
+           my $qset = {section         => $sect,
+                       qtag            => $qtag,
+                       wikiname        => $wikiname,
+                       wikifile        => $wikifile};
+           $meta->{qtags}->{$qtag} = $qset;
+           push(@{$sect->{qtags}}, $qset);
+       }
+    }
+}
+
+\f
+
+sub process_faqsrc ($$) {
+    my $fh = shift;
+    my $meta = shift;
+
+    my $qset;
+    my $qlines = [];
+    my $alines = [];
+    my $clines = $qlines;
+
+    while(<$fh>) {
+       chomp;
+       next if (/^#/);
+       # skip preceding stuff....
+       unless(defined($qset)) {
+           next unless (/^Q\d+/);
+       }
+
+       if (/^(\d+)\.\s/) {
+           # just skip section boundaries - we have done those before
+           next;
+       } elsif (/^([QA]\d+):\s+(.+)$/) {
+           my $qtag = $1;
+           my $line = $2;
+           if (substr($1, 0, 1) eq 'Q') {
+               process_qset($meta, $qset, $qlines, $alines);
+               $qlines = [];
+               $alines = [];
+               $clines = $qlines;
+               $qset = $meta->{qtags}->{$qtag};
+           } else {
+               $clines = $alines;
+           }
+           push(@{$clines}, $line);
+       } elsif (/^\s*$/) {
+           push(@{$clines}, undef);
+       } else {
+           push(@{$clines}, $_);
+       }
+    }
+    # mop up last q&a
+    process_qset($meta, $qset, $qlines, $alines);
+
+    # now build the tocs
+    build_tocs($meta);
+}
+
+\f
+
+# main
+{
+    my $section;
+
+    my $fh = IO::File->new(shift, 'r') || die $!;
+    my $state = {};
+    parse_faqsrc($fh, $state);
+    $fh->seek(0,0);
+#    print Dumper($state);
+    process_faqsrc($fh, $state);
+
+}
+
+# -*-perl-*-