#!/usr/bin/env perl #*************************************************************************** # _ _ ____ _ # Project ___| | | | _ \| | # / __| | | | |_) | | # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # # Copyright (C) Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at https://curl.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is # furnished to do so, under the terms of the COPYING file. # # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # # SPDX-License-Identifier: curl # ########################################################################### =begin comment This script generates the manpage. Example: managen [files] > curl.1 Dev notes: We open *input* files in :crlf translation (a no-op on many platforms) in case we have CRLF line endings in Windows but a perl that defaults to LF. Unfortunately it seems some perls like msysgit cannot handle a global input-only :crlf so it has to be specified on each file open for text input. =end comment =cut use strict; use warnings; my %optshort; my %optlong; my %helplong; my %arglong; my %redirlong; my %protolong; my %catlong; use POSIX qw(strftime); my @ts; if(defined($ENV{SOURCE_DATE_EPOCH})) { @ts = gmtime($ENV{SOURCE_DATE_EPOCH}); } else { @ts = localtime; } my $date = strftime "%Y-%m-%d", @ts; my $year = strftime "%Y", @ts; my $version = "unknown"; my $globals; my $error = 0; my $indent = 4; # get the long name version, return the manpage string sub manpageify { my ($k, $manpage)=@_; my $trail = ''; # the matching pattern might include a trailing dot that cannot be part of # the option name if($k =~ s/\.$//) { # cut off trailing dot $trail = "."; } if($manpage) { my $klong = $k; # quote "bare" minuses in the long name $klong =~ s/-/\\-/g; # only long return "\\fI\\-\\-$klong\\fP$trail"; } return "--$k$trail"; } my $colwidth=79; # max number of columns sub prefixline { my ($num) = @_; print "\t" x ($num/8); print ' ' x ($num%8); } sub justline { my ($lvl, @line) = @_; my $w = -1; my $spaces = -1; my $width = $colwidth - ($lvl * $indent); for(@line) { $w += length($_); $w++; $spaces++; } my $inject = $width - $w; my $ratio = 0; # stay at zero if no spaces at all if($spaces) { $ratio = $inject / $spaces; } my $spare = 0; prefixline($lvl * $indent); my $prev; for(@line) { while($spare >= 0.90) { print " "; $spare--; } printf "%s%s", $prev?" ":"", $_; $prev = 1; $spare += $ratio; } print "\n"; } sub lastline { my ($lvl, @line) = @_; my $l = 0; $line[0] =~ s/^( +)//; $l = length($1) if($1); prefixline($lvl * $indent + $l); my $prev = 0; for(@line) { printf "%s%s", $prev?" ":"", $_; $prev = 1; } print "\n"; } sub outputpara { my ($lvl, $f) = @_; $f =~ s/\n/ /g; my $w = 0; my @words = split(/ */, $f); my $width = $colwidth - ($lvl * $indent); my @line; for my $e (@words) { my $l = length($e); my $spaces = scalar(@line); if(($w + $l + $spaces) >= $width) { justline($lvl, @line); undef @line; $w = 0; } push @line, $e; $w += $l; # new width } if($w) { lastline($lvl, @line); print "\n"; } } sub printdesc { my ($manpage, $baselvl, @desc) = @_; if($manpage) { for my $d (@desc) { print $d; } } else { my $p = -1; my $pnum; my $para = ''; for my $l (@desc) { my $lvl = 0; my $lvlnum; if($l !~ /^[\n\r]+/) { # get the indent level off the string $l =~ s/^\[([0-9q]*)\]//; $lvl = $1; } if(($p =~ /q/) && ($lvl !~ /q/)) { # the previous was quoted, this is not print "\n"; } if($lvl ne $p) { $pnum = $p; $pnum =~ s/q$//; outputpara($baselvl + $pnum, $para); $para = ""; } if($lvl =~ /q/) { # quoted, do not right-justify chomp $l; $lvlnum = $lvl; $lvlnum =~ s/q$//; lastline($baselvl + $lvlnum + 1, $l); my $w = ($baselvl + $lvlnum + 1) * $indent + length($l); if($w > $colwidth) { print STDERR "ERROR: $w columns is too long\n"; print STDERR "$l\n"; $error++; } } else { $para .= $l; } $p = $lvl; } $pnum = $p; $pnum =~ s/q$//; outputpara($baselvl + $pnum, $para); } } sub seealso { my($standalone, $data)=@_; if($standalone) { return sprintf ".SH \"SEE ALSO\"\n$data\n"; } else { return "See also $data. "; } } sub overrides { my ($standalone, $data)=@_; if($standalone) { return ".SH \"OVERRIDES\"\n$data\n"; } else { return $data; } } my %protexists = ( 'DNS' => 1, 'FILE' => 1, 'FTP' => 1, 'FTPS' => 1, 'GSS/kerberos' => 1, 'HTTP' => 1, 'HTTPS' => 1, 'IMAP' => 1, 'IPFS' => 1, 'LDAP' => 1, 'MQTT' => 1, 'POP3' => 1, 'SCP' => 1, 'SFTP' => 1, 'SMTP' => 1, 'SSL' => 2, # deprecated 'TELNET' => 1, 'TFTP' => 1, 'TLS' => 1, ); sub protocols { my ($f, $line, $manpage, $standalone, $data)=@_; my @e = split(/ +/, $data); for my $pr (@e) { if(!$protexists{$pr}) { print STDERR "$f:$line:1:ERROR: unrecognized protocol: $pr\n"; exit 2; } } if($standalone) { return ".SH \"PROTOCOLS\"\n$data\n"; } else { return "($data) " if($manpage); return "[1]($data) " if(!$manpage); } } sub too_old { my ($version)=@_; my $a = 999999; if($version =~ /^(\d+)\.(\d+)\.(\d+)/) { $a = $1 * 1000 + $2 * 10 + $3; } elsif($version =~ /^(\d+)\.(\d+)/) { $a = $1 * 1000 + $2 * 10; } if($a < 7660) { # we consider everything before 7.66.0 to be too old to mention # specific changes for return 1; } return 0; } sub added { my ($standalone, $data)=@_; if(too_old($data)) { # do not mention ancient additions return ""; } if($standalone) { return ".SH \"ADDED\"\nAdded in curl version $data\n"; } else { return "Added in $data. "; } } sub render { my ($manpage, $fh, $f, $line) = @_; my @desc; my $tablemode = 0; my $header = 0; # if $top is TRUE, it means a top-level page and not a command line option my $top = ($line == 1); my $quote = 0; my $level = 0; my $finalblank; my $blankline = 0; my $start = 0; while(<$fh>) { my $d = $_; $line++; $finalblank = ($d eq "\n"); if($d =~ /^\.(SH|BR|IP|B)/) { print STDERR "$f:$line:1:ERROR: nroff instruction in input: \".$1\"\n"; return 4; } if(/^ *