]> sjero.net Git - wget/blob - util/paramcheck.pl
Check for undocumented options in --help, too.
[wget] / util / paramcheck.pl
1 #!/usr/bin/perl
2
3 # Copyright (C) 2008, 2009 Free Software Foundation, Inc.
4
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14
15 # You should have received a copy of the GNU General Public License
16 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 use strict;
19 use warnings;
20 use constant true  => 1;
21 use constant false => 0;
22
23 use FindBin qw($Bin);
24 use File::Spec ();
25
26 my $main_c_file = File::Spec->catfile($Bin, File::Spec->updir, 'src', 'main.c');
27 my $init_c_file = File::Spec->catfile($Bin, File::Spec->updir, 'src', 'init.c');
28 my $tex_file = File::Spec->catfile($Bin, File::Spec->updir, 'doc', 'wget.texi');
29 my $main_content = read_file($main_c_file);
30 my $init_content = read_file($init_c_file);
31 my $tex_content = read_file($tex_file);
32
33 my @args = ([
34     $main_content,
35     qr/static \s+? struct \s+? cmdline_option \s+? option_data\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
36     [ qw(long_name short_name type data argtype) ],
37 ], [
38     $init_content,
39     qr/commands\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
40     [ qw(name place action) ],
41 ]);
42
43 {
44     my @data;
45
46     foreach my $arg (@args) {
47         my ($source, $regex, $names) = @$arg;
48         my @chunks = extract_chunks($source, $regex);
49         push @data, extract_entries(\@chunks, $names);
50     }
51
52     output_results(@data);
53 }
54
55 sub read_file
56 {
57     my ($file) = @_;
58
59     open(my $fh, '<', $file) or die "Cannot open $file: $!";
60
61     return do { local $/; <$fh> };
62 }
63
64 sub extract_chunks
65 {
66     my ($source, $regex) = @_;
67
68     my ($raw_data) = $source =~ $regex;
69
70     return map { /\S/ && !/^\#/ ? $_ : () } split /(?<=\})/, $raw_data;
71 }
72
73 sub extract_entries
74 {
75     my ($chunks, $names) = @_;
76
77     my (@entries, %index, $i);
78
79     foreach my $chunk (@$chunks) {
80         my ($args) = $chunk =~ /\{ \s+? (.*?) \s+? \}/sx;
81         next unless defined $args;
82
83         my @args = map {
84           tr/'"//d; $_
85         } map {
86           /\((.*?)\)/ ? $1 : $_
87         } split /\,\s+/, $args;
88
89         my $entry = { map { $_ => shift @args } @$names };
90
91         ($entry->{line}) = $chunk =~ /^ \s+? (\{.*)/mx;
92         if ($chunk =~ /deprecated/i) {
93             $entries[-1]->{deprecated} = true;
94         }
95
96         my $index_name = exists $entry->{data}
97           ? $entry->{data}
98           : $entry->{name};
99
100         $index{$index_name} = $i++;
101
102         push @entries, $entry;
103     }
104
105     push @entries, \%index;
106
107     return \@entries;
108 }
109
110 sub output_results
111 {
112     my ($opts, $cmds) = @_;
113
114     my %index = (
115         opts => pop @$opts,
116         cmds => pop @$cmds,
117     );
118
119     emit_no_corresponding_cmds($opts);
120     print "\n";
121     emit_no_matching_long_cmds($opts);
122     print "\n";
123     emit_no_corresponding_opts($opts, $cmds);
124     print "\n";
125     emit_deprecated_opts($opts);
126     print "\n";
127     emit_deprecated_cmds($cmds);
128     print "\n";
129
130     emit_undocumented_opts($tex_content, $main_content, $opts);
131     print "\n";
132     emit_undocumented_cmds($tex_content, $opts, $cmds, \%index);
133     print "\n";
134 }
135
136 sub emit_no_corresponding_cmds
137 {
138     my ($opts) = @_;
139
140     my @options;
141     foreach my $opt (@$opts) {
142         unless ($opt->{type} =~ /^(?:OPT_BOOLEAN|OPT_VALUE)$/
143              && $opt->{argtype} == -1)
144         {
145             push @options, $opt->{line};
146         }
147     }
148
149     local $" = "\n";
150     print <<EOT;
151 No corresponding commands
152 =========================
153 @options
154 EOT
155 }
156
157 sub emit_no_matching_long_cmds
158 {
159     my ($opts) = @_;
160
161     my @options;
162     foreach my $opt (@$opts) {
163         my $long_name = $opt->{long_name};
164         $long_name =~ tr/-//d;
165         unless ($long_name eq $opt->{data}) {
166             push @options, $opt->{line};
167         }
168     }
169
170     local $" = "\n";
171     print <<EOT;
172 Non-matching commands
173 =====================
174 @options
175 EOT
176 }
177
178 sub emit_no_corresponding_opts
179 {
180     my ($opts, $cmds) = @_;
181
182     my @commands;
183     foreach my $cmd (@$cmds) {
184         my $found = false;
185         foreach my $opt (@$opts) {
186             my $long_name = $opt->{long_name};
187             $long_name =~ tr/-//d;
188             if ($cmd->{name} eq $opt->{data}
189              || $cmd->{name} eq $long_name) {
190                 $found = true;
191                 last;
192             }
193         }
194         unless ($found) {
195             push @commands, $cmd->{line};
196         }
197     }
198
199     local $" = "\n";
200     print <<EOT;
201 No corresponding options
202 ========================
203 @commands
204 EOT
205 }
206
207 sub emit_deprecated_opts
208 {
209     my ($opts) = @_;
210
211     my @options;
212     foreach my $opt (@$opts) {
213         if ($opt->{deprecated}) {
214             push @options, $opt->{line};
215         }
216     }
217
218     local $" = "\n";
219     print <<EOT;
220 Deprecated options
221 ==================
222 @options
223 EOT
224 }
225
226 sub emit_deprecated_cmds
227 {
228     my ($cmds) = @_;
229
230     my @commands;
231     foreach my $cmd (@$cmds) {
232         if ($cmd->{deprecated}) {
233             push @commands, $cmd->{line};
234         }
235     }
236
237     local $" = "\n";
238     print <<EOT;
239 Deprecated commands
240 ===================
241 @commands
242 EOT
243 }
244
245 sub find_documentation
246 {
247     my ($push, $opt) = (shift, shift);
248     my $info = [];
249
250     my $opt_name = $opt->{'long_name'};
251     for my $doc (@_) {
252         next if $opt->{'deprecated'};
253         if ($doc->{$opt_name}
254             || ($opt_name !~ /^no/ && $doc->{"no-$opt_name"})) {
255             push @$info, 1;
256         } else {
257             push @$info, 0;
258         }
259     }
260     push @$push, [$opt_name, @$info] if grep {$_ eq 0} @$info;
261 }
262
263 sub emit_undocumented_opts
264 {
265     my ($tex, $main, $opts) = @_;
266
267     my (%tex_items, %main_items);
268     while ($tex =~ /^\@item\w*? \s+? --([\w\-]+)/gmx) {
269         $tex_items{$1} = true;
270     }
271     ($main) = $main =~ /(\nprint_help.*\n}\n)/s;
272     while ($main =~ /--([-a-z0-9]+)/g) {
273         $main_items{$1} = true;
274     }
275     my @options;
276     foreach my $opt (@$opts) {
277         find_documentation (\@options, $opt, \%tex_items, \%main_items);
278     }
279
280     local $" = "\n";
281     print <<EOT;
282 Undocumented options          Not In:
283 ====================          ==================
284 EOT
285     for my $opt (@options) {
286         printf("%-29s ", $opt->[0]);
287         print 'texinfo ' unless $opt->[1];
288         print 'nor ' unless $opt->[1] or $opt->[2];
289         print '--help ' unless $opt->[2];
290         print "\n";
291     }
292 }
293
294 sub emit_undocumented_cmds
295 {
296     my ($tex, $opts, $cmds, $index) = @_;
297
298     my %items;
299     while ($tex =~ /^\@item\w*? \s+? ([\w\_]+) \s+? = \s+? \S+?/gmx) {
300         my $cmd = $1;
301         $cmd =~ tr/_//d;
302         $items{$cmd} = true;
303     }
304     my @commands;
305     foreach my $cmd (@$cmds) {
306         my $cmd_name = do {
307             local $_ = exists $index->{opts}->{$cmd->{name}}
308               ? $opts->[$index->{opts}->{$cmd->{name}}]->{long_name}
309               : $cmd->{name};
310             tr/-/_/;
311             $_;
312         };
313         if (not $items{$cmd->{name}} || $cmd->{deprecated}) {
314             push @commands, $cmd_name;
315         }
316     }
317
318     local $" = "\n";
319     print <<EOT;
320 Undocumented commands
321 =====================
322 @commands
323 EOT
324 }