/* inventory.c: file inventories
 *
 ****************************************************************
 * Copyright (C) 2002  Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "config-options.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/vu/safe.h"
#include "file-utils/liblarch/inv.h"



static t_uchar * program_name = "inventory";
static t_uchar * usage = "[options] [--] [dir]*";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
				   "\n"
				   "Copyright 2002 Tom Lord\n"
				   "\n"
				   "This is free software; see the source for copying conditions.\n"
				   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
				   "PARTICULAR PURPOSE.\n"
				   "\n"
				   "Report bugs to " cfg__file_utils_bug_mail ".\n"
				   "\n");

#define OPTS(OP, OP2) \
  OP (opt_help_msg, "h", "help", 0, \
      "display help") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "display version info") \
  OP2 (opt_version, 0, 0, 0, "") \
  OP (opt_source, "s", "source", 0, \
      "list source files") \
  OP (opt_precious, "p", "precious", 0, \
      "list precious files") \
  OP (opt_backups, "b", "backups", 0, \
      "list backup files") \
  OP (opt_junk, "j", "junk", 0, \
      "list junk files") \
  OP (opt_unrecognized, "u", "unrecognized", 0, \
      "list unrecognized files") \
  OP (opt_trees, "t", "trees", 0, \
      "list roots of nested trees") \
  OP2 (opt_source, 0, 0, 0, "") \
  OP (opt_report, "r", "report", 0, \
      "print a report") \
  OP2 (opt_report, 0, 0, 0, "") \
  OP (opt_directories, "d", "directories", 0, \
      "list only directories") \
  OP (opt_files, "f", "files", 0, \
      "list only non-directories") \
  OP (opt_both, "B", "both", 0, \
      "list both dirs and files") \
  OP (opt_kind, 0, "kind", 0, \
      "indicate file kinds") \
  OP2 (opt_kind, 0, 0, 0, "") \
  OP (opt_all, 0, "all", 0, \
      "include arch control files") \
  OP (opt_nested, 0, "nested", 0, \
      "include nested trees") \
  OP2 (opt_report, 0, 0, 0, "") \
  OP (opt_tags, 0, "tags", 0, \
      "list with id tags (source files only)") \
  OP (opt_untagged, 0, "untagged", 0, \
      "include files that are missing tags") \
  OP2 (opt_report, 0, 0, 0, "") \
  OP (opt_explicit, 0, "explicit", 0, \
      "use explicit file tags") \
  OP (opt_implicit, 0, "implicit", 0, \
      "permit implicit file tags") \
  OP (opt_names, 0, "names", 0, \
      "use name-based file tags") \
  OP (opt_ctl_dir, 0, "control-dir NAME", 1, \
      "specify the control directory basename") \
  OP (opt_conventions_file, 0, "conventions-file NAME", 1, \
      "specify the naming conventions file basename")
  

static t_uchar long_help[] = ("inventory a source tree\n"
			      "With no arguments, print a human-readable inventory report.\n"
			      "\n"
			      "With -r or --report, category options (--source etc)\n"
			      "limit the report to just those files.  With no other options,\n"
			      "the report includes all sections and files.\n"
			      "\n"
			      "The options -d, -f, and -b cancel each other.\n"
			      "\n"
			      "If a directory is precious, junk, or unrecognized, only the\n"
			      "directory name itself is printed -- its contents are not\n"
			      "searched.\n"
			      "\n"
			      "Each command option implies the corresponding category option\n"
			      "(e.g. \"--source-command\" implies \"--source\").  The exit\n"
			      "status of the command is ignored.\n");

enum options
{
  OPTS (OPT_ENUM, OPT_IGN)  
};

struct opt_desc opts[] = 
{
  OPTS (OPT_DESC, OPT_DESC)
    {-1, 0, 0, 0, 0}
};


/* __STDC__ prototypes for static functions */
static int inventory_printer (int * errn,
			      struct alloc_limits * limits,
			      t_uchar * path,
			      struct stat * stat_buf,
			      enum inv_category category,
			      t_uchar * tag,
			      void * closure);



static int show_dirs = 0;
static int show_files = 1;
static int show_kind = 0;
static int override_method = 0;
static enum ftag_method method_override;
static int show_tags;

static int n_categories = 0;

