
/* argp.c */

#define DESC_ONE_LINE    \
  "look .debs up in the Debian Ramification"
#define DOC_TAIL         \
  "Each RAMIFICATION on the command line is a "                        \
  N_DIG_STR "-digit number.  "                                         \
  "If none is given, 0000 is assumed "                                 \
  "and a table of the principal ramification sections is printed."     \
  "\n\n"                                                               \
  "If this help list scrolls up off the screen "                       \
  "and you cannot see it all, "                                        \
  "try \"" PROG " --help | more\".  "                                  \
  "The list omits a few seldom-used options; "                         \
  "enter \"man 1 " PROG "\" for complete documentation."
#define ARGS_DOC         "RAMIFICATION..."
#define OPT_DATA_FILE    1
#define OPT_DATA_FILE_GZ 2
// #define BUG_EMAIL "thb@debian.org"

/* This file provides the function parse_cl() to parse and validate the
 * command line the user enters to start the program.  The parse_cl()
 * stores its parse in `opt' (see `argp.h').
 *
 */

#include "argp.h"
#include "alloc.h"
#include "gen.h"
#include <argp.h>

struct options opt = {0}        ;
char           zero_ram[N_DIG+1];

const char *argp_program_version = PROG " " VERSION;
#ifdef BUG_EMAIL
  const char *argp_program_bug_address = "<" BUG_EMAIL ">";
#endif

static const struct argp_option option[] = {

  { 0, 0, 0, 0, "Basic output formatting:"                    , 0x110 },
  {
    "wide"          , 'w', 0, 0,
    "Output in " TTY_WIDTH2_STR
    "-column format (this is recommended\n"
    "  if your terminal is sufficiently wide)"          ,
    0x111
  },
  {
    "color"         , 'c', 0, 0,
    "Color-code the output (recommended)"               ,
    0x111
  },

  { 0, 0, 0, 0, "Other general options:"                      , 0x120 },
  {
    "expand-xref"   , 'x', 0, 0,
    "Print cross-references in long form"               ,
    0x121
  },
  {
    "plan"          , 'p', 0, 0,
    "Print the ramification plan or table of conts"     ,
    0x121
  },
  {
    "recursive"     , 'r', 0, 0,
    "Print the entire tree under the given ram"         ,
    0x121
  },
  {
    "trunk"         , 't', 0, 0,
    "Print the trunk above the given ram"               ,
    0x121
  },
  {
    "recursive-up"  , 't', 0, OPTION_ALIAS,
    0                                                   ,
    0x121
  },
  {
    "maint"         , 'm', "MAINTAINER", 0,
    "Print all MAINTAINER's .debs"                      ,
    0x121
  },
  {
    "find-deb"      , 'd', "DEB"       , 0,
    "Find package DEB"                                  ,
    0x121
  },

  { 0, 0, 0, 0, "Selective output suppression:"               , 0x130 },
  {
    "no-end-level"  , 'A', 0, 0,
    "Omit package tables"                               ,
    0x131
  },
  {
    "only-end-level", 'B', 0, 0,
    "Omit subramification tables"                       ,
    0x131
  },
  {
    "no-title"      , 'T', 0, 0,
    "Omit ramification titles"                          ,
    0x131
  },
  {
    "no-xref"       , 'X', 0, 0,
    "Omit cross-references"                             ,
    0x131
  },
  {
    "no-maint"      , 'M', 0, 0,
    "Omit names of package maintainers"                 ,
    0x135
  },
  {
    "no-pri"        , 'P', 0, 0,
    "Omit package priorities"                           ,
    0x135
  },
  {
    "no-desc"       , 'D', 0, 0,
    "Omit package descriptions"                         ,
    0x135
  },
  {
    "no-count"      , 'N', 0, 0,
    "Omit per-ramification .deb counts"                 ,
    0x135
  },
  {
    "names-only"    , '1', 0, 0,
    "Print package names only"                          ,
    0x13d
  },

  { 0, 0, 0, 0, "Manual character-encoding selection:"  , 0x140 },
  {
    "ascii"         , 'L', 0, 0,
    "Output and accept arguments in ascii"              ,
    0x141
  },
  {
    "no-latin1"     , 'L', 0, OPTION_ALIAS,
    0                                                   ,
    0x141
  },
  {
    "utf8"          , 'u', 0, 0,
    "Output and accept arguments in utf-8"              ,
    0x141
  },
  {
    "latin1"        , 'l', 0, 0,
    "Output and accept arguments in Latin-1"            ,
    0x141
  },

  { 0, 0, 0, 0, "Other options:"                              , 0x150 },
  {
    "selections",     's', 0, 0,
    "Print only packages named on stdin (see dpkg(8))"  ,
    0x151
  },
  {
    "ascii-dots",     '.', 0, 0,
    "In the output, fill blanks with `.'"               ,
    0x151
  },

  // { 0, 0, 0, OPTION_HIDDEN, "Seldom used options:"            , 0x200 },
  {
    "pri-one-color" , 'j', 0, OPTION_HIDDEN,
    "Do not differentiate priorities by color"          ,
    0x211
  },
  {
    "data-file"     , OPT_DATA_FILE   , "FILE", OPTION_HIDDEN,
    "Substitute FILE for " DATA_FILE                    ,
    0x211
  },
  {
    "data-file-gz"  , OPT_DATA_FILE_GZ, "FILE", OPTION_HIDDEN,
    "Substitute FILE for " DATA_FILE_GZ                 ,
    0x211
  },

  { 0, 0, 0, 0, "Metaoptions:"                                , 0xd00 },
  { 0 }

};

