#!/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 # ########################################################################### # # Scan man page(s) and detect some simple and yet common formatting mistakes. # # Output all deviances to stderr. use strict; use warnings; # get the file name first my $symbolsinversions=shift @ARGV; # we may get the dir roots pointed out my @manpages=@ARGV; my $errors = 0; my %optblessed; my %funcblessed; my @optorder = ( 'NAME', 'SYNOPSIS', 'DESCRIPTION', #'DEFAULT', # CURLINFO_ has no default 'PROTOCOLS', 'EXAMPLE', 'AVAILABILITY', 'RETURN VALUE', 'SEE ALSO' ); my @funcorder = ( 'NAME', 'SYNOPSIS', 'DESCRIPTION', 'EXAMPLE', 'AVAILABILITY', 'RETURN VALUE', 'SEE ALSO' ); my %shline; # section => line number my %symbol; # some CURLINFO_ symbols are not actual options for curl_easy_getinfo, # mark them as "deprecated" to hide them from link-warnings my %deprecated = ( CURLINFO_TEXT => 1, CURLINFO_HEADER_IN => 1, CURLINFO_HEADER_OUT => 1, CURLINFO_DATA_IN => 1, CURLINFO_DATA_OUT => 1, CURLINFO_SSL_DATA_IN => 1, CURLINFO_SSL_DATA_OUT => 1, ); sub allsymbols { open(my $f, "<", "$symbolsinversions") || die "$symbolsinversions: $|"; while(<$f>) { if($_ =~ /^([^ ]*) +(.*)/) { my ($name, $info) = ($1, $2); $symbol{$name}=$name; if($info =~ /([0-9.]+) +([0-9.]+)/) { $deprecated{$name}=$info; } } } close($f); } sub scanmanpage { my ($file) = @_; my $reqex = 0; my $inex = 0; my $insynop = 0; my $exsize = 0; my $synopsize = 0; my $shc = 0; my $optpage = 0; # option or function my @sh; my $SH=""; open(my $m, "<", "$file") || die "no such file: $file"; if($file =~ /[\/\\](CURL|curl_)[^\/\\]*.3/) { # This is a man page for libcurl. It requires an example! $reqex = 1; if($1 eq "CURL") { $optpage = 1; } } my $line = 1; while(<$m>) { chomp; if($_ =~ /^.so /) { # this man page is just a referral close($m); return; } if(($_ =~ /^\.SH SYNOPSIS/i) && ($reqex)) { # this is for libcurl man page SYNOPSIS checks $insynop = 1; $inex = 0; } elsif($_ =~ /^\.SH EXAMPLE/i) { $insynop = 0; $inex = 1; } elsif($_ =~ /^\.SH/i) { $insynop = 0; $inex = 0; } elsif($inex) { $exsize++; if($_ =~ /[^\\]\\n/) { print STDERR "$file:$line '\\n' need to be '\\\\n'!\n"; } } elsif($insynop) { $synopsize++; if(($synopsize == 1) && ($_ !~ /\.nf/)) { print STDERR "$file:$line:1:ERROR: be .nf for proper formatting\n"; } } if($_ =~ /^\.SH ([^\r\n]*)/i) { my $n = $1; # remove enclosing quotes $n =~ s/\"(.*)\"\z/$1/; push @sh, $n; $shline{$n} = $line; $SH = $n; } if($_ =~ /^\'/) { print STDERR "$file:$line line starts with single quote!\n"; $errors++; } if($_ =~ /\\f([BI])(.*)/) { my ($format, $rest) = ($1, $2); if($rest !~ /\\fP/) { print STDERR "$file:$line missing \\f${format} terminator!\n"; $errors++; } } if($_ =~ /(.*)\\f([^BIP])/) { my ($pre, $format) = ($1, $2); if($pre !~ /\\\z/) { # only if there wasn't another backslash before the \f print STDERR "$file:$line suspicious \\f format!\n"; $errors++; } } if($optpage && $SH && ($SH !~ /^(SYNOPSIS|EXAMPLE|NAME|SEE ALSO)/i) && ($_ =~ /(.*)(CURL(OPT_|MOPT_|INFO_)[A-Z0-9_]*)/)) { # an option with its own man page, check that it is tagged # for linking my ($pref, $symbol) = ($1, $2); if($deprecated{$symbol}) { # let it be } elsif($pref !~ /\\fI\z/) { print STDERR "$file:$line option $symbol missing \\fI tagging\n"; $errors++; } } if($_ =~ /[ \t]+$/) { print STDERR "$file:$line trailing whitespace\n"; $errors++; } if($_ =~ /\\f([BI])([^\\]*)\\fP/) { my $r = $2; if($r =~ /^(CURL.*)\(3\)/) { my $rr = $1; if(!$symbol{$rr}) { print STDERR "$file:$line link to non-libcurl option $rr!\n"; $errors++; } } } $line++; } close($m); if($reqex) { # only for libcurl options man-pages my $shcount = scalar(@sh); # before @sh gets shifted if($exsize < 2) { print STDERR "$file:$line missing EXAMPLE section\n"; $errors++; } if($shcount < 3) { print STDERR "$file:$line too few man page sections!\n"; $errors++; return; } my $got = "start"; my $i = 0; my $shused = 1; my @shorig = @sh; my @order = $optpage ? @optorder : @funcorder; my $blessed = $optpage ? \%optblessed : \%funcblessed; while($got) { my $finesh; $got = shift(@sh); if($got) { if($$blessed{$got}) { $i = $$blessed{$got}; $finesh = $got; # a mandatory one } } if($i && defined($finesh)) { # mandatory section if($i != $shused) { printf STDERR "$file:%u Got %s, when %s was expected\n", $shline{$finesh}, $finesh, $order[$shused-1]; $errors++; return; } $shused++; if($i == scalar(@order)) { # last mandatory one, exit last; } } } if($i != scalar(@order)) { printf STDERR "$file:$line missing mandatory section: %s\n", $order[$i]; printf STDERR "$file:$line section found at index %u: '%s'\n", $i, $shorig[$i]; printf STDERR " Found %u used sections\n", $shcount; $errors++; } } } allsymbols(); if(!$symbol{'CURLALTSVC_H1'}) { print STDERR "didn't get the symbols-in-version!\n"; exit; } my $ind = 1; for my $s (@optorder) { $optblessed{$s} = $ind++ } $ind = 1; for my $s (@funcorder) { $funcblessed{$s} = $ind++ } for my $m (@manpages) { scanmanpage($m); } print STDERR "ok\n" if(!$errors); exit $errors;