int
main (int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  struct inv_options inv_options;
  char * explicit_control_dir;
  char * explicit_conventions_file;

  option = 0;
  mem_set0 ((t_uchar *)&inv_options, sizeof (inv_options));
  explicit_control_dir = 0;
  explicit_conventions_file = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv,
			program_name, usage, version_string, long_help,
			opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
	break;
      switch (o)
	{
	default:
	  safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
	  panic ("internal error parsing arguments");

	usage_error:
	  opt_usage (2, argv[0], program_name, usage, 1);
	  exit (1);

	/* bogus_arg: */
	  safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
	  goto usage_error;

	case opt_source:
	  inv_options.categories |= inv_source;
	  break;
	case opt_precious:
	  inv_options.categories |= inv_precious;
	  break;
	case opt_backups:
	  inv_options.categories |= inv_backup;
	  break;
	case opt_junk:
	  inv_options.categories |= inv_junk;
	  break;
	case opt_unrecognized:
	  inv_options.categories |= inv_unrecognized;
	  break;
	case opt_trees:
	  inv_options.categories |= inv_tree;
	  break;

	case opt_directories:
	  show_dirs = 1;
	  show_files = 0;
	  break;
	case opt_files:
	  show_dirs = 0;
	  show_files = 1;
	  break;
	case opt_both:
	  show_dirs = 1;
	  show_files = 1;
	  break;
	case opt_kind:
	  show_kind = 1;
	  break;

	  
	case opt_all:
	  inv_options.include_excluded = 1;
	  break;

	case opt_nested:
	  inv_options.nested = 1;
	  break;

	case opt_tags:
	  inv_options.want_tags = 1;
	  show_tags = 1;
	  break;

	case opt_untagged:
	  inv_options.include_untagged = 1;
	  break;

	case opt_explicit:
	  override_method = 1;
	  method_override = ftag_explicit;
	  break;
	  
	case opt_implicit:
	  override_method = 1;
	  method_override = ftag_implicit;
	  break;
	  
	case opt_names:
	  override_method = 1;
	  method_override = ftag_names;
	  break;

	case opt_ctl_dir:
	  explicit_control_dir = str_save (0, option->arg_string);
	  break;

	case opt_conventions_file:
	  explicit_conventions_file = str_save (0, option->arg_string);
	  break;
	}
    }

  n_categories = 0;
  if (inv_options.categories & inv_source)
    ++n_categories;

  if (inv_options.categories & inv_precious)
    ++n_categories;

  if (inv_options.categories & inv_backup)
    ++n_categories;

  if (inv_options.categories & inv_junk)
    ++n_categories;

  if (inv_options.categories & inv_tree)
    ++n_categories;

  if (inv_options.categories & inv_unrecognized)
    ++n_categories;

  if (!n_categories)
    {
      n_categories = 6;
      inv_options.categories = inv_source | inv_precious | inv_backup | inv_tree | inv_unrecognized;
    }

  if (argc == 1)
    {
      argv[1] = ".";
      ++argc;
    }

  {
    int x;
    int errn;
    int re_error;

    for (x = 1; x < argc; ++x)
      {
	char * bad_file;
	errn = 0;
	re_error = 0;


	if (!inv_options.control_dir_name)
	  {
	    int errn;

	    if (explicit_control_dir || explicit_conventions_file)
	      {
		inv_options.control_dir_name = explicit_control_dir;
		inv_options.conventions_file_name = explicit_conventions_file;
	      }
	    else if (0 > inv_guess_conventions_files (&errn, 0, &inv_options, argv[x]))
	      {
		safe_printfmt (2, "\n");
		safe_printfmt (2, "inventory: error while guessing conventions file name\n");
		safe_printfmt (2, "  error: %s (%d)\n", errno_to_string (errn), errn);
		safe_printfmt (2, "\n");
		exit (2);
	      }
      
	  }

	if (0 > inv_get_naming_conventions (&errn, &re_error, 0, &inv_options, argv[x]))
	  {
	    safe_printfmt (2, "\n");
	    safe_printfmt (2, "inventory: error while computing naming conventions\n");
	    if (errn)
	      safe_printfmt (2, "  %s\n", errno_to_string (errn));
	    if (re_error)
	      safe_printfmt (2, "  bogus regular expression (not to mention poor error reporting in \"inventory\")\n");
	    safe_printfmt (2, "\n");
	    exit (2);
	  }

	if (override_method)
	  inv_options.method = method_override;

	bad_file = 0;
	if (0 > inv_traversal (&errn, &bad_file, 0, &inv_options, argv[x], inventory_printer, 0))
	  {
	    safe_printfmt (2, "\n");
	    safe_printfmt (2, "inventory: error while computing inventory\n");
	    if (bad_file)
	      safe_printfmt (2, "  file: %s\n", bad_file);
	    safe_printfmt (2, "  %s\n", errno_to_string (errn));
	    safe_printfmt (2, "\n");
	    exit (2);
	  }

	inv_free_naming_conventions (&inv_options);
      }
  }
  return 0;
}



static int
inventory_printer (int * errn,
		   struct alloc_limits * limits,
		   t_uchar * path,
		   struct stat * stat_buf,
		   enum inv_category category,
		   t_uchar * tag,
		   void * closure)
{
  if ((category != inv_tree) && !show_dirs && S_ISDIR (stat_buf->st_mode))
    return 0;

  if (!show_files && !S_ISDIR (stat_buf->st_mode))
    return 0;

  if (n_categories > 1)
    {
      switch (category)
	{
	case inv_source:
	  safe_printfmt (1, "S ");
	  break;
	case inv_precious:
	  safe_printfmt (1, "P ");
	  break;
	case inv_backup:
	  safe_printfmt (1, "B ");
	  break;
	case inv_junk:
	  safe_printfmt (1, "J ");
	  break;
	case inv_tree:
	  safe_printfmt (1, "T ");
	  break;
	case inv_unrecognized:
	  safe_printfmt (1, "U ");
	  break;
	}
    }

  if (show_kind)
    {
      if (S_ISDIR (stat_buf->st_mode))
	safe_printfmt (1, "d ");
      else if (S_ISLNK (stat_buf->st_mode))
	safe_printfmt (1, "> ");
      else 
	safe_printfmt (1, "r ");
    }

  safe_printfmt (1, "%s%s%s\n", path, (show_tags ? "\t" : ""), (show_tags ? (tag ? (char *)tag : "???") : ""));

  return 0;
}

/* tag: Tom Lord Thu Feb 21 15:44:53 2002 (inventory.c)
 */
