/*
* fhist - file history and comparison tools
* Copyright (C) 1991-1994, 1998-2000, 2002 Peter Miller;
* All rights reserved.
*
* Derived from a work
* Copyright (C) 1990 David I. Bell.
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
*
* MANIFEST: operating system entry point, and parse arguments
*
* fcomp - a file comparison program
*/
#include <ac/errno.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <arglex.h>
#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <fileio.h>
#include <help.h>
#include <isdir.h>
#include <progname.h>
#include <quit.h>
#include <sub.h>
#include <version.h>
enum
{
arglex_token_binary,
arglex_token_binary_not,
arglex_token_blank,
arglex_token_context,
arglex_token_debug,
arglex_token_edit,
arglex_token_failures,
arglex_token_join,
arglex_token_line_numbers,
arglex_token_matching,
arglex_token_output,
arglex_token_quiet,
arglex_token_spaces,
arglex_token_upcase,
arglex_token_verbose,
arglex_token_what,
};
static arglex_table_ty argtab[] =
{
{ "-BINary", arglex_token_binary, },
{ "-No_BINary", arglex_token_binary_not, },
{ "-Blank", arglex_token_blank, },
{ "-Context", arglex_token_context, },
{ "-Debug", arglex_token_debug, },
{ "-Edit", arglex_token_edit, },
{ "-Failures", arglex_token_failures, },
{ "-Join", arglex_token_join, },
{ "-Number", arglex_token_line_numbers, },
{ "-Matching", arglex_token_matching, },
{ "-Output", arglex_token_output, },
{ "-Quiet", arglex_token_quiet, },
{ "-Spaces", arglex_token_spaces, },
{ "-Upcase", arglex_token_upcase, },
{ "-Verbose", arglex_token_verbose, },
{ "-What", arglex_token_what, },
{ 0, 0, }, /* end marker */
};
/*
* Type out the possible commands.
*/
static void
usage(void)
{
char *progname;
progname = progname_get();
fprintf(stderr, "usage: %s [ <option>... ] <fileA> <fileB>\n", progname);
fprintf(stderr, " %s -Help\n", progname);
fprintf(stderr, " %s -VERSion\n", progname);
quit(1);
}
/*
* give the user some help
*/
static void
main_help(void)
{
help(NULL, usage);
}
/*
* Expand a global variable name.
* Returns original argument if string is not a global variable,
* or returns copied value of global variable.
*/
static char *
expand(char *cp)
{
char *var; /* variable value */
if (!cp || *cp != '.')
return cp;
var = getenv(cp);
if (!var || !*var)
return cp;
cp = r_alloc_and_check(strlen(var) + 1);
strcpy(cp, var);
return cp;
}
/*
* Make a directory name into a file name by applying the last
* component of the second name to it. For example:
* dirfile("/etc", "/foo/bar")
* results in "/etc/bar".
*/
static char *
dirfile(char *dir_name, char *filename)
{
char *cp;
char *endname;
endname = strrchr(filename, '/');
if (!endname)
endname = filename;
cp = r_alloc_and_check(strlen(dir_name) + strlen(endname) + 2);
strcpy(cp, dir_name);
strcat(cp, "/");
strcat(cp, endname);
return cp;
}
/*
* This is the main program.
* This performs all of the parsing of the command line, and then
* calls a subroutine to do all the work. This allows the subroutine
* to be loaded with other programs and called by them easily.
*/
int
main(int argc, char **argv)
{
char *cp;
char *nameA;
char *nameB;
char *ofile;
int dirA;
int dirB;
sub_context_ty *scp;
arglex_init(argc, argv, argtab);
switch (arglex())
{
case arglex_token_help:
main_help();
quit(0);
case arglex_token_version:
version();
quit(0);
default:
break;
}
nameA = NULL;
nameB = NULL;
ofile = NULL;
fc.verbosity = VERBOSE_DEFAULT;
fc.maxchanges = INFINITY;
while (arglex_token != arglex_token_eoln)
{
switch (arglex_token)
{
default:
bad_argument(usage);
/* NOTREACHED */
case arglex_token_string:
/* a file name */
cp = expand(arglex_value.alv_string);
if (!nameA)
nameA = cp;
else if (!nameB)
nameB = cp;
else
{
fatal_intl
(
0,
i18n("only two input files allowed")
);
}
break;
case arglex_token_binary:
/* compare binary bytes */
if (fc.hexify_flag)
goto duplicate;
fc.hexify_flag = 1;
break;
case arglex_token_binary_not:
/* ignore binary files */
if (fc.cant_do_binary)
goto duplicate;
fc.cant_do_binary = 1;
break;
case arglex_token_blank:
/* ignore blank lines */
if (fc.blankflag)
{
duplicate:
scp = sub_context_new();
sub_var_set_charstar(scp, "Name", arglex_value.alv_string);
fatal_intl
(
scp,
i18n("duplicate $name command line option")
);
/* NOTREACHED */
sub_context_delete(scp);
}
fc.blankflag = 1;
break;
case arglex_token_debug:
/* type debugging information */
if (fc.debugflag)
goto duplicate;
fc.debugflag = 1;
break;
case arglex_token_edit:
/* output an edit script */
if (fc.editscriptflag)
goto duplicate;
fc.editscriptflag = 1;
break;
case arglex_token_matching:
/* show lines which match */
if (fc.matchflag)
goto duplicate;
fc.matchflag = 1;
break;
case arglex_token_line_numbers:
/* show line numbers */
if (fc.numflag)
goto duplicate;
fc.numflag = 1;
break;
case arglex_token_quiet:
/* display quick summary */
if (fc.quickflag)
goto duplicate;
fc.quickflag = 1;
break;
case arglex_token_spaces:
/* ignore extra spaces or tabs */
if (fc.spaceflag)
goto duplicate;
fc.spaceflag = 1;
break;
case arglex_token_upcase:
/* translate to upper case */
if (fc.upcaseflag)
goto duplicate;
fc.upcaseflag = 1;
break;
case arglex_token_verbose:
/* be verbose */
if (fc.verbosity != VERBOSE_DEFAULT)
goto duplicate;
fc.verbosity = VERBOSE_FULL;
break;
case arglex_token_what:
/* show what changes are */
if (fc.whatflag)
goto duplicate;
fc.whatflag = 1;
break;
case arglex_token_context:
if (fc.context)
goto duplicate;
if (arglex() != arglex_token_number)
{
scp = sub_context_new();
sub_var_set_charstar(scp, "Name", "-Context");
fatal_intl
(
scp,
i18n("numeric value required for $name option")
);
/* NOTREACHED */
sub_context_delete(scp);
}
fc.context = arglex_value.alv_number;
if (fc.context < 1)
{
scp = sub_context_new();
sub_var_set_format(scp, "Number", "%d", fc.context);
fatal_intl
(
scp,
i18n("context of $number is out of range")
);
/* NOTREACHED */
sub_context_delete(scp);
}
break;
case arglex_token_failures:
if (fc.maxchanges != INFINITY)
goto duplicate;
if (arglex() != arglex_token_number)
{
scp = sub_context_new();
sub_var_set_charstar(scp, "Name", "-Failures");
fatal_intl
(
scp,
i18n("numeric value required for $name option")
);
}
fc.maxchanges = arglex_value.alv_number;
if (fc.maxchanges < 0)
{
scp = sub_context_new();
sub_var_set_format(scp, "Number", "%d", fc.maxchanges);
fatal_intl
(
scp,
i18n("maximum changes of $number is out of range")
);
/* NOTREACHED */
sub_context_delete(scp);
}
break;
case arglex_token_join:
if (fc.maxjoin)
goto duplicate;
if (arglex() != arglex_token_number)
{
scp = sub_context_new();
sub_var_set_charstar(scp, "Name", "-Join");
fatal_intl
(
scp,
i18n("numeric value required for $name option")
);
}
fc.maxjoin = arglex_value.alv_number;
if (fc.maxjoin < 1)
{
scp = sub_context_new();
sub_var_set_format(scp, "Number", "%d", fc.maxjoin);
fatal_intl
(
scp,
i18n("join of $number is out of range")
);
/* NOTREACHED */
sub_context_delete(scp);
}
break;
case arglex_token_output:
/* specify output file */
if (ofile)
goto duplicate;
switch (arglex())
{
default:
fatal_intl(0, i18n("file name required after -Output option"));
case arglex_token_string:
ofile = expand(arglex_value.alv_string);
break;
case arglex_token_stdio:
ofile = "";
break;
}
break;
}
arglex();
}
if (nameA && !*nameA)
nameA = 0;
if (nameB && !*nameB)
nameB = 0;
if (!nameA || !nameB)
error_intl(0, i18n("must have two input files"));
if (ofile && !*ofile)
ofile = 0;
if (fc.matchflag + fc.whatflag + fc.editscriptflag >= 2)
fatal_intl(0, i18n("too many output formats specified"));
/*
* If just one argument is a directory, then modify it by using
* the other argument so as to reference a corresponding file
* within the directory.
*/
dirA = isdir(nameA);
dirB = isdir(nameB);
if (dirA && dirB)
{
scp = sub_context_new();
sub_var_set_charstar(scp, "Name1", nameA);
sub_var_set_charstar(scp, "Name2", nameB);
fatal_intl
(
scp,
i18n("arguments \"$name1\" and \"$name2\" are both directories")
);
/* NOTREACHED */
sub_context_delete(scp);
}
if (dirA)
nameA = dirfile(nameA, nameB);
if (dirB)
nameB = dirfile(nameB, nameA);
/*
* compare the two files
*/
fcomp(nameA, nameB);
/*
* report the difference
*/
if (fc.whatflag)
dumpwhat(ofile);
else if (fc.matchflag)
dumpmatch(ofile);
else if (fc.editscriptflag)
dumplines(ofile);
else
dumpnormal(ofile);
quit(0);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1