]> git.ipfire.org Git - thirdparty/squid.git/blame_incremental - scripts/www/build-cfg-help.pl
Simplify appending SBuf to String (#2108)
[thirdparty/squid.git] / scripts / www / build-cfg-help.pl
... / ...
CommitLineData
1#!/usr/bin/perl -w
2#
3# * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
4# *
5# * Squid software is distributed under GPLv2+ license and includes
6# * contributions from numerous individuals and organizations.
7# * Please see the COPYING and CONTRIBUTORS files for details.
8#
9
10use strict;
11use IO::File;
12use Getopt::Long;
13use File::Basename;
14
15# This mess is designed to parse the squid config template file
16# cf.data.pre and generate a set of HTML pages to use as documentation.
17#
18# Adrian Chadd <adrian@squid-cache.org>
19
20#
21# The template file is reasonably simple to parse. There's a number of
22# directives which delineate sections but there's no section delineation.
23# A section will "look" somewhat like this, most of the time:
24# NAME: <name>
25# IFDEF: <the ifdef bit>
26# TYPE: <the config type>
27# LOC: <location in the Config struct>
28# DEFAULT: <the default value(s) - may be multiple lines>
29# DEFAULT_IF_NONE: <alternative default value>
30# DEFAULT_DOC: <the text to display instead of default value(s)>
31# DOC_START
32# documentation goes here
33# NOCOMMENT_START
34# stuff which goes verbatim into the config file goes here
35# NOCOMMENT_END
36# DOC_END
37#
38# Now, we can't assume its going to be nicely nested, so I'll say that
39# sections are delineated by NAME: lines, and then stuff is marked up
40# appropriately.
41#
42# Then we have to fake paragraph markups as well for the documentation.
43# We can at least use <PRE> type markups for the NOCOMMENT_START/_END stuff.
44
45#
46# Configuration sections are actually broken up by COMMENT_START/COMMENT_END
47# bits, which we can use in the top-level index page. Nifty!
48#
49
50# XXX NAME: can actually have multiple entries on it; we should generate
51# XXX a configuration index entry for each, linking back to the one entry.
52# XXX I'll probably just choose the first entry in the list.
53
54#
55# This code is ugly, but meh. We'll keep reading, line by line, and appending
56# lines into 'state' variables until the next NAME comes up. We'll then
57# shuffle everything off to a function to generate the page.
58
59
60my ($state) = "";
61my (%option);
62my (%all_names);
63my ($comment);
64my (%defines);
65
66my $version = "3.1.0";
67my $verbose = '';
68my $path = "/tmp";
69my $format = "splithtml";
70my $pagetemplate;
71
72my ($index) = new IO::File;
73
74my $top = dirname($0);
75
76GetOptions(
77 'verbose' => \$verbose, 'v' => \$verbose,
78 'out=s' => \$path,
79 'version=s' => \$version,
80 'format=s' => \$format
81 );
82
83if ($format eq "splithtml") {
84 $pagetemplate = "template.html";
85} elsif ($format eq "singlehtml") {
86 $pagetemplate = "template_single.html";
87}
88
89# Load defines
90my ($df) = new IO::File;
91
92$df->open("$top/../../src/cf_gen_defines", "r") || die;
93while(<$df>) {
94 $defines{$1} = $2 if /define\["([^"]*)"\]="([^"]*)"/;
95}
96close $df;
97undef $df;
98
99# XXX should implement this!
100sub uriescape($)
101{
102 my ($line) = @_;
103 return $line;
104}
105
106sub filename($)
107{
108 my ($name) = @_;
109 return $path . "/" . $name . ".html";
110}
111
112sub htmlescape($)
113{
114 my ($line) = @_;
115 return "" if !defined $line;
116 $line =~ s/&/\&amp;/g;
117 $line =~ s/</\&lt;/g;
118 $line =~ s/>/\&gt;/g;
119 $line =~ s/[^\x{20}-\x{7e}\s]/sprintf ("&#%d;", ord ($1))/ge;
120 return $line;
121}
122
123sub section_link($)
124{
125 return uriescape($_[0]).".html" if $format eq "splithtml";
126 return "#".$_[0] if $format eq "singlehtml";
127}
128
129sub toc_link($)
130{
131 return "index.html#toc_".uriescape($_[0]) if $format eq "splithtml";
132 return "#toc_".uriescape($_[0]) if $format eq "singlehtml";
133}
134
135sub alpha_link($)
136{
137 return "index_all.html#toc_".uriescape($_[0]);
138}
139
140#
141# Yes, we could just read the template file in once..!
142#
143sub generate_page($$)
144{
145 my ($template, $data) = @_;
146 my $fh;
147 my $fh_open = 0;
148 # XXX should make sure the config option is a valid unix filename!
149 if ($format eq "splithtml") {
150 my ($fn) = filename($data->{'name'});
151 $fh = new IO::File;
152 $fh->open($fn, "w") || die "Couldn't open $fn: $!\n";
153 $fh_open = 1;
154 } else {
155 $fh = $index;
156 }
157
158 $data->{"ifdef"} = $defines{$data->{"ifdef"}} if (exists $data->{"ifdef"} && exists $defines{$data->{"ifdef"}});
159
160 my ($th) = new IO::File;
161 $th->open($template, "r") || die "Couldn't open $template: $!\n";
162
163 # add in the local variables
164 $data->{"title"} = $data->{"name"};
165 $data->{"ldoc"} = $data->{"doc"};
166 $data->{"toc_link"} = toc_link($data->{"name"});
167 $data->{"alpha_link"} = alpha_link($data->{"name"});
168 if (exists $data->{"aliases"}) {
169 $data->{"aliaslist"} = join(", ", @{$data->{"aliases"}});
170 }
171 # XXX can't do this and then HTML escape..
172 # $data->{"ldoc"} =~ s/\n\n/<\/p>\n<p>\n/;
173 # XXX and the end-of-line formatting to turn single \n's into <BR>\n's.
174
175 while (<$th>) {
176 # Do variable substitution
177 s/%(.*?)%/htmlescape($data->{$1})/ge;
178 print $fh $_;
179 }
180 close $th;
181 undef $th;
182
183 if ($fh_open) {
184 close $fh;
185 undef $fh;
186 }
187}
188
189$index->open(filename("index"), "w") || die "Couldn't open ".filename("index").": $!\n" if ($format eq "splithtml");
190$index->open($path, "w") || die "Couldn't open ".filename("index").": $!\n" if ($format eq "singlehtml");
191print $index <<EOF
192<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
193<html xmlns="http://www.w3.org/1999/xhtml">
194<head>
195 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
196 <title>Squid $version configuration file</title>
197 <meta name="keywords" content="squid squid.conf config configure" />
198 <meta name="description" content="Squid $version" />
199 <link rel="stylesheet" type="text/css" href="http://www.squid-cache.org/default.css" />
200 <link rel="stylesheet" type="text/css" href="http://www.squid-cache.org/cfgman.css" />
201</head>
202<body>
203EOF
204 ;
205
206
207my ($name, $data);
208my (@chained);
209
210my $in_options = 0;
211sub start_option($$)
212{
213 my ($name, $type) = @_;
214 if (!$in_options) {
215 print $index "<ul>\n";
216 $in_options = 1;
217 }
218 return if $type eq "obsolete";
219 print $index ' <li><a href="' . htmlescape(section_link($name)) . '" name="toc_' . htmlescape($name) . '">' . htmlescape($name) . "</a></li>\n";
220}
221sub end_options()
222{
223 return if !$in_options;
224 print $index "</ul>\n";
225 $in_options = 0;
226}
227sub section_heading($)
228{
229 my ($comment) = @_;
230 print $index "<pre>\n";
231 print $index $comment;
232 print $index "</pre>\n";
233}
234sub update_defaults()
235{
236 if (defined($data->{"default_doc"})) {
237 # default text description masks out the default value display
238 if($data->{"default_doc"} ne "") {
239 print "REPLACE: default '". $data->{"default"} ."' with '" . $data->{"default_doc"} . "'\n" if $verbose;
240 $data->{"default"} = $data->{"default_doc"};
241 }
242 }
243 # when we have no predefined default use the DEFAULT_IF_NONE
244 if (defined($data->{"default_if_none"})) {
245 print "REPLACE: default '". $data->{"default"} ."' with '" . $data->{"default_if_none"} . "'\n" if $verbose && $data->{"default"} eq "";
246 $data->{"default"} = $data->{"default_if_none"} if $data->{"default"} eq "";
247 }
248}
249
250my @ifelse = ();
251while (<>) {
252 chomp;
253 last if (/^EOF$/);
254 if ($_ =~ /^NAME: (.*)$/) {
255 my (@aliases) = split(/ /, $1);
256 $data = {};
257 $data->{'version'} = $version;
258 foreach (@aliases) {
259 $all_names{$_} = $data;
260 }
261
262 $name = shift @aliases;
263
264 $option{$name} = $data;
265 $data->{'name'} = $name;
266 $data->{'aliases'} = \@aliases;
267 $data->{'default'} = "";
268 $data->{'default_doc'} = "";
269 $data->{'default_if_none'} = "";
270
271 print "DEBUG: line $.: new option: $name\n" if $verbose;
272 next;
273 } elsif ($_ =~ /^IF (.*)$/) {
274 my $cond = $1;
275 push(@ifelse, "$.: $1");
276 if (! defined $defines{$1}) {
277 print "NOTICE: line $.: unknown ./configure option '$1'\n";
278 } else {
279 $cond = $defines{$1};
280 }
281 if ($state eq "doc") {
282 $data->{"doc"} .= "if " . $cond . "\n";
283 } elsif ($state eq "comment") {
284 $comment .= "if " . $cond . "\n";
285 }
286 } elsif ($_ =~ /^ENDIF$/) {
287 pop(@ifelse);
288 if ($state eq "doc") {
289 $data->{"doc"} .= "endif\n";
290 } elsif ($state eq "comment") {
291 $comment .= "endif\n";
292 }
293 } elsif ($_ =~ /^COMMENT: (.*)$/) {
294 $data->{"comment"} = $1;
295 } elsif ($_ =~ /^TYPE: (.*)$/) {
296 $data->{"type"} = $1;
297 start_option($data->{"name"}, $data->{"type"});
298 } elsif ($_ =~ /^DEFAULT: (.*)$/) {
299 if ($1 eq "none") {
300 $data->{"default"} = "$1\n";
301 } else {
302 $data->{"default"} .= "$name $1\n";
303 }
304 } elsif ($_ =~ /^POSTSCRIPTUM: (.*)$/) {
305 if ($data->{"default"} eq "none") {
306 $data->{"default"} = "";
307 }
308 $data->{"default"} .= "$name $1\n";
309 } elsif ($_ =~ /^DEFAULT_DOC: (.*)$/) {
310 $data->{"default_doc"} .= "$1\n";
311 } elsif ($_ =~ /^DEFAULT_IF_NONE: (.*)$/) {
312 $data->{"default_if_none"} .= "$1\n";
313 } elsif ($_ =~ /^LOC:(.*)$/) {
314 $data->{"loc"} = $1;
315 $data->{"loc"} =~ s/^[\s\t]*//;
316 } elsif ($_ =~ /^DOC_START$/) {
317 update_defaults;
318 $state = "doc";
319 } elsif ($_ =~ /^DOC_END$/) {
320 $state = "";
321 my $othername;
322 foreach $othername (@chained) {
323 $option{$othername}{'doc'} = $data->{'doc'};
324 }
325 undef @chained;
326 } elsif ($_ =~ /^DOC_NONE$/) {
327 update_defaults;
328 push(@chained, $name);
329 } elsif ($_ =~ /^NOCOMMENT_START$/) {
330 $state = "nocomment";
331 } elsif ($_ =~ /^NOCOMMENT_END$/) {
332 $state = "";
333 } elsif ($_ =~ /^IFDEF: (.*)$/) {
334 $data->{"ifdef"} = $1;
335 } elsif ($_ =~ /^#/ && $state eq "doc") {
336 $data->{"config"} .= $_ . "\n";
337 } elsif ($state eq "nocomment") {
338 $data->{"config"} .= $_ . "\n";
339 } elsif ($state eq "doc") {
340 $data->{"doc"} .= $_ . "\n";
341 } elsif ($_ =~ /^COMMENT_START$/) {
342 end_options;
343 $state = "comment";
344 $comment = "";
345 } elsif ($_ =~ /^COMMENT_END$/) {
346 section_heading($comment);
347 } elsif ($state eq "comment") {
348 $comment .= $_ . "\n";
349 } elsif (/^#/) {
350 next;
351 } elsif ($_ ne "") {
352 print "NOTICE: line $.: unknown line '$_'\n";
353 }
354}
355foreach my $condition (@ifelse) {
356 print "ERROR: missing ENDIF to match $condition\n";
357}
358end_options;
359print $index "<p><a href=\"index_all.html\">Alphabetic index</a></p>\n" if $format eq "splithtml";
360print $index "<p><a href=\"#index\">Alphabetic index</a></p>\n" if $format eq "singlehtml";
361print $index "<hr />\n" if $format eq "singlehtml";
362
363# and now, build the option pages
364my (@names) = keys %option;
365foreach $name (@names) {
366 next if $option{$name}->{'type'} eq "obsolete";
367 generate_page("${top}/${pagetemplate}", $option{$name});
368}
369
370# and now, the alphabetic index file!
371my $fh;
372my $fh_open = 0;
373
374if ($format eq "splithtml") {
375 $fh = new IO::File;
376 my ($indexname) = filename("index_all");
377 $fh->open($indexname, "w") || die "Couldn't open $indexname for writing: $!\n";
378 $fh_open = 1;
379 print $fh <<EOF
380<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
381<html xmlns="http://www.w3.org/1999/xhtml">
382<head>
383 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
384 <title>Squid $version configuration file</title>
385 <meta name="keywords" content="squid squid.conf config configure" />
386 <meta name="description" content="Squid $version" />
387 <link rel="stylesheet" type="text/css" href="http://www.squid-cache.org/default.css" />
388 <link rel="stylesheet" type="text/css" href="http://www.squid-cache.org/cfgman.css" />
389</head>
390<body>
391 <div id="header">
392 <div id="logo">
393 <h1><a href="http://www.squid-cache.org/"><span>Squid-</span>Cache.org</a></h1>
394 <h2>Optimising Web Delivery</h2>
395 </div>
396 </div>
397
398 <p>| <a href="index.html">Table of contents</a> |</p>
399
400 <h1>Alphabetic index of all options</h1>
401EOF
402 ;
403} elsif ($format eq "singlehtml") {
404 $fh = $index;
405 print $fh "<h2><a name=\"index\">Alphabetic index of all options</a></h2>\n";
406}
407
408print $fh "<ul>\n";
409
410foreach $name (sort keys %all_names) {
411 my ($data) = $all_names{$name};
412 next if $data->{'type'} eq "obsolete";
413 print $fh ' <li><a href="' . uriescape($data->{'name'}) . '.html" name="toc_' . htmlescape($name) . '">' . htmlescape($name) . "</a></li>\n";
414}
415
416print $fh "</ul>\n";
417if ($fh_open) {
418 print $fh <<EOF
419 <p>| <a href="index.html">Table of contents</a> |</p>
420 </body>
421</html>
422EOF
423 ;
424 $fh->close;
425}
426undef $fh;
427
428print $index <<EOF
429 </body>
430</html>
431EOF
432 ;
433$index->close;
434undef $index;