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: gen.pl <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 can't 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 $date = strftime "%B %d %Y", localtime; 52my $year = strftime "%Y", localtime; 53my $version = "unknown"; 54my $globals; 55 56open(INC, "<../../include/curl/curlver.h"); 57while(<INC>) { 58 if($_ =~ /^#define LIBCURL_VERSION \"([0-9.]*)/) { 59 $version = $1; 60 last; 61 } 62} 63close(INC); 64 65# get the long name version, return the man page string 66sub manpageify { 67 my ($k)=@_; 68 my $l; 69 if($optlong{$k} ne "") { 70 # both short + long 71 $l = "\\fI-".$optlong{$k}.", --$k\\fP"; 72 } 73 else { 74 # only long 75 $l = "\\fI--$k\\fP"; 76 } 77 return $l; 78} 79 80sub printdesc { 81 my @desc = @_; 82 my $exam = 0; 83 for my $d (@desc) { 84 if($d =~ /\(Added in ([0-9.]+)\)/i) { 85 my $ver = $1; 86 if(too_old($ver)) { 87 $d =~ s/ *\(Added in $ver\)//gi; 88 } 89 } 90 if($d !~ /^.\\"/) { 91 # **bold** 92 $d =~ s/\*\*([^ ]*)\*\*/\\fB$1\\fP/g; 93 # *italics* 94 $d =~ s/\*([^ ]*)\*/\\fI$1\\fP/g; 95 } 96 if(!$exam && ($d =~ /^ /)) { 97 # start of example 98 $exam = 1; 99 print ".nf\n"; # no-fill 100 } 101 elsif($exam && ($d !~ /^ /)) { 102 # end of example 103 $exam = 0; 104 print ".fi\n"; # fill-in 105 } 106 # skip lines starting with space (examples) 107 if($d =~ /^[^ ]/ && $d =~ /--/) { 108 # scan for options in longest-names first order 109 for my $k (sort {length($b) <=> length($a)} keys %optlong) { 110 # --tlsv1 is complicated since --tlsv1.2 etc are also 111 # acceptable options! 112 if(($k eq "tlsv1") && ($d =~ /--tlsv1\.[0-9]\\f/)) { 113 next; 114 } 115 my $l = manpageify($k); 116 $d =~ s/\-\-$k([^a-z0-9-])/$l$1/g; 117 } 118 } 119 # quote "bare" minuses in the output 120 $d =~ s/( |\\fI|^)--/$1\\-\\-/g; 121 $d =~ s/([ -]|\\fI|^)-/$1\\-/g; 122 # handle single quotes first on the line 123 $d =~ s/^(\s*)\'/$1\\(aq/; 124 # handle double quotes first on the line 125 $d =~ s/^(\s*)\"/$1\\(dq/; 126 print $d; 127 } 128 if($exam) { 129 print ".fi\n"; # fill-in 130 } 131} 132 133sub seealso { 134 my($standalone, $data)=@_; 135 if($standalone) { 136 return sprintf 137 ".SH \"SEE ALSO\"\n$data\n"; 138 } 139 else { 140 return "See also $data. "; 141 } 142} 143 144sub overrides { 145 my ($standalone, $data)=@_; 146 if($standalone) { 147 return ".SH \"OVERRIDES\"\n$data\n"; 148 } 149 else { 150 return $data; 151 } 152} 153 154sub protocols { 155 my ($standalone, $data)=@_; 156 if($standalone) { 157 return ".SH \"PROTOCOLS\"\n$data\n"; 158 } 159 else { 160 return "($data) "; 161 } 162} 163 164sub too_old { 165 my ($version)=@_; 166 my $a = 999999; 167 if($version =~ /^(\d+)\.(\d+)\.(\d+)/) { 168 $a = $1 * 1000 + $2 * 10 + $3; 169 } 170 elsif($version =~ /^(\d+)\.(\d+)/) { 171 $a = $1 * 1000 + $2 * 10; 172 } 173 if($a < 7300) { 174 # we consider everything before 7.30.0 to be too old to mention 175 # specific changes for 176 return 1; 177 } 178 return 0; 179} 180 181sub added { 182 my ($standalone, $data)=@_; 183 if(too_old($data)) { 184 # don't mention ancient additions 185 return ""; 186 } 187 if($standalone) { 188 return ".SH \"ADDED\"\nAdded in curl version $data\n"; 189 } 190 else { 191 return "Added in $data. "; 192 } 193} 194 195sub single { 196 my ($f, $standalone)=@_; 197 open(F, "<:crlf", "$f") || 198 return 1; 199 my $short; 200 my $long; 201 my $tags; 202 my $added; 203 my $protocols; 204 my $arg; 205 my $mutexed; 206 my $requires; 207 my $category; 208 my $seealso; 209 my $copyright; 210 my $spdx; 211 my @examples; # there can be more than one 212 my $magic; # cmdline special option 213 my $line; 214 my $multi; 215 my $scope; 216 my $experimental; 217 while(<F>) { 218 $line++; 219 if(/^Short: *(.)/i) { 220 $short=$1; 221 } 222 elsif(/^Long: *(.*)/i) { 223 $long=$1; 224 } 225 elsif(/^Added: *(.*)/i) { 226 $added=$1; 227 } 228 elsif(/^Tags: *(.*)/i) { 229 $tags=$1; 230 } 231 elsif(/^Arg: *(.*)/i) { 232 $arg=$1; 233 } 234 elsif(/^Magic: *(.*)/i) { 235 $magic=$1; 236 } 237 elsif(/^Mutexed: *(.*)/i) { 238 $mutexed=$1; 239 } 240 elsif(/^Protocols: *(.*)/i) { 241 $protocols=$1; 242 } 243 elsif(/^See-also: *(.*)/i) { 244 $seealso=$1; 245 } 246 elsif(/^Requires: *(.*)/i) { 247 $requires=$1; 248 } 249 elsif(/^Category: *(.*)/i) { 250 $category=$1; 251 } 252 elsif(/^Example: *(.*)/i) { 253 push @examples, $1; 254 } 255 elsif(/^Multi: *(.*)/i) { 256 $multi=$1; 257 } 258 elsif(/^Scope: *(.*)/i) { 259 $scope=$1; 260 } 261 elsif(/^Experimental: yes/i) { 262 $experimental=1; 263 } 264 elsif(/^C: (.*)/i) { 265 $copyright=$1; 266 } 267 elsif(/^SPDX-License-Identifier: (.*)/i) { 268 $spdx=$1; 269 } 270 elsif(/^Help: *(.*)/i) { 271 ; 272 } 273 elsif(/^---/) { 274 if(!$long) { 275 print STDERR "ERROR: no 'Long:' in $f\n"; 276 return 1; 277 } 278 if(!$category) { 279 print STDERR "ERROR: no 'Category:' in $f\n"; 280 return 2; 281 } 282 if(!$examples[0]) { 283 print STDERR "$f:$line:1:ERROR: no 'Example:' present\n"; 284 return 2; 285 } 286 if(!$added) { 287 print STDERR "$f:$line:1:ERROR: no 'Added:' version present\n"; 288 return 2; 289 } 290 if(!$seealso) { 291 print STDERR "$f:$line:1:ERROR: no 'See-also:' field present\n"; 292 return 2; 293 } 294 if(!$copyright) { 295 print STDERR "$f:$line:1:ERROR: no 'C:' field present\n"; 296 return 2; 297 } 298 if(!$spdx) { 299 print STDERR "$f:$line:1:ERROR: no 'SPDX-License-Identifier:' field present\n"; 300 return 2; 301 } 302 last; 303 } 304 else { 305 chomp; 306 print STDERR "WARN: unrecognized line in $f, ignoring:\n:'$_';" 307 } 308 } 309 my @desc; 310 while(<F>) { 311 push @desc, $_; 312 } 313 close(F); 314 my $opt; 315 if(defined($short) && $long) { 316 $opt = "-$short, --$long"; 317 } 318 elsif($short && !$long) { 319 $opt = "-$short"; 320 } 321 elsif($long && !$short) { 322 $opt = "--$long"; 323 } 324 325 if($arg) { 326 $opt .= " $arg"; 327 } 328 329 # quote "bare" minuses in opt 330 $opt =~ s/( |^)--/$1\\-\\-/g; 331 $opt =~ s/( |^)-/$1\\-/g; 332 if($standalone) { 333 print ".TH curl 1 \"30 Nov 2016\" \"curl 7.52.0\" \"curl manual\"\n"; 334 print ".SH OPTION\n"; 335 print "curl $opt\n"; 336 } 337 else { 338 print ".IP \"$opt\"\n"; 339 } 340 if($protocols) { 341 print protocols($standalone, $protocols); 342 } 343 344 if($standalone) { 345 print ".SH DESCRIPTION\n"; 346 } 347 348 if($experimental) { 349 print "**WARNING**: this option is experimental. Do not use in production.\n\n"; 350 } 351 352 printdesc(@desc); 353 undef @desc; 354 355 if($scope) { 356 if($scope eq "global") { 357 print "\nThis option is global and does not need to be specified for each use of --next.\n"; 358 } 359 else { 360 print STDERR "$f:$line:1:ERROR: unrecognized scope: '$scope'\n"; 361 return 2; 362 } 363 } 364 365 my @extra; 366 if($multi eq "single") { 367 push @extra, "\nIf --$long is provided several times, the last set ". 368 "value will be used.\n"; 369 } 370 elsif($multi eq "append") { 371 push @extra, "\n--$long can be used several times in a command line\n"; 372 } 373 elsif($multi eq "boolean") { 374 my $rev = "no-$long"; 375 # for options that start with "no-" the reverse is then without 376 # the no- prefix 377 if($long =~ /^no-/) { 378 $rev = $long; 379 $rev =~ s/^no-//; 380 } 381 push @extra, 382 "\nProviding --$long multiple times has no extra effect.\n". 383 "Disable it again with --$rev.\n"; 384 } 385 elsif($multi eq "mutex") { 386 push @extra, 387 "\nProviding --$long multiple times has no extra effect.\n"; 388 } 389 elsif($multi eq "custom") { 390 ; # left for the text to describe 391 } 392 else { 393 print STDERR "$f:$line:1:ERROR: unrecognized Multi: '$multi'\n"; 394 return 2; 395 } 396 397 printdesc(@extra); 398 399 my @foot; 400 if($seealso) { 401 my @m=split(/ /, $seealso); 402 my $mstr; 403 my $and = 0; 404 my $num = scalar(@m); 405 if($num > 2) { 406 # use commas up to this point 407 $and = $num - 1; 408 } 409 my $i = 0; 410 for my $k (@m) { 411 if(!$helplong{$k}) { 412 print STDERR "$f:$line:1:WARN: see-also a non-existing option: $k\n"; 413 } 414 my $l = manpageify($k); 415 my $sep = " and"; 416 if($and && ($i < $and)) { 417 $sep = ","; 418 } 419 $mstr .= sprintf "%s$l", $mstr?"$sep ":""; 420 $i++; 421 } 422 push @foot, seealso($standalone, $mstr); 423 } 424 425 if($requires) { 426 my $l = manpageify($long); 427 push @foot, "$l requires that the underlying libcurl". 428 " was built to support $requires. "; 429 } 430 if($mutexed) { 431 my @m=split(/ /, $mutexed); 432 my $mstr; 433 for my $k (@m) { 434 if(!$helplong{$k}) { 435 print STDERR "WARN: $f mutexes a non-existing option: $k\n"; 436 } 437 my $l = manpageify($k); 438 $mstr .= sprintf "%s$l", $mstr?" and ":""; 439 } 440 push @foot, overrides($standalone, 441 "This option is mutually exclusive to $mstr. "); 442 } 443 if($examples[0]) { 444 my $s =""; 445 $s="s" if($examples[1]); 446 print "\nExample$s:\n.nf\n"; 447 foreach my $e (@examples) { 448 $e =~ s!\$URL!https://example.com!g; 449 print " curl $e\n"; 450 } 451 print ".fi\n"; 452 } 453 if($added) { 454 push @foot, added($standalone, $added); 455 } 456 if($foot[0]) { 457 print "\n"; 458 my $f = join("", @foot); 459 $f =~ s/ +\z//; # remove trailing space 460 print "$f\n"; 461 } 462 return 0; 463} 464 465sub getshortlong { 466 my ($f)=@_; 467 open(F, "<:crlf", "$f"); 468 my $short; 469 my $long; 470 my $help; 471 my $arg; 472 my $protocols; 473 my $category; 474 while(<F>) { 475 if(/^Short: (.)/i) { 476 $short=$1; 477 } 478 elsif(/^Long: (.*)/i) { 479 $long=$1; 480 } 481 elsif(/^Help: (.*)/i) { 482 $help=$1; 483 } 484 elsif(/^Arg: (.*)/i) { 485 $arg=$1; 486 } 487 elsif(/^Protocols: (.*)/i) { 488 $protocols=$1; 489 } 490 elsif(/^Category: (.*)/i) { 491 $category=$1; 492 } 493 elsif(/^---/) { 494 last; 495 } 496 } 497 close(F); 498 if($short) { 499 $optshort{$short}=$long; 500 } 501 if($long) { 502 $optlong{$long}=$short; 503 $helplong{$long}=$help; 504 $arglong{$long}=$arg; 505 $protolong{$long}=$protocols; 506 $catlong{$long}=$category; 507 } 508} 509 510sub indexoptions { 511 my (@files) = @_; 512 foreach my $f (@files) { 513 getshortlong($f); 514 } 515} 516 517sub header { 518 my ($f)=@_; 519 open(F, "<:crlf", "$f"); 520 my @d; 521 while(<F>) { 522 s/%DATE/$date/g; 523 s/%VERSION/$version/g; 524 s/%GLOBALS/$globals/g; 525 push @d, $_; 526 } 527 close(F); 528 printdesc(@d); 529} 530 531sub listhelp { 532 print <<HEAD 533/*************************************************************************** 534 * _ _ ____ _ 535 * Project ___| | | | _ \\| | 536 * / __| | | | |_) | | 537 * | (__| |_| | _ <| |___ 538 * \\___|\\___/|_| \\_\\_____| 539 * 540 * Copyright (C) Daniel Stenberg, <daniel\@haxx.se>, et al. 541 * 542 * This software is licensed as described in the file COPYING, which 543 * you should have received as part of this distribution. The terms 544 * are also available at https://curl.se/docs/copyright.html. 545 * 546 * You may opt to use, copy, modify, merge, publish, distribute and/or sell 547 * copies of the Software, and permit persons to whom the Software is 548 * furnished to do so, under the terms of the COPYING file. 549 * 550 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 551 * KIND, either express or implied. 552 * 553 * SPDX-License-Identifier: curl 554 * 555 ***************************************************************************/ 556#include "tool_setup.h" 557#include "tool_help.h" 558 559/* 560 * DO NOT edit tool_listhelp.c manually. 561 * This source file is generated with the following command: 562 563 cd \$srcroot/docs/cmdline-opts 564 ./gen.pl listhelp *.d > \$srcroot/src/tool_listhelp.c 565 */ 566 567const struct helptxt helptext[] = { 568HEAD 569 ; 570 foreach my $f (sort keys %helplong) { 571 my $long = $f; 572 my $short = $optlong{$long}; 573 my @categories = split ' ', $catlong{$long}; 574 my $bitmask = ' '; 575 my $opt; 576 577 if(defined($short) && $long) { 578 $opt = "-$short, --$long"; 579 } 580 elsif($long && !$short) { 581 $opt = " --$long"; 582 } 583 for my $i (0 .. $#categories) { 584 $bitmask .= 'CURLHELP_' . uc $categories[$i]; 585 # If not last element, append | 586 if($i < $#categories) { 587 $bitmask .= ' | '; 588 } 589 } 590 $bitmask =~ s/(?=.{76}).{1,76}\|/$&\n /g; 591 my $arg = $arglong{$long}; 592 if($arg) { 593 $opt .= " $arg"; 594 } 595 my $desc = $helplong{$f}; 596 $desc =~ s/\"/\\\"/g; # escape double quotes 597 598 my $line = sprintf " {\"%s\",\n \"%s\",\n %s},\n", $opt, $desc, $bitmask; 599 600 if(length($opt) > 78) { 601 print STDERR "WARN: the --$long name is too long\n"; 602 } 603 elsif(length($desc) > 78) { 604 print STDERR "WARN: the --$long description is too long\n"; 605 } 606 print $line; 607 } 608 print <<FOOT 609 { NULL, NULL, CURLHELP_HIDDEN } 610}; 611FOOT 612 ; 613} 614 615sub listcats { 616 my %allcats; 617 foreach my $f (sort keys %helplong) { 618 my @categories = split ' ', $catlong{$f}; 619 foreach (@categories) { 620 $allcats{$_} = undef; 621 } 622 } 623 my @categories; 624 foreach my $key (keys %allcats) { 625 push @categories, $key; 626 } 627 @categories = sort @categories; 628 unshift @categories, 'hidden'; 629 for my $i (0..$#categories) { 630 print '#define ' . 'CURLHELP_' . uc($categories[$i]) . ' ' . "1u << " . $i . "u\n"; 631 } 632} 633 634sub listglobals { 635 my (@files) = @_; 636 my @globalopts; 637 638 # Find all global options and output them 639 foreach my $f (sort @files) { 640 open(F, "<:crlf", "$f") || 641 next; 642 my $long; 643 while(<F>) { 644 if(/^Long: *(.*)/i) { 645 $long=$1; 646 } 647 elsif(/^Scope: global/i) { 648 push @globalopts, $long; 649 last; 650 } 651 elsif(/^---/) { 652 last; 653 } 654 } 655 close(F); 656 } 657 return $ret if($ret); 658 for my $e (0 .. $#globalopts) { 659 $globals .= sprintf "%s--%s", $e?($globalopts[$e+1] ? ", " : " and "):"", 660 $globalopts[$e],; 661 } 662} 663 664sub mainpage { 665 my (@files) = @_; 666 my $ret; 667 # show the page header 668 header("page-header"); 669 670 # output docs for all options 671 foreach my $f (sort @files) { 672 $ret += single($f, 0); 673 } 674 675 if(!$ret) { 676 header("page-footer"); 677 } 678 exit $ret if($ret); 679} 680 681sub showonly { 682 my ($f) = @_; 683 if(single($f, 1)) { 684 print STDERR "$f: failed\n"; 685 } 686} 687 688sub showprotocols { 689 my %prots; 690 foreach my $f (keys %optlong) { 691 my @p = split(/ /, $protolong{$f}); 692 for my $p (@p) { 693 $prots{$p}++; 694 } 695 } 696 for(sort keys %prots) { 697 printf "$_ (%d options)\n", $prots{$_}; 698 } 699} 700 701sub getargs { 702 my ($f, @s) = @_; 703 if($f eq "mainpage") { 704 listglobals(@s); 705 mainpage(@s); 706 return; 707 } 708 elsif($f eq "listhelp") { 709 listhelp(); 710 return; 711 } 712 elsif($f eq "single") { 713 showonly($s[0]); 714 return; 715 } 716 elsif($f eq "protos") { 717 showprotocols(); 718 return; 719 } 720 elsif($f eq "listcats") { 721 listcats(); 722 return; 723 } 724 725 print "Usage: gen.pl <mainpage/listhelp/single FILE/protos/listcats> [files]\n"; 726} 727 728#------------------------------------------------------------------------ 729 730my $cmd = shift @ARGV; 731my @files = @ARGV; # the rest are the files 732 733# learn all existing options 734indexoptions(@files); 735 736getargs($cmd, @files); 737