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