generate wiki output file mappings automatically
[pve-docs.git] / scan-adoc-refs
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use IO::File;
6 use JSON;
7
8 use Data::Dumper;
9
10 my $environments = {
11     default => 1,
12     wiki => 1,
13     manvolnum => 1,
14     pvelogo => 0, # ignore
15 };
16
17 my $fileinfo = {
18     outfile => {
19         default => {
20             "pve-admin-guide.adoc" => "pve-admin-guide.html",
21             "datacenter.cfg.adoc" => "datacenter.cfg.5.html",
22             "ha-manager.adoc" => "chapter-ha-manager.html",
23             "pct.adoc" => "chapter-pct.html",
24             "pve-bibliography.adoc" => "chapter-pve-bibliography.html",
25             "pve-firewall.adoc" => "chapter-pve-firewall.html",
26             "pve-installation.adoc" => "chapter-pve-installation.html",
27             "pvecm.adoc" => "chapter-pvecm.html",
28             "pvesm.adoc" => "chapter-pvesm.html",
29             "pveum.adoc" => "chapter-pveum.html",
30             "qm.adoc" => "chapter-qm.html",
31             "sysadmin.adoc" => "chapter-sysadmin.html",
32             "vzdump.adoc" => "chapter-vzdump.html",
33             "pmxcfs.adoc" => "chapter-pmxcfs.html",
34             "pve-faq.adoc" => "chapter-pve-faq.html",
35         },
36         manvolnum => {
37             "ha-manager.adoc" => "ha-manager.1",
38             "pct.adoc" => "pct.1",
39             "pveam.adoc" => "pveam.1",
40             "pveceph.adoc" => "pveceph.1",
41             "pvecm.adoc" => "pvecm.1",
42             "pveperf.adoc" => "pveperf.1",
43             "pvesm.adoc" => "pvesm.1",
44             "pvesubscription.adoc" => "pvesubscription.1",
45             "pveum.adoc" => "pveum.1",
46             "qm.adoc" => "qm.1",
47             "qmrestore.adoc" => "qmrestore.1",
48             "vzdump.adoc" => "vzdump.1",
49             "datacenter.cfg.adoc" => "datacenter.cfg.5",
50             "pct.conf.adoc" => "pct.conf.5",
51             "qm.conf.adoc" => "qm.conf.5",
52             "pmxcfs.adoc" => "pmxcfs.8",
53             "pvedaemon.adoc" => "pvedaemon.8",
54             "pve-firewall.adoc" => "pve-firewall.8",
55             "pve-ha-crm.adoc" => "pve-ha-crm.8",
56             "pve-ha-lrm.adoc" => "pve-ha-lrm.8",
57             "pveproxy.adoc" => "pveproxy.8",
58             "pvestatd.adoc" => "pvestatd.8",
59             "spiceproxy.adoc" => "spiceproxy.8",
60         },
61     },
62 };
63
64 my $start_env = [];
65 foreach my $e (keys %$environments) {
66     push @$start_env, $e if $environments->{$e};
67 }
68
69 my $env_stack = [$start_env];
70 my $env_name_stack = [];
71
72 sub reset_environment_stack {
73     $env_stack = [$start_env];
74     $env_name_stack = [];
75 }
76
77 sub push_environment {
78     my ($env, $not) = @_;
79
80     die "undefined environment '$env'\n" if !defined($environments->{$env});
81
82     # FIXME: this seems wrong  (nested env?)?
83     return if !$environments->{$env}; # do not track
84
85     if ($not) {
86         my $new_env = [];
87         foreach my $e (@{$env_stack->[-1]}) {
88             if ($e ne $env) {
89                 push @$new_env, $e;
90             }
91         }
92         die "empty environment" if !scalar($new_env);
93         push @$env_stack, $new_env;
94     } else {
95         push @$env_stack, [$env];
96     }
97
98     push @$env_name_stack, $env;
99 }
100
101 sub pop_environment {
102     my ($env) = @_;
103
104     die "undefined environment '$env'\n" if !defined($environments->{$env});
105
106     return if !$environments->{$env}; # do not track
107
108     pop @$env_stack;
109     my $res = pop @$env_name_stack;
110
111     die "environment missmatch ($res != $env)\n" if $res ne $env;
112 }
113
114 sub register_include {
115     my ($filename, $include_filename, $env_list) = @_;
116
117     return if $include_filename !~ m/\.adoc$/; # skip attributes.txt
118
119     foreach my $e (@$env_list) {
120         $fileinfo->{include}->{$e}->{$filename}->{$include_filename} = 1;
121     }
122 }
123
124 sub register_blockid {
125     my ($filename, $blockid, $reftext, $env_list) = @_;
126
127     foreach my $e (@$env_list) {
128         my $fn = $fileinfo->{blockid}->{$e}->{$blockid};
129         die "blockid '$blockid' already defined in $fn"
130             if defined($fn);
131         $fileinfo->{blockid}->{$e}->{$blockid} = $filename;
132         $fileinfo->{reftext}->{$e}->{$blockid} = $reftext
133             if defined($reftext);
134     }
135 }
136
137 sub register_title {
138     my ($filename, $env, $doctype, $title, $blockid) = @_;
139
140     # fixme: what about other macros?
141     $title =~ s/\{pve\}/Proxmox VE/g;
142     $title =~ s!http://\S+\[(.*?)\]!$1!g;
143
144     # register document title (onyl once)
145     if (!defined($fileinfo->{titles}->{$env}->{$filename})) {
146
147         $fileinfo->{titles}->{$env}->{$filename} = $title;
148
149         if (defined($doctype)) {
150             $fileinfo->{doctype}->{$env}->{$filename} = $doctype;
151         } else {
152             die "unable to change title (no doctype)"
153                 if !defined($fileinfo->{doctype}->{$env}->{$filename});
154         }
155
156         if (defined($doctype) && ($env eq 'manvolnum') && ($doctype == 0)) {
157             if ($title =~ m/.*\(([1-8])\)\s*$/) {
158                 $fileinfo->{mansection}->{$env}->{$filename} = $1;
159             }
160         }
161     }
162
163     if ($blockid) {
164         die "internal error"
165             if !defined($fileinfo->{blockid}->{$env}->{$blockid});
166         $fileinfo->{reftitle}->{$env}->{$blockid} = $title;
167     }
168 }
169
170 sub scan_adoc_file {
171     my ($filename) = @_;
172
173     reset_environment_stack();
174
175     # print "SCAN $filename\n";
176
177     my $fh = IO::File->new("$filename", "r") or
178         die "unable to open file '$filename' - $!\n";
179
180     my $env_last_line = {};
181     my $env_last_blockid = {};
182
183     while (defined (my $line = <$fh>)) {
184         if ($line =~ m/^if(n?)def::(\S+)\[(.*)\]\s*$/) {
185             my ($not, $env, $text) = ($1, $2, $3);
186             die "unsuported ifdef usage - implement me" if $text;
187             push_environment($env, $not);
188             next;
189         } elsif ($line =~ m/^endif::(\S+)\[(.*)\]\s*$/) {
190             my ($env, $text) = ($1, $2);
191             die "unsuported ifdef usage - implement me" if $text;
192             pop_environment($env);
193             next;
194         } elsif ($line =~ m/^include::(\S+)\[.*\]\s*$/) {
195             register_include($filename, $1, $env_stack->[-1]);
196             next;
197         }
198
199         # try to detect titles
200         foreach my $e (@{$env_stack->[-1]}) {
201             if ($line =~ m/^=====+$/) {
202                 register_title($filename, $e, 0, $env_last_line->{$e},
203                                $env_last_blockid->{$e});
204             } elsif ($line =~ m/^-----+$/) {
205                 register_title($filename, $e, 1, $env_last_line->{$e},
206                                $env_last_blockid->{$e});
207             } elsif ($line =~ m/^~~~~~+$/) {
208                 register_title($filename, $e, 2, $env_last_line->{$e},
209                                $env_last_blockid->{$e});
210             } elsif ($line =~ m/^\^\^\^\^\^+$/) {
211                 register_title($filename, $e, 3, $env_last_line->{$e},
212                                $env_last_blockid->{$e});
213             } elsif ($line =~ m/^= +(\S.*?)( +=)?$/) {
214                 register_title($filename, $e, 0, $1, $env_last_blockid->{$e});
215             } elsif ($line =~ m/^== +(\S.*?)( +==)?$/) {
216                 register_title($filename, $e, 1, $1, $env_last_blockid->{$e});
217             } elsif ($line =~ m/^=== +(\S.*?)( +===)?$/) {
218                 register_title($filename, $e, 2, $1, $env_last_blockid->{$e});
219             } elsif ($line =~ m/^==== +(\S.*?)( +====)?$/) {
220                 register_title($filename, $e, 3, $1, $env_last_blockid->{$e});
221             }
222
223             $env_last_line->{$e} = $line;
224             chomp $env_last_line->{$e};
225         }
226
227         if ($line =~ m/^:(\S+?):\s*(.*\S)?\s*$/) {
228             my ($key, $value) = ($1, $2);
229             if ($key eq 'pve-toplevel') {
230
231                 foreach my $e (@{$env_stack->[-1]}) {
232                     my $title = $fileinfo->{titles}->{$e}->{$filename};
233                     die "not title for toplevel file '$filename' (env=$e)\n"
234                         if !defined($title);
235                     $fileinfo->{toplevel}->{$e}->{$filename} = 1;
236                 }
237             } elsif ($key eq 'title') {
238                 foreach my $e (@{$env_stack->[-1]}) {
239                     register_title($filename, $e, undef, $value);
240                 }
241             }
242         }
243
244         if ($line =~ m/^\[\[(.*)\]\]\s*$/) {
245             my $blockid = $1;
246             die "implement me" if $blockid =~m/,/;
247             my $reftext = '';
248             register_blockid($filename, $blockid, $reftext, $env_stack->[-1]);
249             foreach my $e (@{$env_stack->[-1]}) {
250                 $env_last_blockid->{$e} = $blockid;
251             }
252         }
253
254         if ($line =~ m/^\s*$/) {
255             foreach my $e (@{$env_stack->[-1]}) {
256                 delete $env_last_blockid->{$e};
257             }
258         }
259
260         # fixme: "anchor:"
261         # bibliography anchors
262         if ($line =~ m/\[\[\[([^\]]*)\]\]\]/) {
263             my $blockid = $1;
264             die "implement me" if $blockid =~m/,/;
265             register_blockid($filename, $blockid, "&#91;$blockid&#93;", $env_stack->[-1]);
266         }
267     }
268 }
269
270 my $scanned_files = {};
271 while (my $filename = shift) {
272     next if $filename !~ m/\.adoc$/; # skip attributes.txt
273     next if $scanned_files->{$filename};
274
275     scan_adoc_file($filename);
276     $scanned_files->{$filename} = 1;
277 }
278
279 sub resolve_link_target {
280     my ($env, $filename) = @_;
281
282     my $include_hash = $fileinfo->{include}->{$env};
283
284     my $repeat = 1;
285
286     while ($repeat) {
287         $repeat = 0;
288         foreach my $fn (keys %$include_hash) {
289             if ($include_hash->{$fn}->{$filename}) {
290                 next if ($fn eq 'pve-admin-guide.adoc') &&
291                     $fileinfo->{outfile}->{$env}->{$filename};
292                 $filename = $fn;
293                 $repeat = 1;
294                 last;
295             }
296         }
297     }
298
299     return $filename;
300 }
301
302
303 # try to generate output file mapping
304 foreach my $e (@$start_env) {
305     my $toplevel_hash = $fileinfo->{toplevel}->{$e};
306     foreach my $fn (sort keys %$toplevel_hash) {
307         my $mansection = $fileinfo->{mansection}->{manvolnum}->{$fn};
308         if ($e eq 'wiki') {
309             my $realfn = $fn;
310             $realfn =~ s/\.adoc$//;
311             if (defined($mansection) && ($mansection == 5)) {
312                 $realfn .= ".$mansection";
313             }
314             my $realfn = "$realfn-plain.html";
315             $fileinfo->{outfile}->{$e}->{$fn} = $realfn;
316         }
317     }
318 }
319
320 # now resolve blockids
321 foreach my $e (@$start_env) {
322     my $blockid_hash = $fileinfo->{blockid}->{$e};
323     foreach my $blockid (keys %$blockid_hash) {
324         my $fn = resolve_link_target($e, $blockid_hash->{$blockid});
325         if ($e eq 'wiki') {
326             my $title = $fileinfo->{titles}->{$e}->{$fn};
327             $title =~ s/\s/_/g;
328             die "found not title for '$fn' in env '$e'" if !$title;
329             $fileinfo->{blockid_target}->{$e}->{$blockid} = "link:/wiki/$title#$blockid";
330
331             # we do not produce wiki pages for all content
332             #my $realfn = $fileinfo->{outfile}->{$e}->{$fn};
333             #warn "no output file mapping for '$fn' ($e)\n" if !$realfn;
334
335         } elsif ($e eq 'default') {
336             my $realfn = $fileinfo->{outfile}->{$e}->{$fn} ||
337                 die "no output file mapping for '$fn'\n";
338             $fileinfo->{blockid_target}->{$e}->{$blockid} = "link:/pve-docs/$realfn#$blockid";
339         } elsif ($e eq 'manvolnum') {
340             # we do not produce manpages for all content
341             # my $realfn = $fileinfo->{outfile}->{$e}->{$fn} ||
342             # warn "no output file mapping for '$fn'\n";
343             $fileinfo->{blockid_target}->{$e}->{$blockid} = $fn;
344         }
345     }
346 }
347
348
349 print to_json($fileinfo, { pretty => 1,  canonical => 1 } );