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