Disable a debug option
[platform/upstream/curl.git] / tests / manpage-syntax.pl
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 # Scan man page(s) and detect some simple and yet common formatting mistakes.
27 #
28 # Output all deviances to stderr.
29
30 use strict;
31 use warnings;
32 use File::Basename;
33
34 # get the file name first
35 my $symbolsinversions=shift @ARGV;
36
37 # we may get the dir roots pointed out
38 my @manpages=@ARGV;
39 my $errors = 0;
40
41 my %docsdirs;
42 my %optblessed;
43 my %funcblessed;
44 my @optorder = (
45     'NAME',
46     'SYNOPSIS',
47     'DESCRIPTION',
48      #'DEFAULT', # CURLINFO_ has no default
49     'PROTOCOLS',
50     'EXAMPLE',
51     'AVAILABILITY',
52     'RETURN VALUE',
53     'SEE ALSO'
54     );
55 my @funcorder = (
56     'NAME',
57     'SYNOPSIS',
58     'DESCRIPTION',
59     'EXAMPLE',
60     'AVAILABILITY',
61     'RETURN VALUE',
62     'SEE ALSO'
63     );
64 my %shline; # section => line number
65
66 my %symbol;
67
68 # some CURLINFO_ symbols are not actual options for curl_easy_getinfo,
69 # mark them as "deprecated" to hide them from link-warnings
70 my %deprecated = (
71     CURLINFO_TEXT => 1,
72     CURLINFO_HEADER_IN => 1,
73     CURLINFO_HEADER_OUT => 1,
74     CURLINFO_DATA_IN => 1,
75     CURLINFO_DATA_OUT => 1,
76     CURLINFO_SSL_DATA_IN => 1,
77     CURLINFO_SSL_DATA_OUT => 1,
78     );
79 sub allsymbols {
80     open(my $f, "<", "$symbolsinversions") ||
81         die "$symbolsinversions: $|";
82     while(<$f>) {
83         if($_ =~ /^([^ ]*) +(.*)/) {
84             my ($name, $info) = ($1, $2);
85             $symbol{$name}=$name;
86
87             if($info =~ /([0-9.]+) +([0-9.]+)/) {
88                 $deprecated{$name}=$info;
89             }
90         }
91     }
92     close($f);
93 }
94
95
96 my %ref = (
97     'curl.1' => 1
98     );
99 sub checkref {
100     my ($f, $sec, $file, $line)=@_;
101     my $present = 0;
102     #print STDERR "check $f.$sec\n";
103     if($ref{"$f.$sec"}) {
104         # present
105         return;
106     }
107     foreach my $d (keys %docsdirs) {
108         if( -f "$d/$f.$sec") {
109             $present = 1;
110             $ref{"$f.$sec"}=1;
111             last;
112         }
113     }
114     if(!$present) {
115         print STDERR "$file:$line broken reference to $f($sec)\n";
116         $errors++;
117     }
118 }
119
120 sub scanmanpage {
121     my ($file) = @_;
122     my $reqex = 0;
123     my $inseealso = 0;
124     my $inex = 0;
125     my $insynop = 0;
126     my $exsize = 0;
127     my $synopsize = 0;
128     my $shc = 0;
129     my $optpage = 0; # option or function
130     my @sh;
131     my $SH="";
132     my @separators;
133     my @sepline;
134
135     open(my $m, "<", "$file") || die "no such file: $file";
136     if($file =~ /[\/\\](CURL|curl_)[^\/\\]*.3/) {
137         # This is a man page for libcurl. It requires an example!
138         $reqex = 1;
139         if($1 eq "CURL") {
140             $optpage = 1;
141         }
142     }
143     my $line = 1;
144     while(<$m>) {
145         chomp;
146         if($_ =~ /^.so /) {
147             # this man page is just a referral
148             close($m);
149             return;
150         }
151         if(($_ =~ /^\.SH SYNOPSIS/i) && ($reqex)) {
152             # this is for libcurl man page SYNOPSIS checks
153             $insynop = 1;
154             $inex = 0;
155         }
156         elsif($_ =~ /^\.SH EXAMPLE/i) {
157             $insynop = 0;
158             $inex = 1;
159         }
160         elsif($_ =~ /^\.SH \"SEE ALSO\"/i) {
161             $inseealso = 1;
162         }
163         elsif($_ =~ /^\.SH/i) {
164             $insynop = 0;
165             $inex = 0;
166         }
167         elsif($inseealso) {
168             if($_ =~ /^\.BR (.*)/i) {
169                 my $f = $1;
170                 if($f =~ /^(lib|)curl/i) {
171                     $f =~ s/[\n\r]//g;
172                     if($f =~ s/([a-z_0-9-]*) \(([13])\)([, ]*)//i) {
173                         push @separators, $3;
174                         push @sepline, $line;
175                         checkref($1, $2, $file, $line);
176                     }
177                     if($f !~ /^ *$/) {
178                         print STDERR "$file:$line bad SEE ALSO format\n";
179                         $errors++;
180                     }
181                 }
182                 else {
183                     if($f =~ /.*(, *)\z/) {
184                         push @separators, $1;
185                         push @sepline, $line;
186                     }
187                     else {
188                         push @separators, " ";
189                         push @sepline, $line;
190                     }
191                 }
192             }
193         }
194         elsif($inex)  {
195             $exsize++;
196             if($_ =~ /[^\\]\\n/) {
197                 print STDERR "$file:$line '\\n' need to be '\\\\n'!\n";
198             }
199         }
200         elsif($insynop)  {
201             $synopsize++;
202             if(($synopsize == 1) && ($_ !~ /\.nf/)) {
203                 print STDERR "$file:$line:1:ERROR: be .nf for proper formatting\n";
204             }
205         }
206         if($_ =~ /^\.SH ([^\r\n]*)/i) {
207             my $n = $1;
208             # remove enclosing quotes
209             $n =~ s/\"(.*)\"\z/$1/;
210             push @sh, $n;
211             $shline{$n} = $line;
212             $SH = $n;
213         }
214
215         if($_ =~ /^\'/) {
216             print STDERR "$file:$line line starts with single quote!\n";
217             $errors++;
218         }
219         if($_ =~ /\\f([BI])(.*)/) {
220             my ($format, $rest) = ($1, $2);
221             if($rest !~ /\\fP/) {
222                 print STDERR "$file:$line missing \\f${format} terminator!\n";
223                 $errors++;
224             }
225         }
226         my $c = $_;
227         while($c =~ s/\\f([BI])((lib|)curl[a-z_0-9-]*)\(([13])\)//i) {
228             checkref($2, $4, $file, $line);
229         }
230         if(($_ =~ /\\f([BI])((libcurl|CURLOPT_|CURLSHOPT_|CURLINFO_|CURLMOPT_|curl_easy_|curl_multi_|curl_url|curl_mime|curl_global|curl_share)[a-zA-Z_0-9-]+)(.)/) &&
231            ($4 ne "(")) {
232             print STDERR "$file:$line curl ref to $2 without section\n";
233             $errors++;
234         }
235         if($_ =~ /(.*)\\f([^BIP])/) {
236             my ($pre, $format) = ($1, $2);
237             if($pre !~ /\\\z/) {
238                 # only if there wasn't another backslash before the \f
239                 print STDERR "$file:$line suspicious \\f format!\n";
240                 $errors++;
241             }
242         }
243         if(($SH =~ /^(DESCRIPTION|RETURN VALUE|AVAILABILITY)/i) &&
244            ($_ =~ /(.*)((curl_multi|curl_easy|curl_url|curl_global|curl_url|curl_share)[a-zA-Z_0-9-]+)/) &&
245            ($1 !~ /\\fI$/)) {
246             print STDERR "$file:$line unrefed curl call: $2\n";
247             $errors++;
248         }
249
250
251         if($optpage && $SH && ($SH !~ /^(SYNOPSIS|EXAMPLE|NAME|SEE ALSO)/i) &&
252            ($_ =~ /(.*)(CURL(OPT_|MOPT_|INFO_|SHOPT_)[A-Z0-9_]*)/)) {
253             # an option with its own man page, check that it is tagged
254             # for linking
255             my ($pref, $symbol) = ($1, $2);
256             if($deprecated{$symbol}) {
257                 # let it be
258             }
259             elsif($pref !~ /\\fI\z/) {
260                 print STDERR "$file:$line option $symbol missing \\fI tagging\n";
261                 $errors++;
262             }
263         }
264         if($_ =~ /[ \t]+$/) {
265             print STDERR "$file:$line trailing whitespace\n";
266             $errors++;
267         }
268         $line++;
269     }
270     close($m);
271
272     if(@separators) {
273         # all except the last one need comma
274         for(0 .. $#separators - 1) {
275             my $l = $_;
276             my $sep = $separators[$l];
277             if($sep ne ",") {
278                 printf STDERR "$file:%d: bad not-last SEE ALSO separator: '%s'\n",
279                     $sepline[$l], $sep;
280                 $errors++;
281             }
282         }
283         # the last one should not do comma
284         my $sep = $separators[$#separators];
285         if($sep eq ",") {
286             printf STDERR "$file:%d: superfluous comma separator\n",
287                 $sepline[$#separators];
288             $errors++;
289         }
290     }
291
292     if($reqex) {
293         # only for libcurl options man-pages
294
295         my $shcount = scalar(@sh); # before @sh gets shifted
296         if($exsize < 2) {
297             print STDERR "$file:$line missing EXAMPLE section\n";
298             $errors++;
299         }
300
301         if($shcount < 3) {
302             print STDERR "$file:$line too few man page sections!\n";
303             $errors++;
304             return;
305         }
306
307         my $got = "start";
308         my $i = 0;
309         my $shused = 1;
310         my @shorig = @sh;
311         my @order = $optpage ? @optorder : @funcorder;
312         my $blessed = $optpage ? \%optblessed : \%funcblessed;
313
314         while($got) {
315             my $finesh;
316             $got = shift(@sh);
317             if($got) {
318                 if($$blessed{$got}) {
319                     $i = $$blessed{$got};
320                     $finesh = $got; # a mandatory one
321                 }
322             }
323             if($i && defined($finesh)) {
324                 # mandatory section
325
326                 if($i != $shused) {
327                     printf STDERR "$file:%u Got %s, when %s was expected\n",
328                         $shline{$finesh},
329                         $finesh,
330                         $order[$shused-1];
331                     $errors++;
332                     return;
333                 }
334                 $shused++;
335                 if($i == scalar(@order)) {
336                     # last mandatory one, exit
337                     last;
338                 }
339             }
340         }
341
342         if($i != scalar(@order)) {
343             printf STDERR "$file:$line missing mandatory section: %s\n",
344                 $order[$i];
345             printf STDERR "$file:$line section found at index %u: '%s'\n",
346                 $i, $shorig[$i];
347             printf STDERR " Found %u used sections\n", $shcount;
348             $errors++;
349         }
350     }
351 }
352
353 allsymbols();
354
355 if(!$symbol{'CURLALTSVC_H1'}) {
356     print STDERR "didn't get the symbols-in-version!\n";
357     exit;
358 }
359
360 my $ind = 1;
361 for my $s (@optorder) {
362     $optblessed{$s} = $ind++
363 }
364 $ind = 1;
365 for my $s (@funcorder) {
366     $funcblessed{$s} = $ind++
367 }
368
369 for my $m (@manpages) {
370     $docsdirs{dirname($m)}++;
371 }
372
373 for my $m (@manpages) {
374     scanmanpage($m);
375 }
376
377 print STDERR "ok\n" if(!$errors);
378
379 exit $errors;