/* argparser.h -- facilities for parsing command line arguments. */
/* Last edited on 2015-10-01 19:07:31 by stolfilocal */

#ifndef argparser_H
#define argparser_H

/* Copyright © 2005 by the State University of Campinas (UNICAMP).*/
/* See the copyright, authorship, and warranty notice at end of file.*/
/* Last edited on 2006-04-01 10:31:38 by stolfi*/

/* This interface provides simple and robust tools for parsing the
  command line arguments given to a process when it is started. Check
  the usage example at the end of this interface. */

#include <stdio.h>
#include <stdint.h>

#include <vec.h>
#include <affirm.h>

typedef struct argparser_t /* A parser for command line arguments. */
  { string_vec_t arg;   /* Command line arguments; {arg[0]} is prog name. */
    bool_vec_t parsed;  /* parsed[i] is {TRUE} if {arg[i]} has been parsed. */
    unsigned next;      /* The next argument to parse is {arg[next]} */
    FILE *wr;           /* File for errors */
    unsigned nhelp;     /* Number of used lines of error help text. */
    string_vec_t help;  /* {help.e[0..nhelp-1]}  is the help text for errors. */
    unsigned ninfo;     /* Number of used lines of program info text. */
    string_vec_t info;  /* {info.e[0..ninfo-1]}  is the program info text. */
  } argparser_t;
  
argparser_t *argparser_new(FILE *wr, int argc, char **argv);
  /* Saves pointers to the given command line arguments. Marks the
   command name {arg[0]} as parsed, all other arguments as unparsed.
   The next argument to be parsed will be {arg[1]}. Any parsing
   errors will be printed to {wr}. */
 
void argparser_error(argparser_t *pp, char *msg);
  /* Prints the given message, the help text, and terminates the program
    with status 1. */

void argparser_finish(argparser_t *pp);
  /* Checks whether all parameters have been parsed; if not, prints a
    message and raises error. Also reclaims {*pp} and all its internal
    storage. */

/* PROGRAM DOCUMENTATION OPTIONS */

void argparser_set_help(argparser_t *pp, char *help);
  /* Appends {help} to the help text stored in {pp}.
  
    The help text will be used by
    {argparser_process_help_info_options} below. Also, if a syntax
    error is found later during argument parsing, the stored help text
    will be written to {pp->wr}, just before halting the program. */

void argparser_set_info(argparser_t *pp, char *info);
  /* Appends {info} to the info text stored in {pp}.
  
    The info text will be used by {argparser_process_help_info_options}
    below.  If and when the info text is printed, it is reformatted
    by breaking every line that is longer than 72 characters at
    internal blanks. Any blanks around the break points (or at the end
    of each line) are discarded, but the initial indentation of the
    line is preserved on each piece. */

void argparser_process_help_info_options(argparser_t *pp);
  /*  If a documentation request keyword ("-info" or "--info") is
    present, the function prints the info text stored in {pp}. Else,
    if a help request keyword ("-help" or "--help") is present, it
    prints the stored help text. In either case, exits the program
    with status 0. */

#define argparser_help_info_HELP \
  "[ -help | --help ] [ -info | --info ]"

#define argparser_help_info_HELP_INFO \
  "  -help\n" \
  "  --help\n" \
  "    Prints an options summary and exits.\n" \
  "\n" \
  "  -info\n" \
  "  --info\n" \
  "    Prints this manpage and exits."

#define argparser_help_info_NO_WARRANTY \
  "  This software is provided \"as is\", WITHOUT ANY EXPLICIT OR" \
  " IMPLICIT WARRANTY, not even the implied warranties of merchantibility" \
  " and fitness for a particular purpose."

#define argparser_help_info_STANDARD_RIGHTS \
  "  Permission to use, copy, modify, and redistribute this software and" \
  " its documentation for any purpose is hereby granted, royalty-free," \
  " provided that: (1) the copyright, AUTHOR, WARRANTY and RIGHTS notices" \
  " in the source files are retained or replaced by completely free" \
  " alternatives, such as the GNU Public  License (GPL);" \
  " (2) no executable code derived from this file is published" \
  " or distributed without the corresponding source code; and (3) these" \
  " same rights are granted to any recipient of such code, under" \
  " the same conditions."

/* PARSING FREE-ORDER NAMED OPTIONS AND ARGUMENTS */

