1#!/usr/bin/env perl 2#*************************************************************************** 3# _ _ ____ _ 4# Project ___| | | | _ \| | 5# / __| | | | |_) | | 6# | (__| |_| | _ <| |___ 7# \___|\___/|_| \_\_____| 8# 9# Copyright (C) 2019 - 2021, 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########################################################################### 23# 24# Scan man page(s) and detect some simple and yet common formatting mistakes. 25# 26# Output all deviances to stderr. 27 28use strict; 29use warnings; 30 31# get the file name first 32my $symbolsinversions=shift @ARGV; 33 34# we may get the dir roots pointed out 35my @manpages=@ARGV; 36my $errors = 0; 37 38my %optblessed; 39my %funcblessed; 40my @optorder = ( 41 'NAME', 42 'SYNOPSIS', 43 'DESCRIPTION', 44 #'DEFAULT', # CURLINFO_ has no default 45 'PROTOCOLS', 46 'EXAMPLE', 47 'AVAILABILITY', 48 'RETURN VALUE', 49 'SEE ALSO' 50 ); 51my @funcorder = ( 52 'NAME', 53 'SYNOPSIS', 54 'DESCRIPTION', 55 'EXAMPLE', 56 'AVAILABILITY', 57 'RETURN VALUE', 58 'SEE ALSO' 59 ); 60my %shline; # section => line number 61 62my %symbol; 63sub allsymbols { 64 open(F, "<$symbolsinversions") || 65 die "$symbolsinversions: $|"; 66 while(<F>) { 67 if($_ =~ /^([^ ]*)/) { 68 $symbol{$1}=$1; 69 } 70 } 71 close(F); 72} 73 74sub scanmanpage { 75 my ($file) = @_; 76 my $reqex = 0; 77 my $inex = 0; 78 my $exsize = 0; 79 my $shc = 0; 80 my $optpage = 0; # option or function 81 my @sh; 82 83 open(M, "<$file") || die "no such file: $file"; 84 if($file =~ /[\/\\](CURL|curl_)[^\/\\]*.3/) { 85 # This is the man page for an libcurl option. It requires an example! 86 $reqex = 1; 87 if($1 eq "CURL") { 88 $optpage = 1; 89 } 90 } 91 my $line = 1; 92 while(<M>) { 93 chomp; 94 if($_ =~ /^.so /) { 95 # this man page is just a referral 96 close(M); 97 return; 98 } 99 if($_ =~ /^\.SH EXAMPLE/i) { 100 $inex = 1; 101 } 102 elsif($_ =~ /^\.SH/i) { 103 $inex = 0; 104 } 105 elsif($inex) { 106 $exsize++; 107 if($_ =~ /[^\\]\\n/) { 108 print STDERR "$file:$line '\\n' need to be '\\\\n'!\n"; 109 } 110 } 111 if($_ =~ /^\.SH ([^\r\n]*)/i) { 112 my $n = $1; 113 # remove enclosing quotes 114 $n =~ s/\"(.*)\"\z/$1/; 115 push @sh, $n; 116 $shline{$n} = $line; 117 } 118 119 if($_ =~ /^\'/) { 120 print STDERR "$file:$line line starts with single quote!\n"; 121 $errors++; 122 } 123 if($_ =~ /\\f([BI])(.*)/) { 124 my ($format, $rest) = ($1, $2); 125 if($rest !~ /\\fP/) { 126 print STDERR "$file:$line missing \\f${format} terminator!\n"; 127 $errors++; 128 } 129 } 130 if($_ =~ /[ \t]+$/) { 131 print STDERR "$file:$line trailing whitespace\n"; 132 $errors++; 133 } 134 if($_ =~ /\\f([BI])([^\\]*)\\fP/) { 135 my $r = $2; 136 if($r =~ /^(CURL.*)\(3\)/) { 137 my $rr = $1; 138 if(!$symbol{$rr}) { 139 print STDERR "$file:$line link to non-libcurl option $rr!\n"; 140 $errors++; 141 } 142 } 143 } 144 $line++; 145 } 146 close(M); 147 148 if($reqex) { 149 # only for libcurl options man-pages 150 151 my $shcount = scalar(@sh); # before @sh gets shifted 152 if($exsize < 2) { 153 print STDERR "$file:$line missing EXAMPLE section\n"; 154 $errors++; 155 } 156 157 if($shcount < 3) { 158 print STDERR "$file:$line too few man page sections!\n"; 159 $errors++; 160 return; 161 } 162 163 my $got = "start"; 164 my $i = 0; 165 my $shused = 1; 166 my @shorig = @sh; 167 my @order = $optpage ? @optorder : @funcorder; 168 my $blessed = $optpage ? \%optblessed : \%funcblessed; 169 170 while($got) { 171 my $finesh; 172 $got = shift(@sh); 173 if($got) { 174 if($$blessed{$got}) { 175 $i = $$blessed{$got}; 176 $finesh = $got; # a mandatory one 177 } 178 } 179 if($i && defined($finesh)) { 180 # mandatory section 181 182 if($i != $shused) { 183 printf STDERR "$file:%u Got %s, when %s was expected\n", 184 $shline{$finesh}, 185 $finesh, 186 $order[$shused-1]; 187 $errors++; 188 return; 189 } 190 $shused++; 191 if($i == scalar(@order)) { 192 # last mandatory one, exit 193 last; 194 } 195 } 196 } 197 198 if($i != scalar(@order)) { 199 printf STDERR "$file:$line missing mandatory section: %s\n", 200 $order[$i]; 201 printf STDERR "$file:$line section found at index %u: '%s'\n", 202 $i, $shorig[$i]; 203 printf STDERR " Found %u used sections\n", $shcount; 204 $errors++; 205 } 206 } 207} 208 209allsymbols(); 210 211if(!$symbol{'CURLALTSVC_H1'}) { 212 print STDERR "didn't get the symbols-in-version!\n"; 213 exit; 214} 215 216my $ind = 1; 217for my $s (@optorder) { 218 $optblessed{$s} = $ind++ 219} 220$ind = 1; 221for my $s (@funcorder) { 222 $funcblessed{$s} = $ind++ 223} 224 225for my $m (@manpages) { 226 scanmanpage($m); 227} 228 229print STDERR "ok\n" if(!$errors); 230 231exit $errors; 232