#!/usr/bin/env perl
# Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015
# 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 <http://www.gnu.org/licenses/>.
#
# Original author: Karl Berry.
#
# Kludge of a script to check command lists in refcard vs. appendix vs.
# tp for consistency.

use strict;

my $srcdir = $ENV{'srcdir'};
$srcdir = '.' if (!defined($srcdir));

exit (&main ());

sub main {
  my $no_common = $ARGV[0] eq "--no-common";

  my %card_cmds = &read_refcard ("$srcdir/txirefcard.tex");
  my %idx_cmds = &read_refidx ("$srcdir/../texinfo.texi");
  my %man_cmds = &read_appendix ("$srcdir/../texinfo.texi");
  my %tp_cmds = &read_tp ("$srcdir/../../util/txicmdlist");

  # find the commands that are covered everywhere.
  my @found = ();
  for my $cc (keys %card_cmds) {
    if (exists $idx_cmds{$cc}
        && exists $man_cmds{$cc}
        && exists $tp_cmds{$cc}) {
      push (@found, $cc);
      delete $card_cmds{$cc};
      delete $idx_cmds{$cc};
      delete $man_cmds{$cc};
      delete $tp_cmds{$cc};
    }
  }

  printf ("    common %d: @{[sort @found]}\n", scalar(@found))
    unless $no_common;

  # there are numerous @findex entries which are not @-commands, which
  # can be seen this way:
  #my @findex_only = sort(keys(%idx_cmds));
  #printf "findex  only %d: ".join('|',@findex_only)."\n", scalar(@findex_only);
  #
  # let's not report those, but we do want to report normal commands that
  # did not have findex entries: those which are present in all the
  # other lists.
  my @idx_missing = ();
  for my $cc (sort keys %card_cmds) {
    if (exists $man_cmds{$cc} && exists $tp_cmds{$cc}) {
      push (@idx_missing, $cc);
      delete $card_cmds{$cc};
      delete $man_cmds{$cc};
      delete $tp_cmds{$cc};
    }
  }
  printf "findex missing %d: @idx_missing\n", scalar(@idx_missing)
     if (scalar(@idx_missing));

  # now report on commands only in some other subset.
  my @card_only = sort(keys(%card_cmds));
  printf "refcard  only %d: @card_only\n", scalar(@card_only);

  my @man_only = sort(keys(%man_cmds));
  printf "appendix only %d: @man_only\n", scalar(@man_only);

  my @tp_only = sort(keys(%tp_cmds));
  printf "tp       only %d: @tp_only\n", scalar(@tp_only);

  return scalar(@card_only) + scalar(@man_only) + scalar(@tp_only);
}