bool_t argparser_keyword_present(argparser_t *pp, char *key);
  /*  Looks for the first unparsed argument {arg[i]} that is equal to
    {key}. If found, marks it as parsed, sets {pp->next} to {i+1}, and
    returns {TRUE}. Otherwise returns {FALSE} and leaves {pp->next}
    unchanged. */

void argparser_get_keyword(argparser_t *pp, char *key);
  /* Same as {argparser_keyword_present}, but raises error if the 
    keyword is not found. */

/* PARSING PARAMETERS AFTER KEYWORDS */

char *argparser_get_next(argparser_t *pp);
  /* Returns {arg[pp->next]}, marks it as parsed and increments {pp->next}.  
    Raises error if {arg[pp->next]} does not exist or has already 
    been parsed. */

char *argparser_get_next_keyword(argparser_t *pp);
  /* Returns {arg[pp->next]}, marks it as parsed and increments {pp->next}.  
    Raises error if {arg[pp->next]} does not exist, has already 
    been parsed, or does not look like a keyword (as
    per {argparser_next_is_keyword}) */

char *argparser_get_next_non_keyword(argparser_t *pp);
  /* Returns {arg[pp->next]}, marks it as parsed and increments {pp->next}.  
    Raises error if {arg[pp->next]} does not exist, has already 
    been parsed, or looks superficially like a keyword (as
    per {argparser_next_is_keyword}). */

int64_t argparser_get_next_int(argparser_t *pp, int64_t min, int64_t max);
uint64_t argparser_get_next_uint(argparser_t *pp, uint64_t min, uint64_t max);
double argparser_get_next_double(argparser_t *pp, double min, double max);
  /* Same as {argparser_get_next}, but converts the result to the
    approriate type (using {strtol} and {strtod}, respectively).
    Raises error if the parameter is not a valid literal, or lies
    outside of the range {[min..max]}.  */

bool_t argparser_get_next_bool(argparser_t *pp);
  /* Same as {argparser_get_next}, but converts the argument string to a
    boolean. The strings "t", "T", "TRUE", "true", "y", "Y", "yes", and "YES", and the numeric value
    1 are converted to {TRUE}; the strings "f", "F", "false", "FALSE", "n", "N","no", and "NO",
    and the numeric value 0 are converted to {FALSE}. Any other value is
    an error. */

int_vec_t argparser_get_int_list(argparser_t *pp, char *key, int min, int max);
  /* Parses all (zero or more) unparsed occurrences of the keyword
    {key}, not necessarily in consecutive positions. Requires that each
    occurrence is immediately followed by an integer in {[min..max]}.
    Returns an array with those integers, in the order found. */

/* PARSING SPECIAL SYNTAX */
  
char *argparser_next(argparser_t *pp);
  /* Returns {arg[pp->next]} if it exists and is still 
    unparsed; else return NULL. Does not change {pp->next}
    and does not mark that argument as parsed. */

bool_t argparser_next_is_keyword(argparser_t *pp);
  /* Returns TRUE if and only if {arg[pp->next]} exists, is still
    unparsed, and looks superficially like a keyword --- that is,
    begins with "-{X}" where {X} is an ASCII letter, or with "--".
    Does not change {pp->next} and does not mark that argument as parsed. */

bool_t argparser_next_is_non_keyword(argparser_t *pp);
  /* Returns TRUE if and only if {arg[pp->next]} exists, is still
    unparsed, and does NOT look like a keyword as per above. Does not
    change {pp->next} and does not mark that argument as parsed. Note
    that it is not the same as {(!argparser_next_is_keyword(pp))}. */

bool_t argparser_next_is_number(argparser_t *pp);
  /* Returns TRUE if and only if {arg[pp->next]} exists, is still
    unparsed, and looks superficially like a number --- namely, begins
    with a digit, or "+" or "-" followed by a digit. Does not change
    {pp->next} and does not mark that argument as parsed. */

bool_t argparser_is_next(argparser_t *pp, char *key);
  /* Returns TRUE if and only if {arg[pp->next]} exists, is still 
    unparsed, and is equal to {key}. Does not change {pp->next}
    and does not mark that argument as parsed. */

void argparser_skip_parsed(argparser_t *pp);
  /* Points {pp->next} at the first unparsed argument. If there are
    any parsed arguments beyond that one, prints a message and raises
    error. */

/* PARSING FIXED-POSITION KEYWORDS AND DELIMITERS */

