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