CRL addition returns count of CRLs added
[exim.git] / doc / doc-scripts / f2wiki
1 #!/usr/bin/perl
2 #
3 # Script to convert Exim FAQ into wiki markup - moin flavour
4 #
5 use strict;
6 use integer;
7 use bytes;
8 use Data::Dumper;
9 use IO::File;
10
11 sub mkwikiname (@) {
12 my @in = @_;
13
14 my @bits;
15 foreach my $str (@in) {
16 $str =~ tr/0-9A-Za-z _//cd;
17 push(@bits, $str);
18 }
19 return join('/', @bits);
20 }
21
22 \f
23
24 sub mkwikifilename ($) {
25 my $wn = shift;
26
27 $wn =~ s/([^A-Za-z0-9])/sprintf("_%02x",ord($1))/eg;
28
29 return $wn;
30 }
31
32 \f
33
34 sub format_wiki_question_header ($) {
35 my $lp = shift;
36
37 my @lines = @{$lp};
38 my $i;
39 for($i =0; ($i <= $#lines); $i++) {
40 unless (defined($lines[$i])) {
41 splice(@lines, $i);
42 last;
43 }
44 $lines[$i] =~ s/^\s+//;
45 }
46 return('= ' . join(' ', @lines) . ' =');
47 }
48
49 \f
50
51 sub wikiref ($$) {
52 my $meta = shift;
53 my $qref = shift;
54
55 my $qtag = $meta->qtags->{$qref};
56 unless ($qtag) {
57 warn "Unknown qtag $qref\n";
58 return $qref;
59 }
60
61 return join(':', '[', $qref->{wikiname}, $qref . ']');
62 }
63
64 \f
65
66 sub wiki_markup ($$) {
67 my $meta = shift;
68 my $s = shift;
69
70 $s =~ s/@\\/\@\@backslash\@\@/g; # @\ temporarily hidden
71
72 $s =~ s/\\#/ /g; # \# is a hard space
73
74 $s =~ s/\\\*\*([^*]*)\*\*\\/'''$1'''/g; # \**...**\ => bold
75 $s =~ s/\\\*([^*]*)\*\\/''$1''/g; # \*.....*\ => italic
76 $s =~ s/\\"([^"]*)"\\/`$1`/g; # \"....."\ => fixed pitch
77 $s =~ s/\\\$([^\$]*)\$\\/''\$$1''/g; # \$.....$\ => $italic
78 $s =~ s/\\\\([^\\]*)\\\\/<small>$1<\/small>/g; # \\.....\\ => small
79 $s =~ s/\\\(([^)]*)\)\\/''$1''/g; # \(.....)\ => italic
80 $s =~ s/\\-([^\\]*)-\\/'''-$1'''/g; # \-.....-\ => -bold
81 $s =~ s/\\\[([^]]*)\]\\/''$1''/gx; # \[.....]\ => <italic>
82 $s =~ s/\\\?(.*?)\?\\/$1/g; # \?.....?\ => URL
83 $s =~ s/\\\^\^([^^]*)\^\^\\/''$1''/g; # \^^...^^\ => italic
84 $s =~ s/\\\^([^^]*)\^\\/''$1''/g; # \^.....^\ => italic
85 $s =~ s/\\%([^%]*)%\\/'''$1'''/g; # \%.....%\ => bold
86 $s =~ s/\\\/([^\/]*)\/\\/''$1''/g; # \/...../\ => italic
87 $s =~ s/\\([^\\]+)\\/`$1`/g; # \.......\ => fixed pitch
88
89 $s =~ s"//([^/\"]*)//"''$1</i>"g; # //.....// => italic
90 $s =~ s/::([^:]*)::/''$1:''/g; # ::.....:: => italic:
91
92 $s =~ s/``(.*?)''/&#147;$1&#148;/g; # ``.....'' => quoted text
93
94 #$s =~ s/\s*\[\[br\]\]\s*/<br>/g; # [[br]] => <br>
95
96 $s =~ s/\@\@backslash\@\@/\\/g; # Put back single backslash
97
98 $s =~ s/^(\s*\(\d\)\s)/$1&nbsp;/; # Extra space after (1), etc.
99
100 # Cross references within paragraphs
101
102 $s =~ s/Q(\d{4})(?!:)/wikiref($meta, $1)/xg;
103
104 # References to configuration samples
105
106 ##$s =~ s/\b([CFLS]\d\d\d)\b/<a href="$1.txt">$1<\/a>/g;
107
108 # Remove white space preceding a newline in the middle of paragraphs,
109 # to keep the file smaller (and for human reading when debugging).
110
111 ##$s =~ s/^\s+//mg;
112
113 return $s;
114 }
115
116 \f
117
118 sub clip_paragraph ($) {
119 my $lines = shift;
120
121 my $ret;
122 my $flags;
123 my $offlen;
124
125 # split off and throw initial para breaks
126 while (($#{$lines} >= 0) && (!defined($lines->[0]))) {
127 shift @{$lines};
128 }
129
130 # if nothing else return
131 return('', 'empty')
132 unless ($#{$lines} >= 0);
133
134 # deal with example chunks
135 if ($lines->[0] =~ /^(\=\=\>\s+)\S/) {
136 $offlen = length($1);
137 while (($#{$lines} >= 0) && (defined($lines->[0]))) {
138 my $txt = substr(shift @{$lines}, $offlen);
139 $ret .= (defined($ret)) ? "\n$txt" : $txt;
140 }
141 return ($ret, 'code');
142 }
143
144 my $skipone;
145 # deal with rest - numeric lines first
146 if ($lines->[0] =~ /^(\s+\(\d+\)\s*)/) {
147 $offlen = length($1);
148 $flags = 'numlist';
149 $skipone = 0;
150 } elsif ($lines->[0] =~ /^(\s+)\S/) {
151 $offlen = length($1);
152 $flags = 'normal';
153 $skipone = 0;
154 } else {
155 $offlen = 7;
156 $flags = 'normal';
157 $skipone = 1;
158 }
159
160 while (($#{$lines} >= 0) && (defined($lines->[0]))) {
161 my $txt = $skipone ?
162 shift @{$lines} :
163 substr(shift @{$lines}, $offlen);
164 $ret .= $txt;
165 $ret .= ' ';
166 $skipone = 0;
167 }
168 return ($ret, $flags);
169 }
170
171 \f
172
173 sub format_wiki_text ($$) {
174 my $meta = shift;
175 my $lp = shift;
176
177 my @lines = @{$lp};
178
179 my $out;
180 while ($#lines >= 0) {
181 my($para, $flags) = clip_paragraph(\@lines);
182 if ($flags eq 'code') {
183 $out .= "{{{\n" . $para . "\n}}}\n";
184 } elsif ($flags eq 'numlist') {
185 $out .= ' 1. ' . wiki_markup($meta, $para) . "\n";
186 } elsif ($flags eq 'empty') {
187 } else {
188 $out .= wiki_markup($meta, $para) . "\n";
189 }
190 }
191 return $out;
192 }
193
194 \f
195
196 sub output_wiki_header ($$$) {
197 my $fh = shift;
198 my $meta = shift;
199 my $qset = shift;
200
201 $fh->print(join("\n",
202 '##language:en',
203 '#pragma section-numbers off',
204 '## Autogenerated by f2wiki',
205 join('', '["FAQ"] / [:',
206 $qset->{section}->{wikiname},
207 ':',
208 $qset->{section}->{title},
209 '] / ',
210 $qset->{qtag}),
211 '----',
212 '[[Navigation(siblings)]]',
213 '----',
214 ''));
215 }
216
217 \f
218
219 sub output_wiki_question ($$$$) {
220 my $fh = shift;
221 my $meta = shift;
222 my $qset = shift;
223 my $lines = shift;
224
225 $fh->print(join("\n",
226 ('= ' . $qset->{qtag} . ' ='),
227 '',
228 '=== Question ===',
229 '##qstart',
230 format_wiki_text($meta, $lines),
231 '##qend',
232 ''));
233 }
234
235 \f
236
237 sub output_wiki_answer ($$$$) {
238 my $fh = shift;
239 my $meta = shift;
240 my $qset = shift;
241 my $lines = shift;
242
243 $fh->print(join("\n",
244 '=== Answer ===',
245 format_wiki_text($meta, $lines),
246 ''));
247 }
248
249 \f
250
251 sub output_wiki_trailer ($$$) {
252 my $fh = shift;
253 my $meta = shift;
254 my $qset = shift;
255
256 $fh->print(join("\n",
257 '----',
258 '[[Navigation(siblings)]]',
259 '----',
260 join('', '["FAQ"] / [:',
261 $qset->{section}->{wikiname},
262 ':',
263 $qset->{section}->{title},
264 '] / ',
265 $qset->{qtag}),
266 '----',
267 'CategoryFrequentlyAskedQuestions',
268 ''));
269 }
270
271 \f
272
273 sub build_tocs ($) {
274 my $meta = shift;
275
276 my $tfh = IO::File->new('FAQ', 'w');
277 my @sections = sort { $a->{num} <=> $b->{num} }
278 values %{$meta->{sections}};
279 foreach my $sect (@sections) {
280 my $fh = IO::File->new($sect->{wikifile}, 'w');
281 $fh->print(join("\n",
282 '##language:en',
283 '#pragma section-numbers off',
284 '## Autogenerated by f2wiki',
285 join('', '["FAQ"] / [:',
286 $sect->{wikiname},
287 ':',
288 $sect->{title},
289 '] '),
290 '----',
291 '[[Navigation(siblings,1)]]',
292 '----',
293 '[[Navigation(children)]]',
294 '----',
295 '',
296 '',
297 '= ' . $sect->{title} . ' =',
298 '',
299 join('',
300 '[[Include(^',
301 $sect->{wikiname},
302 '/.*,,2,from="##qstart",to="##qend")]]'),
303 '',
304 '----',
305 '[[Navigation(siblings,1)]]',
306 '----',
307 '[[Navigation(children)]]',
308 '----',
309 join('', '["FAQ"] / [:',
310 $sect->{wikiname},
311 ':',
312 $sect->{title},
313 '] '),
314 '----',
315 'CategoryFrequentlyAskedQuestions',
316 ''));
317
318 $tfh->print(' * [:', $sect->{wikiname}, ':', $sect->{title}, "]\n");
319 }
320 }
321
322 \f
323
324 sub process_qset ($$$$) {
325 my $meta = shift;
326 my $qset = shift;
327 my $qlines = shift;
328 my $alines = shift;
329
330 unless ($qset->{wikifile}) {
331 print(join("\n#",
332 $qset->{qtag},
333 $qset->{wikiname},
334 $qset->{wikifile}),
335 "\n");
336 return;
337 }
338 my $fh = IO::File->new($qset->{wikifile}, 'w') ||
339 die "$qset->{wikifile} OUT $!";
340 output_wiki_header($fh, $meta, $qset);
341 output_wiki_question($fh, $meta, $qset, $qlines);
342 output_wiki_answer($fh, $meta, $qset, $alines);
343 output_wiki_trailer($fh, $meta, $qset);
344 }
345
346 \f
347
348 sub parse_faqsrc ($$) {
349 my $fh = shift;
350 my $meta = shift;
351
352 my $section;
353 my $sect;
354
355 while(<$fh>) {
356 chomp;
357 unless(defined($section)) {
358 unless (/^\d+\.\s/) {
359 if (/^\s+\d+\./) {
360 my($junk,
361 $secnum,
362 $sectitle) = split(/\s+/, $_, 3);
363 $secnum =~ tr/0-9//cd;
364 my $wikiname = mkwikiname('FAQ', $sectitle);
365 my $wikifile = mkwikifilename($wikiname);
366 $meta->{sections}->{$secnum} =
367 {title => $sectitle,
368 num => $secnum,
369 wikiname => $wikiname,
370 wikifile => $wikifile,
371 qtags => []};
372 }
373 next;
374 }
375 }
376 if (/^(\d+)\.\s/) {
377 $section = $1;
378 $sect = $meta->{sections}->{$section};
379 $sect->{seen}++;
380 } elsif (/^(Q\d+):/) {
381 my $qtag = $1;
382 my $wikiname = mkwikiname('FAQ', $sect->{title}, $qtag);
383 my $wikifile = mkwikifilename($wikiname);
384 my $qset = {section => $sect,
385 qtag => $qtag,
386 wikiname => $wikiname,
387 wikifile => $wikifile};
388 $meta->{qtags}->{$qtag} = $qset;
389 push(@{$sect->{qtags}}, $qset);
390 }
391 }
392 }
393
394 \f
395
396 sub process_faqsrc ($$) {
397 my $fh = shift;
398 my $meta = shift;
399
400 my $qset;
401 my $qlines = [];
402 my $alines = [];
403 my $clines = $qlines;
404
405 while(<$fh>) {
406 chomp;
407 next if (/^#/);
408 # skip preceding stuff....
409 unless(defined($qset)) {
410 next unless (/^Q\d+/);
411 }
412
413 if (/^(\d+)\.\s/) {
414 # just skip section boundaries - we have done those before
415 next;
416 } elsif (/^([QA]\d+):\s+(.+)$/) {
417 my $qtag = $1;
418 my $line = $2;
419 if (substr($1, 0, 1) eq 'Q') {
420 process_qset($meta, $qset, $qlines, $alines);
421 $qlines = [];
422 $alines = [];
423 $clines = $qlines;
424 $qset = $meta->{qtags}->{$qtag};
425 } else {
426 $clines = $alines;
427 }
428 push(@{$clines}, $line);
429 } elsif (/^\s*$/) {
430 push(@{$clines}, undef);
431 } else {
432 push(@{$clines}, $_);
433 }
434 }
435 # mop up last q&a
436 process_qset($meta, $qset, $qlines, $alines);
437
438 # now build the tocs
439 build_tocs($meta);
440 }
441
442 \f
443
444 # main
445 {
446 my $section;
447
448 my $fh = IO::File->new(shift, 'r') || die $!;
449 my $state = {};
450 parse_faqsrc($fh, $state);
451 $fh->seek(0,0);
452 # print Dumper($state);
453 process_faqsrc($fh, $state);
454
455 }
456
457 # -*-perl-*-