blob: a83223d94d8563bd6af7b1d0c5d6046c905fc835 [file] [log] [blame]
xf.li6c8fc1e2023-08-12 00:11:09 -07001#!/usr/bin/env perl
2#***************************************************************************
3# _ _ ____ _
4# Project ___| | | | _ \| |
5# / __| | | | |_) | |
6# | (__| |_| | _ <| |___
7# \___|\___/|_| \_\_____|
8#
9# Copyright (C) 2019 - 2022, 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# Scan man page(s) and detect some simple and yet common formatting mistakes.
27#
28# Output all deviances to stderr.
29
30use strict;
31use warnings;
32
33# get the file name first
34my $symbolsinversions=shift @ARGV;
35
36# we may get the dir roots pointed out
37my @manpages=@ARGV;
38my $errors = 0;
39
40my %optblessed;
41my %funcblessed;
42my @optorder = (
43 'NAME',
44 'SYNOPSIS',
45 'DESCRIPTION',
46 #'DEFAULT', # CURLINFO_ has no default
47 'PROTOCOLS',
48 'EXAMPLE',
49 'AVAILABILITY',
50 'RETURN VALUE',
51 'SEE ALSO'
52 );
53my @funcorder = (
54 'NAME',
55 'SYNOPSIS',
56 'DESCRIPTION',
57 'EXAMPLE',
58 'AVAILABILITY',
59 'RETURN VALUE',
60 'SEE ALSO'
61 );
62my %shline; # section => line number
63
64my %symbol;
65
66# some CURLINFO_ symbols are not actual options for curl_easy_getinfo,
67# mark them as "deprecated" to hide them from link-warnings
68my %deprecated = (
69 CURLINFO_TEXT => 1,
70 CURLINFO_HEADER_IN => 1,
71 CURLINFO_HEADER_OUT => 1,
72 CURLINFO_DATA_IN => 1,
73 CURLINFO_DATA_OUT => 1,
74 CURLINFO_SSL_DATA_IN => 1,
75 CURLINFO_SSL_DATA_OUT => 1,
76 );
77sub allsymbols {
78 open(F, "<$symbolsinversions") ||
79 die "$symbolsinversions: $|";
80 while(<F>) {
81 if($_ =~ /^([^ ]*) +(.*)/) {
82 my ($name, $info) = ($1, $2);
83 $symbol{$name}=$name;
84
85 if($info =~ /([0-9.]+) +([0-9.]+)/) {
86 $deprecated{$name}=$info;
87 }
88 }
89 }
90 close(F);
91}
92
93sub scanmanpage {
94 my ($file) = @_;
95 my $reqex = 0;
96 my $inex = 0;
97 my $insynop = 0;
98 my $exsize = 0;
99 my $synopsize = 0;
100 my $shc = 0;
101 my $optpage = 0; # option or function
102 my @sh;
103 my $SH="";
104
105 open(M, "<$file") || die "no such file: $file";
106 if($file =~ /[\/\\](CURL|curl_)[^\/\\]*.3/) {
107 # This is a man page for libcurl. It requires an example!
108 $reqex = 1;
109 if($1 eq "CURL") {
110 $optpage = 1;
111 }
112 }
113 my $line = 1;
114 while(<M>) {
115 chomp;
116 if($_ =~ /^.so /) {
117 # this man page is just a referral
118 close(M);
119 return;
120 }
121 if(($_ =~ /^\.SH SYNOPSIS/i) && ($reqex)) {
122 # this is for libcurl man page SYNOPSIS checks
123 $insynop = 1;
124 $inex = 0;
125 }
126 elsif($_ =~ /^\.SH EXAMPLE/i) {
127 $insynop = 0;
128 $inex = 1;
129 }
130 elsif($_ =~ /^\.SH/i) {
131 $insynop = 0;
132 $inex = 0;
133 }
134 elsif($inex) {
135 $exsize++;
136 if($_ =~ /[^\\]\\n/) {
137 print STDERR "$file:$line '\\n' need to be '\\\\n'!\n";
138 }
139 }
140 elsif($insynop) {
141 $synopsize++;
142 if(($synopsize == 1) && ($_ !~ /\.nf/)) {
143 print STDERR "$file:$line:1:ERROR: be .nf for proper formatting\n";
144 }
145 }
146 if($_ =~ /^\.SH ([^\r\n]*)/i) {
147 my $n = $1;
148 # remove enclosing quotes
149 $n =~ s/\"(.*)\"\z/$1/;
150 push @sh, $n;
151 $shline{$n} = $line;
152 $SH = $n;
153 }
154
155 if($_ =~ /^\'/) {
156 print STDERR "$file:$line line starts with single quote!\n";
157 $errors++;
158 }
159 if($_ =~ /\\f([BI])(.*)/) {
160 my ($format, $rest) = ($1, $2);
161 if($rest !~ /\\fP/) {
162 print STDERR "$file:$line missing \\f${format} terminator!\n";
163 $errors++;
164 }
165 }
166 if($_ =~ /(.*)\\f([^BIP])/) {
167 my ($pre, $format) = ($1, $2);
168 if($pre !~ /\\\z/) {
169 # only if there wasn't another backslash before the \f
170 print STDERR "$file:$line suspicious \\f format!\n";
171 $errors++;
172 }
173 }
174 if($optpage && $SH && ($SH !~ /^(SYNOPSIS|EXAMPLE|NAME|SEE ALSO)/i) &&
175 ($_ =~ /(.*)(CURL(OPT_|MOPT_|INFO_)[A-Z0-9_]*)/)) {
176 # an option with its own man page, check that it is tagged
177 # for linking
178 my ($pref, $symbol) = ($1, $2);
179 if($deprecated{$symbol}) {
180 # let it be
181 }
182 elsif($pref !~ /\\fI\z/) {
183 print STDERR "$file:$line option $symbol missing \\fI tagging\n";
184 $errors++;
185 }
186 }
187 if($_ =~ /[ \t]+$/) {
188 print STDERR "$file:$line trailing whitespace\n";
189 $errors++;
190 }
191 if($_ =~ /\\f([BI])([^\\]*)\\fP/) {
192 my $r = $2;
193 if($r =~ /^(CURL.*)\(3\)/) {
194 my $rr = $1;
195 if(!$symbol{$rr}) {
196 print STDERR "$file:$line link to non-libcurl option $rr!\n";
197 $errors++;
198 }
199 }
200 }
201 $line++;
202 }
203 close(M);
204
205 if($reqex) {
206 # only for libcurl options man-pages
207
208 my $shcount = scalar(@sh); # before @sh gets shifted
209 if($exsize < 2) {
210 print STDERR "$file:$line missing EXAMPLE section\n";
211 $errors++;
212 }
213
214 if($shcount < 3) {
215 print STDERR "$file:$line too few man page sections!\n";
216 $errors++;
217 return;
218 }
219
220 my $got = "start";
221 my $i = 0;
222 my $shused = 1;
223 my @shorig = @sh;
224 my @order = $optpage ? @optorder : @funcorder;
225 my $blessed = $optpage ? \%optblessed : \%funcblessed;
226
227 while($got) {
228 my $finesh;
229 $got = shift(@sh);
230 if($got) {
231 if($$blessed{$got}) {
232 $i = $$blessed{$got};
233 $finesh = $got; # a mandatory one
234 }
235 }
236 if($i && defined($finesh)) {
237 # mandatory section
238
239 if($i != $shused) {
240 printf STDERR "$file:%u Got %s, when %s was expected\n",
241 $shline{$finesh},
242 $finesh,
243 $order[$shused-1];
244 $errors++;
245 return;
246 }
247 $shused++;
248 if($i == scalar(@order)) {
249 # last mandatory one, exit
250 last;
251 }
252 }
253 }
254
255 if($i != scalar(@order)) {
256 printf STDERR "$file:$line missing mandatory section: %s\n",
257 $order[$i];
258 printf STDERR "$file:$line section found at index %u: '%s'\n",
259 $i, $shorig[$i];
260 printf STDERR " Found %u used sections\n", $shcount;
261 $errors++;
262 }
263 }
264}
265
266allsymbols();
267
268if(!$symbol{'CURLALTSVC_H1'}) {
269 print STDERR "didn't get the symbols-in-version!\n";
270 exit;
271}
272
273my $ind = 1;
274for my $s (@optorder) {
275 $optblessed{$s} = $ind++
276}
277$ind = 1;
278for my $s (@funcorder) {
279 $funcblessed{$s} = $ind++
280}
281
282for my $m (@manpages) {
283 scanmanpage($m);
284}
285
286print STDERR "ok\n" if(!$errors);
287
288exit $errors;