]> sjero.net Git - wget/blob - util/paramcheck.pl
Enhance paramcheck script to recognize undocumented options/commands.
[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 @args = ([
27     File::Spec->catfile($Bin, File::Spec->updir, 'src', 'main.c'),
28     qr/static \s+? struct \s+? cmdline_option \s+? option_data\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
29     [ qw(long_name short_name type data argtype) ],
30 ], [
31     File::Spec->catfile($Bin, File::Spec->updir, 'src', 'init.c'),
32     qr/commands\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
33     [ qw(name place action) ],
34 ]);
35 my $tex_file = File::Spec->catfile($Bin, File::Spec->updir, 'doc', 'wget.texi');
36
37 {
38     my @data;
39
40     foreach my $arg (@args) {
41         my ($file, $regex, $names) = @$arg;
42         my $source = read_file($file);
43         my @chunks = extract_chunks($source, $regex);
44         push @data, extract_entries(\@chunks, $names);
45     }
46
47     output_results(@data);
48 }
49
50 sub read_file
51 {
52     my ($file) = @_;
53
54     open(my $fh, '<', $file) or die "Cannot open $file: $!";
55
56     return do { local $/; <$fh> };
57 }
58
59 sub extract_chunks
60 {
61     my ($source, $regex) = @_;
62
63     my ($raw_data) = $source =~ $regex;
64
65     return map { /\S/ && !/^\#/ ? $_ : () } split /(?<=\})/, $raw_data;
66 }
67
68 sub extract_entries
69 {
70     my ($chunks, $names) = @_;
71
72     my (@entries, %index, $i);
73
74     foreach my $chunk (@$chunks) {
75         my ($args) = $chunk =~ /\{ \s+? (.*?) \s+? \}/sx;
76         next unless defined $args;
77
78         my @args = map {
79           tr/'"//d; $_
80         } map {
81           /\((.*?)\)/ ? $1 : $_
82         } split /\,\s+/, $args;
83
84         my $entry = { map { $_ => shift @args } @$names };
85
86         ($entry->{line}) = $chunk =~ /^ \s+? (\{.*)/mx;
87         if ($chunk =~ /deprecated/i) {
88             $entries[-1]->{deprecated} = true;
89         }
90
91         my $index_name = exists $entry->{data}
92           ? $entry->{data}
93           : $entry->{name};
94
95         $index{$index_name} = $i++;
96
97         push @entries, $entry;
98     }
99
100     push @entries, \%index;
101
102     return \@entries;
103 }
104
105 sub output_results
106 {
107     my ($opts, $cmds) = @_;
108
109     my %index = (
110         opts => pop @$opts,
111         cmds => pop @$cmds,
112     );
113
114     emit_no_corresponding_cmds($opts);
115     print "\n";
116     emit_no_matching_long_cmds($opts);
117     print "\n";
118     emit_no_corresponding_opts($opts, $cmds);
119     print "\n";
120     emit_deprecated_opts($opts);
121     print "\n";
122     emit_deprecated_cmds($cmds);
123     print "\n";
124
125     my $tex = read_file($tex_file);
126
127     emit_undocumented_opts($tex, $opts);
128     print "\n";
129     emit_undocumented_cmds($tex, $opts, $cmds, \%index);
130     print "\n";
131 }
132
133 sub emit_no_corresponding_cmds
134 {
135     my ($opts) = @_;
136
137     my @options;
138     foreach my $opt (@$opts) {
139         unless ($opt->{type} =~ /^(?:OPT_BOOLEAN|OPT_VALUE)$/
140              && $opt->{argtype} == -1)
141         {
142             push @options, $opt->{line};
143         }
144     }
145
146     local $" = "\n";
147     print <<EOT;
148 No corresponding commands
149 =========================
150 @options
151 EOT
152 }
153
154 sub emit_no_matching_long_cmds
155 {
156     my ($opts) = @_;
157
158     my @options;
159     foreach my $opt (@$opts) {
160         my $long_name = $opt->{long_name};
161         $long_name =~ tr/-//d;
162         unless ($long_name eq $opt->{data}) {
163             push @options, $opt->{line};
164         }
165     }
166
167     local $" = "\n";
168     print <<EOT;
169 Non-matching commands
170 =====================
171 @options
172 EOT
173 }
174
175 sub emit_no_corresponding_opts
176 {
177     my ($opts, $cmds) = @_;
178
179     my @commands;
180     foreach my $cmd (@$cmds) {
181         my $found = false;
182         foreach my $opt (@$opts) {
183             my $long_name = $opt->{long_name};
184             $long_name =~ tr/-//d;
185             if ($cmd->{name} eq $opt->{data}
186              || $cmd->{name} eq $long_name) {
187                 $found = true;
188                 last;
189             }
190         }
191         unless ($found) {
192             push @commands, $cmd->{line};
193         }
194     }
195
196     local $" = "\n";
197     print <<EOT;
198 No corresponding options
199 ========================
200 @commands
201 EOT
202 }
203
204 sub emit_deprecated_opts
205 {
206     my ($opts) = @_;
207
208     my @options;
209     foreach my $opt (@$opts) {
210         if ($opt->{deprecated}) {
211             push @options, $opt->{line};
212         }
213     }
214
215     local $" = "\n";
216     print <<EOT;
217 Deprecated options
218 ==================
219 @options
220 EOT
221 }
222
223 sub emit_deprecated_cmds
224 {
225     my ($cmds) = @_;
226
227     my @commands;
228     foreach my $cmd (@$cmds) {
229         if ($cmd->{deprecated}) {
230             push @commands, $cmd->{line};
231         }
232     }
233
234     local $" = "\n";
235     print <<EOT;
236 Deprecated commands
237 ===================
238 @commands
239 EOT
240 }
241
242 sub emit_undocumented_opts
243 {
244     my ($tex, $opts) = @_;
245
246     my %items;
247     while ($tex =~ /^\@item\w*? \s+? --([\w\-]+)/gmx) {
248         my $opt = $1;
249         $items{$opt} = true;
250     }
251     my @options;
252     foreach my $opt (@$opts) {
253         if (not $items{$opt->{long_name}} || $opt->{deprecated}) {
254             push @options, $opt->{long_name};
255         }
256     }
257
258     local $" = "\n";
259     print <<EOT;
260 Undocumented options
261 ====================
262 @options
263 EOT
264 }
265
266 sub emit_undocumented_cmds
267 {
268     my ($tex, $opts, $cmds, $index) = @_;
269
270     my %items;
271     while ($tex =~ /^\@item\w*? \s+? ([\w\_]+) \s+? = \s+? \S+?/gmx) {
272         my $cmd = $1;
273         $cmd =~ tr/_//d;
274         $items{$cmd} = true;
275     }
276     my @commands;
277     foreach my $cmd (@$cmds) {
278         my $cmd_name = do {
279             local $_ = exists $index->{opts}->{$cmd->{name}}
280               ? $opts->[$index->{opts}->{$cmd->{name}}]->{long_name}
281               : $cmd->{name};
282             tr/-/_/;
283             $_;
284         };
285         if (not $items{$cmd->{name}} || $cmd->{deprecated}) {
286             push @commands, $cmd_name;
287         }
288     }
289
290     local $" = "\n";
291     print <<EOT;
292 Undocumented commands
293 =====================
294 @commands
295 EOT
296 }