blob: fe3e43e06f99a8f0deaa64ac45d7217a1f47bdd9 [file] [log] [blame]
xf.li6c8fc1e2023-08-12 00:11:09 -07001#!/usr/bin/env perl
2#***************************************************************************
3# _ _ ____ _
4# Project ___| | | | _ \| |
5# / __| | | | |_) | |
6# | (__| |_| | _ <| |___
7# \___|\___/|_| \_\_____|
8#
9# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
10#
11# This software is licensed as described in the file COPYING, which
12# you should have received as part of this distribution. The terms
13# are also available at https://curl.se/docs/copyright.html.
14#
15# You may opt to use, copy, modify, merge, publish, distribute and/or sell
16# copies of the Software, and permit persons to whom the Software is
17# furnished to do so, under the terms of the COPYING file.
18#
19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20# KIND, either express or implied.
21#
22# SPDX-License-Identifier: curl
23#
24###########################################################################
25
26=begin comment
27
28This script generates the manpage.
29
30Example: gen.pl <command> [files] > curl.1
31
32Dev notes:
33
34We open *input* files in :crlf translation (a no-op on many platforms) in
35case we have CRLF line endings in Windows but a perl that defaults to LF.
36Unfortunately it seems some perls like msysgit can't handle a global input-only
37:crlf so it has to be specified on each file open for text input.
38
39=end comment
40=cut
41
42my %optshort;
43my %optlong;
44my %helplong;
45my %arglong;
46my %redirlong;
47my %protolong;
48my %catlong;
49
50use POSIX qw(strftime);
51my $date = strftime "%B %d %Y", localtime;
52my $year = strftime "%Y", localtime;
53my $version = "unknown";
54
55open(INC, "<../../include/curl/curlver.h");
56while(<INC>) {
57 if($_ =~ /^#define LIBCURL_VERSION \"([0-9.]*)/) {
58 $version = $1;
59 last;
60 }
61}
62close(INC);
63
64# get the long name version, return the man page string
65sub manpageify {
66 my ($k)=@_;
67 my $l;
68 if($optlong{$k} ne "") {
69 # both short + long
70 $l = "\\fI-".$optlong{$k}.", --$k\\fP";
71 }
72 else {
73 # only long
74 $l = "\\fI--$k\\fP";
75 }
76 return $l;
77}
78
79sub printdesc {
80 my @desc = @_;
81 my $exam = 0;
82 for my $d (@desc) {
83 if($d =~ /\(Added in ([0-9.]+)\)/i) {
84 my $ver = $1;
85 if(too_old($ver)) {
86 $d =~ s/ *\(Added in $ver\)//gi;
87 }
88 }
89 if($d !~ /^.\\"/) {
90 # **bold**
91 $d =~ s/\*\*([^ ]*)\*\*/\\fB$1\\fP/g;
92 # *italics*
93 $d =~ s/\*([^ ]*)\*/\\fI$1\\fP/g;
94 }
95 if(!$exam && ($d =~ /^ /)) {
96 # start of example
97 $exam = 1;
98 print ".nf\n"; # no-fill
99 }
100 elsif($exam && ($d !~ /^ /)) {
101 # end of example
102 $exam = 0;
103 print ".fi\n"; # fill-in
104 }
105 # skip lines starting with space (examples)
106 if($d =~ /^[^ ]/ && $d =~ /--/) {
107 for my $k (keys %optlong) {
108 my $l = manpageify($k);
109 $d =~ s/--\Q$k\E([^a-z0-9_-])([^a-zA-Z0-9_])/$l$1$2/;
110 }
111 }
112 # quote "bare" minuses in the output
113 $d =~ s/( |\\fI|^)--/$1\\-\\-/g;
114 $d =~ s/([ -]|\\fI|^)-/$1\\-/g;
115 # handle single quotes first on the line
116 $d =~ s/^(\s*)\'/$1\\(aq/;
117 # handle double quotes first on the line
118 $d =~ s/^(\s*)\"/$1\\(dq/;
119 print $d;
120 }
121 if($exam) {
122 print ".fi\n"; # fill-in
123 }
124}
125
126sub seealso {
127 my($standalone, $data)=@_;
128 if($standalone) {
129 return sprintf
130 ".SH \"SEE ALSO\"\n$data\n";
131 }
132 else {
133 return "See also $data. ";
134 }
135}
136
137sub overrides {
138 my ($standalone, $data)=@_;
139 if($standalone) {
140 return ".SH \"OVERRIDES\"\n$data\n";
141 }
142 else {
143 return $data;
144 }
145}
146
147sub protocols {
148 my ($standalone, $data)=@_;
149 if($standalone) {
150 return ".SH \"PROTOCOLS\"\n$data\n";
151 }
152 else {
153 return "($data) ";
154 }
155}
156
157sub too_old {
158 my ($version)=@_;
159 my $a = 999999;
160 if($version =~ /^(\d+)\.(\d+)\.(\d+)/) {
161 $a = $1 * 1000 + $2 * 10 + $3;
162 }
163 elsif($version =~ /^(\d+)\.(\d+)/) {
164 $a = $1 * 1000 + $2 * 10;
165 }
166 if($a < 7300) {
167 # we consider everything before 7.30.0 to be too old to mention
168 # specific changes for
169 return 1;
170 }
171 return 0;
172}
173
174sub added {
175 my ($standalone, $data)=@_;
176 if(too_old($data)) {
177 # don't mention ancient additions
178 return "";
179 }
180 if($standalone) {
181 return ".SH \"ADDED\"\nAdded in curl version $data\n";
182 }
183 else {
184 return "Added in $data. ";
185 }
186}
187
188sub single {
189 my ($f, $standalone)=@_;
190 open(F, "<:crlf", "$f") ||
191 return 1;
192 my $short;
193 my $long;
194 my $tags;
195 my $added;
196 my $protocols;
197 my $arg;
198 my $mutexed;
199 my $requires;
200 my $category;
201 my $seealso;
202 my $copyright;
203 my $spdx;
204 my @examples; # there can be more than one
205 my $magic; # cmdline special option
206 my $line;
207 my $multi;
208 my $experimental;
209 while(<F>) {
210 $line++;
211 if(/^Short: *(.)/i) {
212 $short=$1;
213 }
214 elsif(/^Long: *(.*)/i) {
215 $long=$1;
216 }
217 elsif(/^Added: *(.*)/i) {
218 $added=$1;
219 }
220 elsif(/^Tags: *(.*)/i) {
221 $tags=$1;
222 }
223 elsif(/^Arg: *(.*)/i) {
224 $arg=$1;
225 }
226 elsif(/^Magic: *(.*)/i) {
227 $magic=$1;
228 }
229 elsif(/^Mutexed: *(.*)/i) {
230 $mutexed=$1;
231 }
232 elsif(/^Protocols: *(.*)/i) {
233 $protocols=$1;
234 }
235 elsif(/^See-also: *(.*)/i) {
236 $seealso=$1;
237 }
238 elsif(/^Requires: *(.*)/i) {
239 $requires=$1;
240 }
241 elsif(/^Category: *(.*)/i) {
242 $category=$1;
243 }
244 elsif(/^Example: *(.*)/i) {
245 push @examples, $1;
246 }
247 elsif(/^Multi: *(.*)/i) {
248 $multi=$1;
249 }
250 elsif(/^Experimental: yes/i) {
251 $experimental=1;
252 }
253 elsif(/^C: (.*)/i) {
254 $copyright=$1;
255 }
256 elsif(/^SPDX-License-Identifier: (.*)/i) {
257 $spdx=$1;
258 }
259 elsif(/^Help: *(.*)/i) {
260 ;
261 }
262 elsif(/^---/) {
263 if(!$long) {
264 print STDERR "ERROR: no 'Long:' in $f\n";
265 return 1;
266 }
267 if($multi !~ /(single|append|boolean|mutex)/) {
268 print STDERR "ERROR: bad 'Multi:' in $f\n";
269 return 1;
270 }
271 if(!$category) {
272 print STDERR "ERROR: no 'Category:' in $f\n";
273 return 2;
274 }
275 if(!$examples[0]) {
276 print STDERR "$f:$line:1:ERROR: no 'Example:' present\n";
277 return 2;
278 }
279 if(!$added) {
280 print STDERR "$f:$line:1:ERROR: no 'Added:' version present\n";
281 return 2;
282 }
283 if(!$seealso) {
284 print STDERR "$f:$line:1:ERROR: no 'See-also:' field present\n";
285 return 2;
286 }
287 if(!$copyright) {
288 print STDERR "$f:$line:1:ERROR: no 'C:' field present\n";
289 return 2;
290 }
291 if(!$spdx) {
292 print STDERR "$f:$line:1:ERROR: no 'SPDX-License-Identifier:' field present\n";
293 return 2;
294 }
295 last;
296 }
297 else {
298 chomp;
299 print STDERR "WARN: unrecognized line in $f, ignoring:\n:'$_';"
300 }
301 }
302 my @desc;
303 while(<F>) {
304 push @desc, $_;
305 }
306 close(F);
307 my $opt;
308 if(defined($short) && $long) {
309 $opt = "-$short, --$long";
310 }
311 elsif($short && !$long) {
312 $opt = "-$short";
313 }
314 elsif($long && !$short) {
315 $opt = "--$long";
316 }
317
318 if($arg) {
319 $opt .= " $arg";
320 }
321
322 # quote "bare" minuses in opt
323 $opt =~ s/( |^)--/$1\\-\\-/g;
324 $opt =~ s/( |^)-/$1\\-/g;
325 if($standalone) {
326 print ".TH curl 1 \"30 Nov 2016\" \"curl 7.52.0\" \"curl manual\"\n";
327 print ".SH OPTION\n";
328 print "curl $opt\n";
329 }
330 else {
331 print ".IP \"$opt\"\n";
332 }
333 if($protocols) {
334 print protocols($standalone, $protocols);
335 }
336
337 if($standalone) {
338 print ".SH DESCRIPTION\n";
339 }
340
341 if($experimental) {
342 print "**WARNING**: this option is experimental. Do not use in production.\n\n";
343 }
344
345 printdesc(@desc);
346 undef @desc;
347
348 if($multi eq "single") {
349 print "\nIf --$long is provided several times, the last set ".
350 "value will be used.\n";
351 }
352 elsif($multi eq "append") {
353 print "\n--$long can be used several times in a command line\n";
354 }
355 elsif($multi eq "boolean") {
356 my $rev = "no-$long";
357 # for options that start with "no-" the reverse is then without
358 # the no- prefix
359 if($long =~ /^no-/) {
360 $rev = $long;
361 $rev =~ s/^no-//;
362 }
363 print "\nProviding --$long multiple times has no extra effect.\n".
364 "Disable it again with --$rev.\n";
365 }
366 elsif($multi eq "mutex") {
367 print "\nProviding --$long multiple times has no extra effect.\n";
368 }
369
370 my @foot;
371 if($seealso) {
372 my @m=split(/ /, $seealso);
373 my $mstr;
374 my $and = 0;
375 my $num = scalar(@m);
376 if($num > 2) {
377 # use commas up to this point
378 $and = $num - 1;
379 }
380 my $i = 0;
381 for my $k (@m) {
382 if(!$helplong{$k}) {
383 print STDERR "$f:$line:1:WARN: see-also a non-existing option: $k\n";
384 }
385 my $l = manpageify($k);
386 my $sep = " and";
387 if($and && ($i < $and)) {
388 $sep = ",";
389 }
390 $mstr .= sprintf "%s$l", $mstr?"$sep ":"";
391 $i++;
392 }
393 push @foot, seealso($standalone, $mstr);
394 }
395 if($requires) {
396 my $l = manpageify($long);
397 push @foot, "$l requires that the underlying libcurl".
398 " was built to support $requires. ";
399 }
400 if($mutexed) {
401 my @m=split(/ /, $mutexed);
402 my $mstr;
403 for my $k (@m) {
404 if(!$helplong{$k}) {
405 print STDERR "WARN: $f mutexes a non-existing option: $k\n";
406 }
407 my $l = manpageify($k);
408 $mstr .= sprintf "%s$l", $mstr?" and ":"";
409 }
410 push @foot, overrides($standalone,
411 "This option is mutually exclusive to $mstr. ");
412 }
413 if($examples[0]) {
414 my $s ="";
415 $s="s" if($examples[1]);
416 print "\nExample$s:\n.nf\n";
417 foreach my $e (@examples) {
418 $e =~ s!\$URL!https://example.com!g;
419 print " curl $e\n";
420 }
421 print ".fi\n";
422 }
423 if($added) {
424 push @foot, added($standalone, $added);
425 }
426 if($foot[0]) {
427 print "\n";
428 my $f = join("", @foot);
429 $f =~ s/ +\z//; # remove trailing space
430 print "$f\n";
431 }
432 return 0;
433}
434
435sub getshortlong {
436 my ($f)=@_;
437 open(F, "<:crlf", "$f");
438 my $short;
439 my $long;
440 my $help;
441 my $arg;
442 my $protocols;
443 my $category;
444 while(<F>) {
445 if(/^Short: (.)/i) {
446 $short=$1;
447 }
448 elsif(/^Long: (.*)/i) {
449 $long=$1;
450 }
451 elsif(/^Help: (.*)/i) {
452 $help=$1;
453 }
454 elsif(/^Arg: (.*)/i) {
455 $arg=$1;
456 }
457 elsif(/^Protocols: (.*)/i) {
458 $protocols=$1;
459 }
460 elsif(/^Category: (.*)/i) {
461 $category=$1;
462 }
463 elsif(/^---/) {
464 last;
465 }
466 }
467 close(F);
468 if($short) {
469 $optshort{$short}=$long;
470 }
471 if($long) {
472 $optlong{$long}=$short;
473 $helplong{$long}=$help;
474 $arglong{$long}=$arg;
475 $protolong{$long}=$protocols;
476 $catlong{$long}=$category;
477 }
478}
479
480sub indexoptions {
481 my (@files) = @_;
482 foreach my $f (@files) {
483 getshortlong($f);
484 }
485}
486
487sub header {
488 my ($f)=@_;
489 open(F, "<:crlf", "$f");
490 my @d;
491 while(<F>) {
492 s/%DATE/$date/g;
493 s/%VERSION/$version/g;
494 push @d, $_;
495 }
496 close(F);
497 printdesc(@d);
498}
499
500sub listhelp {
501 print <<HEAD
502/***************************************************************************
503 * _ _ ____ _
504 * Project ___| | | | _ \\| |
505 * / __| | | | |_) | |
506 * | (__| |_| | _ <| |___
507 * \\___|\\___/|_| \\_\\_____|
508 *
509 * Copyright (C) 1998 - $year, Daniel Stenberg, <daniel@haxx.se>, et al.
510 *
511 * This software is licensed as described in the file COPYING, which
512 * you should have received as part of this distribution. The terms
513 * are also available at https://curl.se/docs/copyright.html.
514 *
515 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
516 * copies of the Software, and permit persons to whom the Software is
517 * furnished to do so, under the terms of the COPYING file.
518 *
519 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
520 * KIND, either express or implied.
521 *
522 * SPDX-License-Identifier: curl
523 *
524 ***************************************************************************/
525#include "tool_setup.h"
526#include "tool_help.h"
527
528/*
529 * DO NOT edit tool_listhelp.c manually.
530 * This source file is generated with the following command:
531
532 cd \$srcroot/docs/cmdline-opts
533 ./gen.pl listhelp *.d > \$srcroot/src/tool_listhelp.c
534 */
535
536const struct helptxt helptext[] = {
537HEAD
538 ;
539 foreach my $f (sort keys %helplong) {
540 my $long = $f;
541 my $short = $optlong{$long};
542 my @categories = split ' ', $catlong{$long};
543 my $bitmask;
544 my $opt;
545
546 if(defined($short) && $long) {
547 $opt = "-$short, --$long";
548 }
549 elsif($long && !$short) {
550 $opt = " --$long";
551 }
552 for my $i (0 .. $#categories) {
553 $bitmask .= 'CURLHELP_' . uc $categories[$i];
554 # If not last element, append |
555 if($i < $#categories) {
556 $bitmask .= ' | ';
557 }
558 }
559 my $arg = $arglong{$long};
560 if($arg) {
561 $opt .= " $arg";
562 }
563 my $desc = $helplong{$f};
564 $desc =~ s/\"/\\\"/g; # escape double quotes
565
566 my $line = sprintf " {\"%s\",\n \"%s\",\n %s},\n", $opt, $desc, $bitmask;
567
568 if(length($opt) > 78) {
569 print STDERR "WARN: the --$long name is too long\n";
570 }
571 elsif(length($desc) > 78) {
572 print STDERR "WARN: the --$long description is too long\n";
573 }
574 print $line;
575 }
576 print <<FOOT
577 { NULL, NULL, CURLHELP_HIDDEN }
578};
579FOOT
580 ;
581}
582
583sub listcats {
584 my %allcats;
585 foreach my $f (sort keys %helplong) {
586 my @categories = split ' ', $catlong{$f};
587 foreach (@categories) {
588 $allcats{$_} = undef;
589 }
590 }
591 my @categories;
592 foreach my $key (keys %allcats) {
593 push @categories, $key;
594 }
595 @categories = sort @categories;
596 unshift @categories, 'hidden';
597 for my $i (0..$#categories) {
598 print '#define ' . 'CURLHELP_' . uc($categories[$i]) . ' ' . "1u << " . $i . "u\n";
599 }
600}
601
602sub mainpage {
603 my (@files) = @_;
604 my $ret;
605 # show the page header
606 header("page-header");
607
608 # output docs for all options
609 foreach my $f (sort @files) {
610 $ret += single($f, 0);
611 }
612
613 header("page-footer");
614 exit $ret if($ret);
615}
616
617sub showonly {
618 my ($f) = @_;
619 if(single($f, 1)) {
620 print STDERR "$f: failed\n";
621 }
622}
623
624sub showprotocols {
625 my %prots;
626 foreach my $f (keys %optlong) {
627 my @p = split(/ /, $protolong{$f});
628 for my $p (@p) {
629 $prots{$p}++;
630 }
631 }
632 for(sort keys %prots) {
633 printf "$_ (%d options)\n", $prots{$_};
634 }
635}
636
637sub getargs {
638 my ($f, @s) = @_;
639 if($f eq "mainpage") {
640 mainpage(@s);
641 return;
642 }
643 elsif($f eq "listhelp") {
644 listhelp();
645 return;
646 }
647 elsif($f eq "single") {
648 showonly($s[0]);
649 return;
650 }
651 elsif($f eq "protos") {
652 showprotocols();
653 return;
654 }
655 elsif($f eq "listcats") {
656 listcats();
657 return;
658 }
659
660 print "Usage: gen.pl <mainpage/listhelp/single FILE/protos/listcats> [files]\n";
661}
662
663#------------------------------------------------------------------------
664
665my $cmd = shift @ARGV;
666my @files = @ARGV; # the rest are the files
667
668# learn all existing options
669indexoptions(@files);
670
671getargs($cmd, @files);