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