• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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