#!/usr/bin/env perl
# Copyright (C) 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
use strict;
use warnings;
use constant true => 1;
use constant false => 0;
use FindBin qw($Bin);
use File::Spec ();
my $main_c_file = File::Spec->catfile($Bin, File::Spec->updir, 'src', 'main.c');
my $init_c_file = File::Spec->catfile($Bin, File::Spec->updir, 'src', 'init.c');
my $tex_file = File::Spec->catfile($Bin, File::Spec->updir, 'doc', 'wget.texi');
my $main_content = read_file($main_c_file);
my $init_content = read_file($init_c_file);
my $tex_content = read_file($tex_file);
my @args = ([
$main_content,
qr/static \s+? struct \s+? cmdline_option \s+? option_data\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
[ qw(long_name short_name type data argtype) ],
], [
$init_content,
qr/commands\[\] \s+? = \s+? \{ (.*?) \}\;/sx,
[ qw(name place action) ],
]);
{
my @data;
foreach my $arg (@args) {
my ($source, $regex, $names) = @$arg;
my @chunks = extract_chunks($source, $regex);
push @data, extract_entries(\@chunks, $names);
}
output_results(@data);
}
sub read_file
{
my ($file) = @_;
open(my $fh, '<', $file) or die "Cannot open $file: $!";
return do { local $/; <$fh> };
}
sub extract_chunks
{
my ($source, $regex) = @_;
my ($raw_data) = $source =~ $regex;
return map { /\S/ && !/^\#/ ? $_ : () } split /(?<=\})/, $raw_data;
}
sub extract_entries
{
my ($chunks, $names) = @_;
my (@entries, %index, $i);
foreach my $chunk (@$chunks) {
my ($args) = $chunk =~ /\{ \s+? (.*?) \s+? \}/sx;
next unless defined $args;
my @args = map {
tr/'"//d; $_
} map {
/\((.*?)\)/ ? $1 : $_
} split /\,\s+/, $args;
my $entry = { map { $_ => shift @args } @$names };
($entry->{line}) = $chunk =~ /^ \s+? (\{.*)/mx;
if ($chunk =~ /deprecated/i) {
$entries[-1]->{deprecated} = true;
}
my $index_name = exists $entry->{data}
? $entry->{data}
: $entry->{name};
$index{$index_name} = $i++;
push @entries, $entry;
}
push @entries, \%index;
return \@entries;
}
sub output_results
{
my ($opts, $cmds) = @_;
my %index = (
opts => pop @$opts,
cmds => pop @$cmds,
);
emit_no_corresponding_cmds($opts);
print "\n";
emit_no_matching_long_cmds($opts);
print "\n";
emit_no_corresponding_opts($opts, $cmds);
print "\n";
emit_deprecated_opts($opts);
print "\n";
emit_deprecated_cmds($cmds);
print "\n";
emit_undocumented_opts($tex_content, $main_content, $opts);
print "\n";
emit_undocumented_cmds($tex_content, $opts, $cmds, \%index);
print "\n";
}
sub emit_no_corresponding_cmds
{
my ($opts) = @_;
my @options;
foreach my $opt (@$opts) {
unless ($opt->{type} =~ /^(?:OPT_BOOLEAN|OPT_VALUE)$/
&& $opt->{argtype} == -1)
{
push @options, $opt->{line};
}
}
local $" = "\n";
print <{long_name};
$long_name =~ tr/-//d;
unless ($long_name eq $opt->{data}) {
push @options, $opt->{line};
}
}
local $" = "\n";
print <{long_name};
$long_name =~ tr/-//d;
if ($cmd->{name} eq $opt->{data}
|| $cmd->{name} eq $long_name) {
$found = true;
last;
}
}
unless ($found) {
push @commands, $cmd->{line};
}
}
local $" = "\n";
print <{deprecated}) {
push @options, $opt->{line};
}
}
local $" = "\n";
print <{deprecated}) {
push @commands, $cmd->{line};
}
}
local $" = "\n";
print < $tex_items,
main => $main_items,
);
my $opt_name = $opt->{long_name};
foreach my $doc_type (qw(tex main)) {
my $doc = $items{$doc_type};
if ($doc->{$opt_name}
|| ($opt_name !~ /^no/ && $doc->{"no-$opt_name"})) {
$found_in{$doc_type} = true;
}
else {
$found_in{$doc_type} = false;
}
}
if (scalar grep { $_ == false } values %found_in) {
push @$options, {
name => $opt_name,
tex => $found_in{tex},
main => $found_in{main},
}
}
}
sub emit_undocumented_opts
{
my ($tex, $main, $opts) = @_;
my (%tex_items, %main_items);
while ($tex =~ /^\@item\w*? \s+? --([-a-z0-9]+)/gmx) {
$tex_items{$1} = true;
}
my ($help) = $main =~ /\n print_help .*? \{\n (.*) \n\} \n/sx;
while ($help =~ /--([-a-z0-9]+)/g) {
$main_items{$1} = true;
}
my @options;
foreach my $opt (@$opts) {
next if $opt->{deprecated};
find_documentation(\@options, $opt, \%tex_items, \%main_items);
}
my ($opt, $not_found_in);
format STDOUT_TOP =
Undocumented options Not In:
==================== ==================
.
format STDOUT =
@<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<
$opt->{name}, $not_found_in
.
foreach $opt (@options) {
$not_found_in = join ' ', (
! $opt->{tex} ? 'texinfo' : (),
!($opt->{tex} || $opt->{main}) ? 'nor' : (),
! $opt->{main} ? '--help' : (),
);
write;
}
}
sub emit_undocumented_cmds
{
my ($tex, $opts, $cmds, $index) = @_;
my %items;
while ($tex =~ /^\@item\w*? \s+? ([_a-z0-9]+) \s+? = \s+? \S+?/gmx) {
my $cmd = $1;
$cmd =~ tr/_//d;
$items{$cmd} = true;
}
my @commands;
foreach my $cmd (@$cmds) {
my $cmd_name = do {
local $_ = exists $index->{opts}->{$cmd->{name}}
? $opts->[$index->{opts}->{$cmd->{name}}]->{long_name}
: $cmd->{name};
tr/-/_/;
$_;
};
if (not $items{$cmd->{name}} || $cmd->{deprecated}) {
push @commands, $cmd_name;
}
}
local $" = "\n";
print <