# Return command names from the reference card as the keys of a hash
# (with empty values).  In principle it's a list, but as a practical
# matter we want to work with a hash anyway, so we might as well return
# it that way in the first place.  (Ditto for the other functions.)
#
sub read_refcard {
  my ($fname) = @_;
  my @ret = ();

  local *FILE;
  my $FILE = $fname;
  open (FILE, $FILE) || die "open($FILE) failed: $!";
  while (<FILE>) {
    next unless /^\\txicmd/;
    chomp;
    my $xcmd = 0;
    s/\\txicmdarg\{.*?\}\}?//; # first get rid of the arguments
    s/\}\{.*//;                # then the descriptions
    s/^\\txicmdx\{// && ($xcmd = 1);  # used for the @def...x
    s/^\\txicmd\{//;           # finally the markup cmd itself
    s/\\ttbraced\{\}//g;       # quote cmd list

    my (@cmds) = split (/,? +/, $_);  # occasionally we combine cmds

    # we typeset these specially in TeX.
    if ("@cmds" eq "@#1footing") {
      @cmds = ('@oddfooting', '@evenfooting', '@everyfooting');
    } elsif ("@cmds" eq "@#1heading") {
      @cmds = ('@oddheading', '@evenheading', '@everyheading');
    }

    # add each command from this line to the return.
    for my $c (@cmds) {
      #warn "refcard $c\n";
      #warn "refcard $c{x}\n" if $xcmd;
      next if $c eq "txicommandconditionals"; # variable not separate in manual
      if ($c eq '@\tildechar') { # TeX specialties, forcibly make them match
        $c = '@~';
      } elsif ($c eq '@\var{whitespace}') {
        $c = '@var{whitespace}';
      }
      $c = '@~' if $c eq '@\tildechar';  # TeX
      $c = '@\\' if $c eq '@\bschar';  # TeX
      $c = '@{' if $c eq '@\lbracechar';  # TeX
      $c = '@}' if $c eq '@\rbracechar';  # TeX
      $c = '@&' if $c eq '@\&';  # TeX
      push (@ret, $c);
      push (@ret, "${c}x") if $xcmd;
    }
  }
  push (@ret, '@end', '@uref', '@appendixsection');  # described in text
  push (@ret, '@,');  # our non-parsing above lost these
  push (@ret, qw(@atchar @ampchar @lbracechar @rbracechar @backslashchar));
  close (FILE) || warn "close($FILE) failed: $!";

  my %ret; @ret{@ret} = ();
  return %ret;
}


# Return command names from @findex entries in the reference manual as
# the keys of a hash (empty values).
#
sub read_refidx {
  my ($fname) = @_;
  my %ret = ();

  local *FILE;
  my $FILE = $fname;
  open (FILE, $FILE) || die "open($FILE) failed: $!";
  while (<FILE>) {
    next unless s/^\@findex\s+//; # only consider @findex lines
    chomp;
    s/\s+\@r.*$//;# if /^[^a-zA-Z]/;        # remove comment
    s/\@\{\@\}//;         # remove @{@} used in atchar, etc.
    s/<colon>/:/;         # @:
    s/<newline>/var{whitespace}/;  # special generic entry: @var{whitespace}
    s/\s*\@sortas{[^}]+}\s*//;  # remove @sortas
    s/^/\@/ unless /^\@/; # prepend @ unless already there (@@ @{ @})
    $ret{$_} = 1;
  }
  close (FILE) || warn "close($FILE) failed: $!";

  return %ret;
}


# Return command names from the @-Command List node in the reference
# manual as the keys of a hash (empty values).
#
sub read_appendix {
  my ($fname) = @_;
  my %ret = ();

  local *FILE;
  my $FILE = $fname;
  open (FILE, $FILE) || die "open($FILE) failed: $!";
  while (<FILE>) {
    last if /^\@section \@\@-Command List/;  # ignore until right section
  }
  while (<FILE>) {
    last if /^\@end table/; # ignore again after the summary
    next unless s/^\@itemx? *\@//;  # only want item[x]s in the table
    chomp;
    s/\@\{.+//;  # remove braced arguments (but not @{)
    s/ .*//;     # remove arguments following a space
    s/\@\@/@/g;  # @@ -> @
    next if $_ =~ /^\@(br|ctrl)$/;  # @ignore-d in text
    $ret{$_} = 1;
  }
  $ret{'@{'} = 1; # our non-parsing above fails on this one
  close (FILE) || warn "close($FILE) failed: $!";

  return %ret;
}


# Return command names implemented in the general parser as the keys of
# a hash (empty values).  The argument is the command to run to return
# the list.
#
sub read_tp {
  my ($prog) = @_;
  my %ret = ();

  local *FILE;
  my $FILE = "$prog |";
  open (FILE, $FILE) || die "open($FILE) failed: $!";
  while (<FILE>) {
    chomp;
    # excise @<whitespace> commands from normal list.
    next if $_ eq '@ ' || $_ eq "\@\t" || $_ eq "" || $_ eq '@';

    # obsolete and/or subsidiary commands we don't want to document as usual.
    next if $_ =~ /columnfractions
                   |(even|every|odd)(foot|head)ingmarks
                   |rmacro
                   |refill
                   |item_LINE
                   |^\@..index
                   |\|
                  /x;
    $ret{$_} = 1;
  }
  close (FILE) || warn "close($FILE) failed: $!";

  $ret{'@var{whitespace}'} = 1;

  return %ret;
}
