xref: /curl/scripts/managen (revision bcc2e90e)
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: managen <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 cannot 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 @ts;
52if (defined($ENV{SOURCE_DATE_EPOCH})) {
53    @ts = gmtime($ENV{SOURCE_DATE_EPOCH});
54} else {
55    @ts = localtime;
56}
57my $date = strftime "%Y-%m-%d", @ts;
58my $year = strftime "%Y", @ts;
59my $version = "unknown";
60my $globals;
61
62# get the long name version, return the man page string
63sub manpageify {
64    my ($k)=@_;
65    my $l;
66    my $trail;
67    # the matching pattern might include a trailing dot that cannot be part of
68    # the option name
69    if($k =~ s/\.$//) {
70        # cut off trailing dot
71        $trail = ".";
72    }
73    my $klong = $k;
74    # quote "bare" minuses in the long name
75    $klong =~ s/-/\\-/g;
76    if($optlong{$k}) {
77        # both short + long
78        $l = "\\fI-".$optlong{$k}.", \\-\\-$klong\\fP$trail";
79    }
80    else {
81        # only long
82        $l = "\\fI\\-\\-$klong\\fP$trail";
83    }
84    return $l;
85}
86
87
88my $colwidth=78; # max number of columns
89
90sub justline {
91    my ($lvl, @line) = @_;
92    my $w = -1;
93    my $spaces = -1;
94    my $width = $colwidth - ($lvl * 4);
95    for(@line) {
96        $w += length($_);
97        $w++;
98        $spaces++;
99    }
100    my $inject = $width - $w;
101    my $ratio = 0; # stay at zero if no spaces at all
102    if($spaces) {
103        $ratio = $inject / $spaces;
104    }
105    my $spare = 0;
106    print ' ' x ($lvl * 4);
107    my $prev;
108    for(@line) {
109        while($spare >= 0.90) {
110            print " ";
111            $spare--;
112        }
113        printf "%s%s", $prev?" ":"", $_;
114        $prev = 1;
115        $spare += $ratio;
116    }
117    print "\n";
118}
119
120sub lastline {
121    my ($lvl, @line) = @_;
122    print ' ' x ($lvl * 4);
123    my $prev = 0;
124    for(@line) {
125        printf "%s%s", $prev?" ":"", $_;
126        $prev = 1;
127    }
128    print "\n";
129}
130
131sub outputpara {
132    my ($lvl, $f) = @_;
133    $f =~ s/\n/ /g;
134
135    my $w = 0;
136    my @words = split(/  */, $f);
137    my $width = $colwidth - ($lvl * 4);
138
139    my @line;
140    for my $e (@words) {
141        my $l = length($e);
142        my $spaces = scalar(@line);
143        if(($w + $l + $spaces) >= $width) {
144            justline($lvl, @line);
145            undef @line;
146            $w = 0;
147        }
148
149        push @line, $e;
150        $w += $l; # new width
151    }
152    if($w) {
153        lastline($lvl, @line);
154        print "\n";
155    }
156}
157
158sub printdesc {
159    my ($manpage, $baselvl, @desc) = @_;
160
161    if($manpage) {
162        for my $d (@desc) {
163            print $d;
164        }
165    }
166    else {
167        my $p = -1;
168        my $para;
169        for my $l (@desc) {
170            my $lvl;
171            if($l !~ /^[\n\r]+/) {
172                # get the indent level off the string
173                $l =~ s/^\[([0-9q]*)\]//;
174                $lvl = $1;
175            }
176            if(($p =~ /q/) && ($lvl !~ /q/)) {
177                # the previous was quoted, this is not
178                print "\n";
179            }
180            if($lvl != $p) {
181                outputpara($baselvl + $p, $para);
182                $para = "";
183            }
184            if($lvl =~ /q/) {
185                # quoted, do not right-justify
186                chomp $l;
187                lastline($baselvl + $lvl + 1, $l);
188            }
189            else {
190                $para .= $l;
191            }
192
193            $p = $lvl;
194        }
195        outputpara($baselvl + $p, $para);
196    }
197}
198
199sub seealso {
200    my($standalone, $data)=@_;
201    if($standalone) {
202        return sprintf
203            ".SH \"SEE ALSO\"\n$data\n";
204    }
205    else {
206        return "See also $data. ";
207    }
208}
209
210sub overrides {
211    my ($standalone, $data)=@_;
212    if($standalone) {
213        return ".SH \"OVERRIDES\"\n$data\n";
214    }
215    else {
216        return $data;
217    }
218}
219
220sub protocols {
221    my ($manpage, $standalone, $data)=@_;
222    if($standalone) {
223        return ".SH \"PROTOCOLS\"\n$data\n";
224    }
225    else {
226        return "($data) " if($manpage);
227        return "[1]($data) " if(!$manpage);
228    }
229}
230
231sub too_old {
232    my ($version)=@_;
233    my $a = 999999;
234    if($version =~ /^(\d+)\.(\d+)\.(\d+)/) {
235        $a = $1 * 1000 + $2 * 10 + $3;
236    }
237    elsif($version =~ /^(\d+)\.(\d+)/) {
238        $a = $1 * 1000 + $2 * 10;
239    }
240    if($a < 7500) {
241        # we consider everything before 7.50.0 to be too old to mention
242        # specific changes for
243        return 1;
244    }
245    return 0;
246}
247
248sub added {
249    my ($standalone, $data)=@_;
250    if(too_old($data)) {
251        # do not mention ancient additions
252        return "";
253    }
254    if($standalone) {
255        return ".SH \"ADDED\"\nAdded in curl version $data\n";
256    }
257    else {
258        return "Added in $data. ";
259    }
260}
261
262sub render {
263    my ($manpage, $fh, $f, $line) = @_;
264    my @desc;
265    my $tablemode = 0;
266    my $header = 0;
267    # if $top is TRUE, it means a top-level page and not a command line option
268    my $top = ($line == 1);
269    my $quote;
270    my $level;
271    $start = 0;
272
273    while(<$fh>) {
274        my $d = $_;
275        $line++;
276        if($d =~ /^\.(SH|BR|IP|B)/) {
277            print STDERR "$f:$line:1:ERROR: nroff instruction in input: \".$1\"\n";
278            return 4;
279        }
280        if(/^ *<!--/) {
281            # skip comments
282            next;
283        }
284        if((!$start) && ($_ =~ /^[\r\n]*\z/)) {
285            # skip leading blank lines
286            next;
287        }
288        $start = 1;
289        if(/^# (.*)/) {
290            $header = 1;
291            if($top != 1) {
292                # ignored for command line options
293                $blankline++;
294                next;
295            }
296            push @desc, ".SH $1\n" if($manpage);
297            push @desc, "[0]$1\n" if(!$manpage);
298            next;
299        }
300        elsif(/^###/) {
301            print STDERR "$f:$line:1:ERROR: ### header is not supported\n";
302            exit 3;
303        }
304        elsif(/^## (.*)/) {
305            my $word = $1;
306            # if there are enclosing quotes, remove them first
307            $word =~ s/[\"\'](.*)[\"\']\z/$1/;
308
309            # remove backticks from headers
310            $words =~ s/\`//g;
311
312            # if there is a space, it needs quotes for man page
313            if(($word =~ / /) && $manpage) {
314                $word = "\"$word\"";
315            }
316            $level = 1;
317            if($top == 1) {
318                push @desc, ".IP $word\n" if($manpage);
319                push @desc, "\n" if(!$manpage);
320                push @desc, "[1]$word\n" if(!$manpage);
321            }
322            else {
323                if(!$tablemode) {
324                    push @desc, ".RS\n" if($manpage);
325                    $tablemode = 1;
326                }
327                push @desc, ".IP $word\n" if($manpage);
328                push @desc, "\n" if(!$manpage);
329                push @desc, "[1]$word\n" if(!$manpage);
330            }
331            $header = 1;
332            next;
333        }
334        elsif(/^##/) {
335            if($top == 1) {
336                print STDERR "$f:$line:1:ERROR: ## empty header top-level mode\n";
337                exit 3;
338            }
339            if($tablemode) {
340                # end of table
341                push @desc, ".RE\n.IP\n";
342                $tablemode = 0;
343            }
344            $header = 1;
345            next;
346        }
347        elsif(/^\.(IP|RS|RE)/) {
348            my ($cmd) = ($1);
349            print STDERR "$f:$line:1:ERROR: $cmd detected, use ##-style\n";
350            return 3;
351        }
352        elsif(/^[ \t]*\n/) {
353            # count and ignore blank lines
354            $blankline++;
355            next;
356        }
357        elsif($d =~ /^    (.*)/) {
358            my $word = $1;
359            if(!$quote && $manpage) {
360                push @desc, ".nf\n";
361            }
362            $quote = 1;
363            $d = "$word\n";
364        }
365        elsif($quote && ($d !~ /^    (.*)/)) {
366            # end of quote
367            push @desc, ".fi\n" if($manpage);
368            $quote = 0;
369        }
370
371        $d =~ s/`%DATE`/$date/g;
372        $d =~ s/`%VERSION`/$version/g;
373        $d =~ s/`%GLOBALS`/$globals/g;
374
375        # convert backticks to double quotes
376        $d =~ s/\`/\"/g;
377
378        if($d =~ /\(Added in ([0-9.]+)\)/i) {
379            my $ver = $1;
380            if(too_old($ver)) {
381                $d =~ s/ *\(Added in $ver\)//gi;
382            }
383        }
384
385        if(!$quote) {
386            if($d =~ /^(.*)  /) {
387                printf STDERR "$f:$line:%d:ERROR: 2 spaces detected\n",
388                    length($1);
389                return 3;
390            }
391            elsif($d =~ /[^\\][\<\>]/) {
392                print STDERR "$f:$line:1:WARN: un-escaped < or > used\n";
393                return 3;
394            }
395        }
396        # convert backslash-'<' or '> to just the second character
397        $d =~ s/\\([><])/$1/g;
398        # convert single backslash to double-backslash
399        $d =~ s/\\/\\\\/g if($manpage);
400
401
402        if($manpage) {
403            if(!$quote && $d =~ /--/) {
404                $d =~ s/--([a-z0-9.-]+)/manpageify($1)/ge;
405            }
406
407            # quote minuses in the output
408            $d =~ s/([^\\])-/$1\\-/g;
409            # replace single quotes
410            $d =~ s/\'/\\(aq/g;
411            # handle double quotes or periods first on the line
412            $d =~ s/^([\.\"])/\\&$1/;
413            # **bold**
414            $d =~ s/\*\*(\S.*?)\*\*/\\fB$1\\fP/g;
415            # *italics*
416            $d =~ s/\*(\S.*?)\*/\\fI$1\\fP/g;
417        }
418        else {
419            # **bold**
420            $d =~ s/\*\*(\S.*?)\*\*/$1/g;
421            # *italics*
422            $d =~ s/\*(\S.*?)\*/$1/g;
423        }
424        # trim trailing spaces
425        $d =~ s/[ \t]+\z//;
426        push @desc, "\n" if($blankline && !$header);
427        $blankline = 0;
428        push @desc, $d if($manpage);
429        my $qstr = $quote ? "q": "";
430        push @desc, "[".(1 + $level)."$qstr]$d" if(!$manpage);
431        $header = 0;
432
433    }
434    if($tablemode) {
435        # end of table
436        push @desc, ".RE\n.IP\n" if($manpage);
437    }
438    return @desc;
439}
440
441sub single {
442    my ($dir, $manpage, $f, $standalone)=@_;
443    my $fh;
444    open($fh, "<:crlf", "$dir/$f") ||
445        die "could not find $dir/$f";
446    my $short;
447    my $long;
448    my $tags;
449    my $added;
450    my $protocols;
451    my $arg;
452    my $mutexed;
453    my $requires;
454    my $category;
455    my @seealso;
456    my $copyright;
457    my $spdx;
458    my @examples; # there can be more than one
459    my $magic; # cmdline special option
460    my $line;
461    my $dline;
462    my $multi;
463    my $scope;
464    my $experimental;
465    my $start;
466    my $list; # identifies the list, 1 example, 2 see-also
467    while(<$fh>) {
468        $line++;
469        if(/^ *<!--/) {
470            next;
471        }
472        if(!$start) {
473            if(/^---/) {
474                $start = 1;
475            }
476            next;
477        }
478        if(/^Short: *(.)/i) {
479            $short=$1;
480        }
481        elsif(/^Long: *(.*)/i) {
482            $long=$1;
483        }
484        elsif(/^Added: *(.*)/i) {
485            $added=$1;
486        }
487        elsif(/^Tags: *(.*)/i) {
488            $tags=$1;
489        }
490        elsif(/^Arg: *(.*)/i) {
491            $arg=$1;
492        }
493        elsif(/^Magic: *(.*)/i) {
494            $magic=$1;
495        }
496        elsif(/^Mutexed: *(.*)/i) {
497            $mutexed=$1;
498        }
499        elsif(/^Protocols: *(.*)/i) {
500            $protocols=$1;
501        }
502        elsif(/^See-also: +(.+)/i) {
503            if($seealso) {
504                print STDERR "ERROR: duplicated See-also in $f\n";
505                return 1;
506            }
507            push @seealso, $1;
508        }
509        elsif(/^See-also:/i) {
510            $list=2;
511        }
512        elsif(/^  *- (.*)/i && ($list == 2)) {
513            push @seealso, $1;
514        }
515        elsif(/^Requires: *(.*)/i) {
516            $requires=$1;
517        }
518        elsif(/^Category: *(.*)/i) {
519            $category=$1;
520        }
521        elsif(/^Example: +(.+)/i) {
522            push @examples, $1;
523        }
524        elsif(/^Example:/i) {
525            # '1' is the example list
526            $list = 1;
527        }
528        elsif(/^  *- (.*)/i && ($list == 1)) {
529            push @examples, $1;
530        }
531        elsif(/^Multi: *(.*)/i) {
532            $multi=$1;
533        }
534        elsif(/^Scope: *(.*)/i) {
535            $scope=$1;
536        }
537        elsif(/^Experimental: yes/i) {
538            $experimental=1;
539        }
540        elsif(/^C: (.*)/i) {
541            $copyright=$1;
542        }
543        elsif(/^SPDX-License-Identifier: (.*)/i) {
544            $spdx=$1;
545        }
546        elsif(/^Help: *(.*)/i) {
547            ;
548        }
549        elsif(/^---/) {
550            $start++;
551            if(!$long) {
552                print STDERR "ERROR: no 'Long:' in $f\n";
553                return 1;
554            }
555            if(!$category) {
556                print STDERR "ERROR: no 'Category:' in $f\n";
557                return 2;
558            }
559            if(!$examples[0]) {
560                print STDERR "$f:$line:1:ERROR: no 'Example:' present\n";
561                return 2;
562            }
563            if(!$added) {
564                print STDERR "$f:$line:1:ERROR: no 'Added:' version present\n";
565                return 2;
566            }
567            if(!$seealso[0]) {
568                print STDERR "$f:$line:1:ERROR: no 'See-also:' field present\n";
569                return 2;
570            }
571            if(!$copyright) {
572                print STDERR "$f:$line:1:ERROR: no 'C:' field present\n";
573                return 2;
574            }
575            if(!$spdx) {
576                print STDERR "$f:$line:1:ERROR: no 'SPDX-License-Identifier:' field present\n";
577                return 2;
578            }
579            last;
580        }
581        else {
582            chomp;
583            print STDERR "$f:$line:1:WARN: unrecognized line in $f, ignoring:\n:'$_';"
584        }
585    }
586
587    if($start < 2) {
588        print STDERR "$f:1:1:ERROR: no proper meta-data header\n";
589        return 2;
590    }
591
592    my @desc = render($manpage, $fh, $f, $line);
593    close($fh);
594    if($tablemode) {
595        # end of table
596        push @desc, ".RE\n.IP\n";
597    }
598    my $opt;
599
600    if(defined($short) && $long) {
601        $opt = "-$short, --$long";
602    }
603    elsif($short && !$long) {
604        $opt = "-$short";
605    }
606    elsif($long && !$short) {
607        $opt = "--$long";
608    }
609
610    if($arg) {
611        $opt .= " $arg";
612    }
613
614    # quote "bare" minuses in opt
615    $opt =~ s/-/\\-/g if($manpage);
616    if($standalone) {
617        print ".TH curl 1 \"30 Nov 2016\" \"curl 7.52.0\" \"curl manual\"\n";
618        print ".SH OPTION\n";
619        print "curl $opt\n";
620    }
621    elsif($manpage) {
622        print ".IP \"$opt\"\n";
623    }
624    else {
625        lastline(1, $opt);
626    }
627    my @leading;
628    if($protocols) {
629        push @leading, protocols($manpage, $standalone, $protocols);
630    }
631
632    if($standalone) {
633        print ".SH DESCRIPTION\n";
634    }
635
636    if($experimental) {
637        push @leading, "**WARNING**: this option is experimental. Do not use in production.\n\n";
638    }
639
640    my $pre = $manpage ? "\n": "[1]";
641
642    if($scope) {
643        if($scope eq "global") {
644            push @desc, "\n" if(!$manpage);
645            push @desc, "${pre}This option is global and does not need to be specified for each use of --next.\n";
646        }
647        else {
648            print STDERR "$f:$line:1:ERROR: unrecognized scope: '$scope'\n";
649            return 2;
650        }
651    }
652
653    printdesc($manpage, 2, (@leading, @desc));
654    undef @desc;
655
656    my @extra;
657    if($multi eq "single") {
658        push @extra, "${pre}If --$long is provided several times, the last set ".
659            "value is used.\n";
660    }
661    elsif($multi eq "append") {
662        push @extra, "${pre}--$long can be used several times in a command line\n";
663    }
664    elsif($multi eq "boolean") {
665        my $rev = "no-$long";
666        # for options that start with "no-" the reverse is then without
667        # the no- prefix
668        if($long =~ /^no-/) {
669            $rev = $long;
670            $rev =~ s/^no-//;
671        }
672        my $dashes = $manpage ? "\\-\\-" : "--";
673        push @extra,
674            "${pre}Providing --$long multiple times has no extra effect.\n".
675            "Disable it again with $dashes$rev.\n";
676    }
677    elsif($multi eq "mutex") {
678        push @extra,
679            "${pre}Providing --$long multiple times has no extra effect.\n";
680    }
681    elsif($multi eq "custom") {
682        ; # left for the text to describe
683    }
684    else {
685        print STDERR "$f:$line:1:ERROR: unrecognized Multi: '$multi'\n";
686        return 2;
687    }
688
689    printdesc($manpage, 2, @extra);
690
691    my @foot;
692
693    my $mstr;
694    my $and = 0;
695    my $num = scalar(@seealso);
696    if($num > 2) {
697        # use commas up to this point
698        $and = $num - 1;
699    }
700    my $i = 0;
701    for my $k (@seealso) {
702        if(!$helplong{$k}) {
703            print STDERR "$f:$line:1:WARN: see-also a non-existing option: $k\n";
704        }
705        my $l = $manpage ? manpageify($k) : "--$k";
706        my $sep = " and";
707        if($and && ($i < $and)) {
708            $sep = ",";
709        }
710        $mstr .= sprintf "%s$l", $mstr?"$sep ":"";
711        $i++;
712    }
713    push @foot, seealso($standalone, $mstr);
714
715    if($requires) {
716        my $l = $manpage ? manpageify($long) : "--$long";
717        push @foot, "$l requires that the underlying libcurl".
718            " was built to support $requires. ";
719    }
720    if($mutexed) {
721        my @m=split(/ /, $mutexed);
722        my $mstr;
723        for my $k (@m) {
724            if(!$helplong{$k}) {
725                print STDERR "WARN: $f mutexes a non-existing option: $k\n";
726            }
727            my $l = $manpage ? manpageify($k) : "--$k";
728            $mstr .= sprintf "%s$l", $mstr?" and ":"";
729        }
730        push @foot, overrides($standalone,
731                              "This option is mutually exclusive to $mstr. ");
732    }
733    if($examples[0]) {
734        my $s ="";
735        $s="s" if($examples[1]);
736        if($manpage) {
737            print "\nExample$s:\n";
738            print ".nf\n";
739            foreach my $e (@examples) {
740                $e =~ s!\$URL!https://example.com!g;
741                # convert single backslahes to doubles
742                $e =~ s/\\/\\\\/g;
743                print " curl $e\n";
744            }
745            print ".fi\n";
746        }
747        else {
748            my @ex;
749            push @ex, "[0q]Example$s:\n";
750            foreach my $e (@examples) {
751                $e =~ s!\$URL!https://example.com!g;
752                push @ex, "[0q] curl $e\n";
753            }
754            printdesc($manpage, 2, @ex);
755        }
756    }
757    if($added) {
758        push @foot, added($standalone, $added);
759    }
760    if($foot[0]) {
761        print "\n";
762        my $f = join("", @foot);
763        if($manpage) {
764            $f =~ s/ +\z//; # remove trailing space
765            print "$f\n";
766        }
767        else {
768            printdesc($manpage, 2, "[1]$f");
769        }
770    }
771    return 0;
772}
773
774sub getshortlong {
775    my ($dir, $f)=@_;
776    $f =~ s/^.*\///;
777    open(F, "<:crlf", "$dir/$f") ||
778        die "could not find $dir/$f";
779    my $short;
780    my $long;
781    my $help;
782    my $arg;
783    my $protocols;
784    my $category;
785    my $start = 0;
786    while(<F>) {
787        if(!$start) {
788            if(/^---/) {
789                $start = 1;
790            }
791            next;
792        }
793        if(/^Short: (.)/i) {
794            $short=$1;
795        }
796        elsif(/^Long: (.*)/i) {
797            $long=$1;
798        }
799        elsif(/^Help: (.*)/i) {
800            $help=$1;
801        }
802        elsif(/^Arg: (.*)/i) {
803            $arg=$1;
804        }
805        elsif(/^Protocols: (.*)/i) {
806            $protocols=$1;
807        }
808        elsif(/^Category: (.*)/i) {
809            $category=$1;
810        }
811        elsif(/^---/) {
812            last;
813        }
814    }
815    close(F);
816    if($short) {
817        $optshort{$short}=$long;
818    }
819    if($long) {
820        $optlong{$long}=$short;
821        $helplong{$long}=$help;
822        $arglong{$long}=$arg;
823        $protolong{$long}=$protocols;
824        $catlong{$long}=$category;
825    }
826}
827
828sub indexoptions {
829    my ($dir, @files) = @_;
830    foreach my $f (@files) {
831        getshortlong($dir, $f);
832    }
833}
834
835sub header {
836    my ($dir, $manpage, $f)=@_;
837    my $fh;
838    open($fh, "<:crlf", "$dir/$f") ||
839        die "could not find $dir/$f";
840    my @d = render($manpage, $fh, $f, 1);
841    close($fh);
842    printdesc($manpage, 0, @d);
843}
844
845sub listhelp {
846    print <<HEAD
847/***************************************************************************
848 *                                  _   _ ____  _
849 *  Project                     ___| | | |  _ \\| |
850 *                             / __| | | | |_) | |
851 *                            | (__| |_| |  _ <| |___
852 *                             \\___|\\___/|_| \\_\\_____|
853 *
854 * Copyright (C) Daniel Stenberg, <daniel\@haxx.se>, et al.
855 *
856 * This software is licensed as described in the file COPYING, which
857 * you should have received as part of this distribution. The terms
858 * are also available at https://curl.se/docs/copyright.html.
859 *
860 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
861 * copies of the Software, and permit persons to whom the Software is
862 * furnished to do so, under the terms of the COPYING file.
863 *
864 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
865 * KIND, either express or implied.
866 *
867 * SPDX-License-Identifier: curl
868 *
869 ***************************************************************************/
870#include "tool_setup.h"
871#include "tool_help.h"
872
873/*
874 * DO NOT edit tool_listhelp.c manually.
875 * This source file is generated with the following command in an autotools
876 * build:
877 *
878 * "make listhelp"
879 */
880
881const struct helptxt helptext[] = {
882HEAD
883        ;
884    foreach my $f (sort keys %helplong) {
885        my $long = $f;
886        my $short = $optlong{$long};
887        my @categories = split ' ', $catlong{$long};
888        my $bitmask = ' ';
889        my $opt;
890
891        if(defined($short) && $long) {
892            $opt = "-$short, --$long";
893        }
894        elsif($long && !$short) {
895            $opt = "    --$long";
896        }
897        for my $i (0 .. $#categories) {
898            $bitmask .= 'CURLHELP_' . uc $categories[$i];
899            # If not last element, append |
900            if($i < $#categories) {
901                $bitmask .= ' | ';
902            }
903        }
904        $bitmask =~ s/(?=.{76}).{1,76}\|/$&\n  /g;
905        my $arg = $arglong{$long};
906        if($arg) {
907            $opt .= " $arg";
908        }
909        my $desc = $helplong{$f};
910        $desc =~ s/\"/\\\"/g; # escape double quotes
911
912        my $line = sprintf "  {\"%s\",\n   \"%s\",\n  %s},\n", $opt, $desc, $bitmask;
913
914        if(length($opt) > 78) {
915            print STDERR "WARN: the --$long name is too long\n";
916        }
917        elsif(length($desc) > 78) {
918            print STDERR "WARN: the --$long description is too long\n";
919        }
920        print $line;
921    }
922    print <<FOOT
923  { NULL, NULL, CURLHELP_HIDDEN }
924};
925FOOT
926        ;
927}
928
929sub listcats {
930    my %allcats;
931    foreach my $f (sort keys %helplong) {
932        my @categories = split ' ', $catlong{$f};
933        foreach (@categories) {
934            $allcats{$_} = undef;
935        }
936    }
937    my @categories;
938    foreach my $key (keys %allcats) {
939        push @categories, $key;
940    }
941    @categories = sort @categories;
942    unshift @categories, 'hidden';
943    for my $i (0..$#categories) {
944        print '#define ' . 'CURLHELP_' . uc($categories[$i]) . ' ' . "1u << " . $i . "u\n";
945    }
946}
947
948sub listglobals {
949    my ($dir, @files) = @_;
950    my @globalopts;
951
952    # Find all global options and output them
953    foreach my $f (sort @files) {
954        open(F, "<:crlf", "$dir/$f") ||
955            die "could not read $dir/$f";
956        my $long;
957        my $start = 0;
958        while(<F>) {
959            if(/^---/) {
960                if(!$start) {
961                    $start = 1;
962                    next;
963                }
964                else {
965                    last;
966                }
967            }
968            if(/^Long: *(.*)/i) {
969                $long=$1;
970            }
971            elsif(/^Scope: global/i) {
972                push @globalopts, $long;
973                last;
974            }
975        }
976        close(F);
977    }
978    return $ret if($ret);
979    for my $e (0 .. $#globalopts) {
980        $globals .= sprintf "%s--%s",  $e?($globalopts[$e+1] ? ", " : " and "):"",
981            $globalopts[$e],;
982    }
983}
984
985sub noext {
986    my $in = $_[0];
987    $in =~ s/\.d//;
988    return $in;
989}
990
991sub sortnames {
992    return noext($a) cmp noext($b);
993}
994
995sub mainpage {
996    my ($dir, $manpage, @files) = @_;
997    # $manpage is 1 for nroff, 0 for ASCII
998    my $ret;
999    my $fh;
1000    open($fh, "<:crlf", "$dir/mainpage.idx") ||
1001        die "no $dir/mainpage.idx file";
1002
1003    print <<HEADER
1004.\\" **************************************************************************
1005.\\" *                                  _   _ ____  _
1006.\\" *  Project                     ___| | | |  _ \\| |
1007.\\" *                             / __| | | | |_) | |
1008.\\" *                            | (__| |_| |  _ <| |___
1009.\\" *                             \\___|\\___/|_| \\_\\_____|
1010.\\" *
1011.\\" * Copyright (C) Daniel Stenberg, <daniel\@haxx.se>, et al.
1012.\\" *
1013.\\" * This software is licensed as described in the file COPYING, which
1014.\\" * you should have received as part of this distribution. The terms
1015.\\" * are also available at https://curl.se/docs/copyright.html.
1016.\\" *
1017.\\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1018.\\" * copies of the Software, and permit persons to whom the Software is
1019.\\" * furnished to do so, under the terms of the COPYING file.
1020.\\" *
1021.\\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1022.\\" * KIND, either express or implied.
1023.\\" *
1024.\\" * SPDX-License-Identifier: curl
1025.\\" *
1026.\\" **************************************************************************
1027.\\"
1028.\\" DO NOT EDIT. Generated by the curl project managen man page generator.
1029.\\"
1030.TH curl 1 "$date" "curl $version" "curl Manual"
1031HEADER
1032        if ($manpage);
1033
1034    while(<$fh>) {
1035        my $f = $_;
1036        chomp $f;
1037        if($f =~ /^#/) {
1038            # standard comment
1039            next;
1040        }
1041        if(/^%options/) {
1042            # output docs for all options
1043            foreach my $f (sort sortnames @files) {
1044                $ret += single($dir, $manpage, $f, 0);
1045            }
1046        }
1047        else {
1048            # render the file
1049            header($dir, $manpage, $f);
1050        }
1051    }
1052    close($fh);
1053    exit $ret if($ret);
1054}
1055
1056sub showonly {
1057    my ($f) = @_;
1058    if(single($f, 1)) {
1059        print STDERR "$f: failed\n";
1060    }
1061}
1062
1063sub showprotocols {
1064    my %prots;
1065    foreach my $f (keys %optlong) {
1066        my @p = split(/ /, $protolong{$f});
1067        for my $p (@p) {
1068            $prots{$p}++;
1069        }
1070    }
1071    for(sort keys %prots) {
1072        printf "$_ (%d options)\n", $prots{$_};
1073    }
1074}
1075
1076sub getargs {
1077    my ($dir, $f, @s) = @_;
1078    if($f eq "mainpage") {
1079        listglobals($dir, @s);
1080        mainpage($dir, 1, @s);
1081        return;
1082    }
1083    elsif($f eq "ascii") {
1084        listglobals($dir, @s);
1085        mainpage($dir, 0, @s);
1086        return;
1087    }
1088    elsif($f eq "listhelp") {
1089        listhelp();
1090        return;
1091    }
1092    elsif($f eq "single") {
1093        showonly($s[0]);
1094        return;
1095    }
1096    elsif($f eq "protos") {
1097        showprotocols();
1098        return;
1099    }
1100    elsif($f eq "listcats") {
1101        listcats();
1102        return;
1103    }
1104
1105    print "Usage: managen ".
1106        "[-d dir] <mainpage/ascii/listhelp/single FILE/protos/listcats> [files]\n";
1107}
1108
1109#------------------------------------------------------------------------
1110
1111my $dir = ".";
1112my $include = "../../include";
1113my $cmd = shift @ARGV;
1114
1115 check:
1116if($cmd eq "-d") {
1117    # specifies source directory
1118    $dir = shift @ARGV;
1119    $cmd = shift @ARGV;
1120    goto check;
1121}
1122elsif($cmd eq "-I") {
1123    # include path root
1124    $include = shift @ARGV;
1125    $cmd = shift @ARGV;
1126    goto check;
1127}
1128
1129my @files = @ARGV; # the rest are the files
1130
1131open(INC, "<$include/curl/curlver.h");
1132while(<INC>) {
1133    if($_ =~ /^#define LIBCURL_VERSION \"([0-9.]*)/) {
1134        $version = $1;
1135        last;
1136    }
1137}
1138close(INC);
1139
1140# learn all existing options
1141indexoptions($dir, @files);
1142
1143getargs($dir, $cmd, @files);
1144