bool_t argparser_keyword_present_next(argparser_t *pp, char *key);
  /* If {argparser_is_next(pp, key)} is true, marks the next argument
    as parsed, increments {pp->next} and returns TRUE. Otherwise does
    none of these things and returns {FALSE}. */

void argparser_get_keyword_next(argparser_t *pp, char *key);
  /* If {argparser_is_next(pp, key)} is true, marks the next argument
    as parsed and increments {pp->next}. Otherwise raises an error. */

/*
  Most Unix programs expect their command-line arguments to consist
  of a string of keywords and keyword-labeled arguments (`options',
  `switches', etc.), followed by a list of positional arguments.

  For the user's convenience, programs generally allow the switches
  and keyword-labeled arguments to be given in any order. Some of
  those parameters may be optional and/or repeatable, some may be
  mandatory; some may be required or forbidden depending on the values
  of the other parameters. Furthermore, the value of an argument may
  be just a number or a text string, or may be a cluster of two or
  more values with their own little syntax.

  This module simplifies the parsing of such command-line parameters,
  by allowing the program to scan the arguments in the order which
  most suits the program. This module also detects automatically many
  kinds of common mistakes --- such as arguments that are missing, repeated,
  extraneous, malformed, or out of range --- and prints the appropriate
  error messages.

  For example, here is how this module could be used by an
  hypothetical program {prt} that concatenates a bunch of files and
  prints selected line ranges of the result, possibly in reverse
  order, with several formatting options.

    #define MaxLines MAX_INT
    #define MaxRanges 100
    #define MaxFiles 100
    #define MinFontSize 1
    #define MaxFontSize 100
    
    / * Arguments from command line: * /
    int fontSize;
    bool_t landscape;
    int nRanges = 0;
    int ini[MaxRanges], fin[MaxRanges];
    bool_t reverse[MaxRanges];
    int nFiles = 0;
    char *files[MaxFiles];
    
    void parse_args(int argc, char **argv)
      {
        static char *help = 
          "prt \\\n"
          "  -fontSize NUM \\\n"
          "  [ -landscape | -portrait ] \\\n"
          "  [ -lines NUM NUM [ -reverse ] ]..."
          "  FNAME...";
    
        / * Initialize the argument parser: * /
        argparser_t *pp = argparser_new(stderr, argc, argv);
        argparser_set_help(pp, help);
    
        / * The "-fontSize" parameter is mandatory: * /
        argparser_get_keyword(pp, "-fontSize");
        fontSize = argparser_get_next_int(pp, MinFontsize, MaxFontSize);
    
        / * Either "-landscape" or "-portrait", but not both: * /
        if (argparser_keyword_present(pp, "-landscape"))
          { landscape = TRUE; } 
        else if (argparser_keyword_present(pp, "-portrait"))
          { landscape = FALSE; }
        else
          { / * Default is "-portrait" unless font is too big. * /
            landscape = (fontSize > 8);
          }
    
        / * Parse the line ranges: * /
        nRanges = 0;
        while (argparser_keyword_present(pp, "-lines"))
          { if (nRanges >= MaxRanges) 
              { argparser_error(pp, "Too many page ranges"); }
            ini[nRanges] = argparser_get_next_int(pp, 1,MaxLines);
            fin[nRanges] = argparser_get_next_int(pp, ini[nRanges],MaxLines);
            rev[nRanges] = argparser_keyword_present_next(pp, "-reverse");
            nRanges = nRanges+1;
          }
    
        / * By default, print all lines: * /
        if (nRanges == 0)
          { ini[0] = 1; fin[0] = MaxLines; rev[0] = FALSE;
            nRanges = 1;
          }
    
        / * Parse the file list (after all keyword options): * /
        argparser_skip_parsed(pp);
        nFiles = argc - pp->next;
        if (nFiles == 0)
          { argparser_error(pp, "no files specified"); }
        for (i = 0; i < nFiles; i++)
          { files[i] = argparser_get_next(pp); }
    
        / * Check for any unparsed parameters: * /
        argparser_finish(pp);
      }

  Note that this code allows the user to give the options
  "-fontSize" and "-landscape"/"-portrait" in any order, even
  anywhere among or after the "-range" arguments. However, each
  "-range" flag must be immediately followed by two numbers; and the
  "-reverse" flag, if given, must immediately follow the second
  number.  Also, all file names must follow all the other options
  and arguments.
*/  

/* Copyright © 2003 by Jorge Stolfi.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appears in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty of any kind.
*/

#endif
