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