]> sjero.net Git - wget/blob - util/paramcheck.pl
5fd562b9898a25497d812dca9e8f44df0bedc0b6
[wget] / util / paramcheck.pl
1 #!/usr/bin/env perl
2
3 # Copyright (C) 2008, 2009, 2010 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
30 my $main_content = read_file($main_c_file);
31 my $init_content = read_file($init_c_file);
32 my $tex_content  = read_file($tex_file);
33
34 my @args = ([
35     $main_content,
36     qr/static \s+? struct \s+? cmdline_option \s+? option_data\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
37     [ qw(long_name short_name type data argtype) ],
38 ], [
39     $init_content,
40     qr/commands\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
41     [ qw(name place action) ],
42 ]);
43
44 {
45     my @data;
46
47     foreach my $arg (@args) {
48         my ($source, $regex, $names) = @$arg;
49         my @chunks = extract_chunks($source, $regex);
50         push @data, extract_entries(\@chunks, $names);
51     }
52
53     output_results(@data);
54 }
55
56 sub read_file
57 {
58     my ($file) = @_;
59
60     open(my $fh, '<', $file) or die "Cannot open $file: $!";
61
62     return do { local $/; <$fh> };
63 }
64
65 sub extract_chunks
66 {
67     my ($source, $regex) = @_;
68
69     my ($raw_data) = $source =~ $regex;
70
71     return map { /\S/ && !/^\#/ ? $_ : () } split /(?<=\})/, $raw_data;
72 }
73
74 sub extract_entries
75 {
76     my ($chunks, $names) = @_;
77
78     my (@entries, %index, $i);
79
80     foreach my $chunk (@$chunks) {
81         my ($args) = $chunk =~ /\{ \s+? (.*?) \s+? \}/sx;
82         next unless defined $args;
83
84         my @args = map {
85           tr/'"//d; $_
86         } map {
87           /\((.*?)\)/ ? $1 : $_
88         } split /\,\s+/, $args;
89
90         my $entry = { map { $_ => shift @args } @$names };
91
92         ($entry->{line}) = $chunk =~ /^ \s+? (\{.*)/mx;
93         if ($chunk =~ /deprecated/i) {
94             $entries[-1]->{deprecated} = true;
95         }
96
97         my $index_name = exists $entry->{data}
98           ? $entry->{data}
99           : $entry->{name};
100
101         $index{$index_name} = $i++;
102
103         push @entries, $entry;
104     }
105
106     push @entries, \%index;
107
108     return \@entries;
109 }
110
111 sub output_results
112 {
113     my ($opts, $cmds) = @_;
114
115     my %index = (
116         opts => pop @$opts,
117         cmds => pop @$cmds,
118     );
119
120     emit_no_corresponding_cmds($opts);
121     print "\n";
122     emit_no_matching_long_cmds($opts);
123     print "\n";
124     emit_no_corresponding_opts($opts, $cmds);
125     print "\n";
126     emit_deprecated_opts($opts);
127     print "\n";
128     emit_deprecated_cmds($cmds);
129     print "\n";
130
131     emit_undocumented_opts($tex_content, $main_content, $opts);
132     print "\n";
133     emit_undocumented_cmds($tex_content, $opts, $cmds, \%index);
134     print "\n";
135 }
136
137 sub emit_no_corresponding_cmds
138 {
139     my ($opts) = @_;
140
141     my @options;
142     foreach my $opt (@$opts) {
143         unless ($opt->{type} =~ /^(?:OPT_BOOLEAN|OPT_VALUE)$/
144              && $opt->{argtype} == -1)
145         {
146             push @options, $opt->{line};
147         }
148     }
149
150     local $" = "\n";
151     print <<EOT;
152 No corresponding commands
153 =========================
154 @options
155 EOT
156 }
157
158 sub emit_no_matching_long_cmds
159 {
160     my ($opts) = @_;
161
162     my @options;
163     foreach my $opt (@$opts) {
164         my $long_name = $opt->{long_name};
165         $long_name =~ tr/-//d;
166         unless ($long_name eq $opt->{data}) {
167             push @options, $opt->{line};
168         }
169     }
170
171     local $" = "\n";
172     print <<EOT;
173 Non-matching commands
174 =====================
175 @options
176 EOT
177 }
178
179 sub emit_no_corresponding_opts
180 {
181     my ($opts, $cmds) = @_;
182
183     my @commands;
184     foreach my $cmd (@$cmds) {
185         my $found = false;
186         foreach my $opt (@$opts) {
187             my $long_name = $opt->{long_name};
188             $long_name =~ tr/-//d;
189             if ($cmd->{name} eq $opt->{data}
190              || $cmd->{name} eq $long_name) {
191                 $found = true;
192                 last;
193             }
194         }
195         unless ($found) {
196             push @commands, $cmd->{line};
197         }
198     }
199
200     local $" = "\n";
201     print <<EOT;
202 No corresponding options
203 ========================
204 @commands
205 EOT
206 }
207
208 sub emit_deprecated_opts
209 {
210     my ($opts) = @_;
211
212     my @options;
213     foreach my $opt (@$opts) {
214         if ($opt->{deprecated}) {
215             push @options, $opt->{line};
216         }
217     }
218
219     local $" = "\n";
220     print <<EOT;
221 Deprecated options
222 ==================
223 @options
224 EOT
225 }
226
227 sub emit_deprecated_cmds
228 {
229     my ($cmds) = @_;
230
231     my @commands;
232     foreach my $cmd (@$cmds) {
233         if ($cmd->{deprecated}) {
234             push @commands, $cmd->{line};
235         }
236     }
237
238     local $" = "\n";
239     print <<EOT;
240 Deprecated commands
241 ===================
242 @commands
243 EOT
244 }
245
246 sub find_documentation
247 {
248     my ($options, $opt, $tex_items, $main_items) = @_;
249
250     my %found_in;
251     my %items = (
252         tex  => $tex_items,
253         main => $main_items,
254     );
255     my $opt_name = $opt->{long_name};
256
257     foreach my $doc_type (qw(tex main)) {
258         my $doc = $items{$doc_type};
259         if ($doc->{$opt_name}
260             || ($opt_name !~ /^no/ && $doc->{"no-$opt_name"})) {
261             $found_in{$doc_type} = true;
262         }
263         else {
264             $found_in{$doc_type} = false;
265         }
266     }
267     if (scalar grep { $_ == false } values %found_in) {
268         push @$options, {
269             name => $opt_name,
270             tex  => $found_in{tex},
271             main => $found_in{main},
272         }
273     }
274 }
275
276 sub emit_undocumented_opts
277 {
278     my ($tex, $main, $opts) = @_;
279
280     my (%tex_items, %main_items);
281     while ($tex =~ /^\@item\w*? \s+? --([-a-z0-9]+)/gmx) {
282         $tex_items{$1} = true;
283     }
284     my ($help) = $main =~ /\n print_help .*? \{\n (.*) \n\} \n/sx;
285     while ($help =~ /--([-a-z0-9]+)/g) {
286         $main_items{$1} = true;
287     }
288
289     my @options;
290     foreach my $opt (@$opts) {
291         next if $opt->{deprecated};
292         find_documentation(\@options, $opt, \%tex_items, \%main_items);
293     }
294
295     my ($opt, $not_found_in);
296
297 format STDOUT_TOP =
298 Undocumented options          Not In:
299 ====================          ==================
300 .
301
302 format STDOUT =
303 @<<<<<<<<<<<<<<<<<<<          @<<<<<<<<<<<<<<<<<
304 $opt->{name},                 $not_found_in
305 .
306     foreach $opt (@options) {
307         $not_found_in = join ' ', (
308             ! $opt->{tex}                  ? 'texinfo' : (),
309             !($opt->{tex} || $opt->{main}) ? 'nor'     : (),
310             ! $opt->{main}                 ? '--help'  : (),
311         );
312         write;
313     }
314 }
315
316 sub emit_undocumented_cmds
317 {
318     my ($tex, $opts, $cmds, $index) = @_;
319
320     my %items;
321     while ($tex =~ /^\@item\w*? \s+? ([_a-z0-9]+) \s+? = \s+? \S+?/gmx) {
322         my $cmd = $1;
323         $cmd =~ tr/_//d;
324         $items{$cmd} = true;
325     }
326     my @commands;
327     foreach my $cmd (@$cmds) {
328         my $cmd_name = do {
329             local $_ = exists $index->{opts}->{$cmd->{name}}
330               ? $opts->[$index->{opts}->{$cmd->{name}}]->{long_name}
331               : $cmd->{name};
332             tr/-/_/;
333             $_;
334         };
335         if (not $items{$cmd->{name}} || $cmd->{deprecated}) {
336             push @commands, $cmd_name;
337         }
338     }
339
340     local $" = "\n";
341     print <<EOT;
342 Undocumented commands
343 =====================
344 @commands
345 EOT
346 }