static error_t parse_opt(
  int key, char *arg, struct argp_state *state
) {

  enum type_name_cl type_name_cl = 0;
  int               gz           = 0;

  switch (key) {

    case 'w': opt.wide          = 1; break;
    case 'L': opt.no_latin1     = 1; break;
    case 'u': opt.utf8          = 1; break;
    case 'l': opt.latin1        = 1; break;
    case 'x': opt.expand_xref   = 1; break;
    case 'p': opt.plan          = 1; break;
    case 'r': opt.recursive     = 1; break;
    case 't': opt.recursive_up  = 1; break;
    case 'A': opt.no_deb        = 1; break;
    case 'B': opt.no_subram     = 1; break;
    case 'T': opt.no_title      = 1;
    case 'X': opt.no_xref       = 1; break;
    case 'M': opt.no_maint      = 1; break;
    case 'P': opt.no_pri        = 1; break;
    case 'D': opt.no_desc       = 1; break;
    case 'N': opt.no_count      = 1; break;
    case 's': opt.selections    = 1; break;
    case 'c': opt.color         = 1; break;
    case 'j': opt.pri_one_color = 1; break;
    case '.': opt.ascii_dots    = 1; break;

    case '1':
        opt.no_subram           = 1;
        opt.no_title            = 1;
        opt.no_xref             = 1;
        opt.no_maint            = 1;
        opt.no_pri              = 1;
        opt.no_desc             = 1;
        opt.no_count            = 1;
        break;

    /* (the following two OPT_DATA_FILE cases are seldom used;
     * they substitute an alternate for the library data file) */
    case OPT_DATA_FILE_GZ: gz = 1;
    case OPT_DATA_FILE   :
    {
      char **data_file = gz ? &opt.data_file_gz : &opt.data_file;
      if ( *data_file ) argp_error(
        state, "ambiguous data-file designation"
      );
      *data_file = strcpy( malloc2( strlen(arg)+1 ), arg );
    } break;

    case 'm': if ( !type_name_cl ) type_name_cl = NAME_MAINT;
    case 'd': if ( !type_name_cl ) type_name_cl = NAME_DEB  ;
    {
      /* maintainer or deb name */
      struct name_cl **name_cl_first =
        type_name_cl == NAME_MAINT ? &opt.maint_cl_first :
        type_name_cl == NAME_DEB   ? &opt.deb_cl_first   : 0;
      struct name_cl *name      = calloc2( 1, sizeof(struct name_cl) );
      {
        struct name_cl *name_last = *name_cl_first;
        while ( name_last && name_last->next )
          name_last = name_last->next;
        if ( name_last ) name_last->next = name;
        else            *name_cl_first   = name;
      }
      name->name = strcpy( malloc2( strlen(arg)+1 ), arg );
    } break;

    case ARGP_KEY_ARG: {
      /* this is a ramno arg not an opt: add it to the ramno list */
      struct ram_cl *ram = calloc2( 1, sizeof(struct ram_cl) );
      {
        struct ram_cl *ram_last = opt.ram_cl_first;
        while ( ram_last && ram_last->next )
          ram_last = ram_last->next;
        if ( ram_last ) ram_last->next   = ram;
        else            opt.ram_cl_first = ram;
      }
      {
        int error = 0;
        if ( strnlen( arg, N_DIG+1 ) > N_DIG ) error = 1;
        else {
          int         i = N_DIG   ;
          const char *s = arg     ;
          char       *d = ram->ram;
          while ( *s ) {
            if ( !isdigit(*s) ) error = 1;
            *d++ = *s++;
            --i;
          }
          for ( ; i; --i ) *d++ = '0' ;
          *d                    = '\0';
        }
        if ( error ) argp_error(
          state, "\"%s\" is no ram number", arg
        );
      }
    } break;

    case ARGP_KEY_END: {

      int need_ram_cl = 1;

      /* Forbid conflicting options.  */
      if (
        opt.plan && (
          opt.deb_cl_first   ||
          opt.maint_cl_first ||
          opt.no_deb         ||
          opt.no_subram      ||
          opt.no_title       ||
          opt.no_xref        ||
          opt.no_maint       ||
          opt.no_pri         ||
          opt.no_desc        ||
          opt.no_count       ||
          opt.selections
        )
      ) argp_error( state, "-p conflicts with -dmABTXDMNPs" );
      if ( opt.deb_cl_first || opt.maint_cl_first ) {
        need_ram_cl = 0;
        if ( opt.plan || opt.no_deb )
          argp_error( state, "-dm conflict with -pA" );
      }
      if (
        opt.no_title
        && !opt.no_deb
        && !opt.no_subram
        && !opt.deb_cl_first
        && !opt.maint_cl_first
      ) argp_error( state, "-T requires at least one of -dmAB" );
      /* If the user has given no ramno, default to ram 0000.  */
      if ( need_ram_cl && !opt.ram_cl_first ) {
        opt.ram_cl_first = calloc2( 1, sizeof(struct ram_cl) );
        strncpy( opt.ram_cl_first->ram, zero_ram, N_DIG+1 );
      }
      if ( opt.utf8 && opt.latin1 )
        argp_error( state, "-u conflicts with -l" );

      if ( opt.no_latin1 ) {
        opt.utf8   = 0;
        opt.latin1 = 0;
      }

      if (
        (
          ( opt.deb_cl_first || opt.maint_cl_first )
          && opt.ram_cl_first
        ) || ( opt.plan && !opt.recursive_up )
      ) opt.recursive = 1;

    } break;

    default: return ARGP_ERR_UNKNOWN;

  }

  return 0;

}

static const struct argp argp = {
  option,
  parse_opt,
  ARGS_DOC,
  PROG " -- " DESC_ONE_LINE "\v" DOC_TAIL
};

void parse_cl( int argc, char **argv ) {

  {
    /* Initialize zero_ram; */
    int i;
    for ( i = 0; i < N_DIG; ++i ) zero_ram[i] = '0' ;
    zero_ram[i]                               = '\0';
  }

  if ( argp_parse( &argp, argc, argv, 0, 0, 0 ) ) error(
    EPERM, 0,
    "cannot parse the command line"
  );

}

