Initial Commit
This commit is contained in:
@@ -0,0 +1,462 @@
|
||||
/**
|
||||
* @file argparse.h
|
||||
* @brief Contains the structures and functions for a small command-line
|
||||
* argument parser built atop @c strview_t, modeled after Python's
|
||||
* @c argparse module.
|
||||
* @author John Christman (sorakatadzuma@gmail.com)
|
||||
* @copyright Malunal Studios, LLC.
|
||||
*
|
||||
* @code
|
||||
* argparser_t parser;
|
||||
* argresult_t result;
|
||||
* argparser_init("prog", "Does a thing.", null, &parser);
|
||||
* argparser_add_option(&parser, &ARGOPTION_FLAG(
|
||||
* .short_name = 'v',
|
||||
* .long_name = "verbose",
|
||||
* .help_string = "increase verbosity"
|
||||
* ));
|
||||
* argparser_add_option(&parser, &ARGOPTION_POSITIONAL(
|
||||
* .value_name = "input",
|
||||
* .value_type = ARGOPTION_TYPE_STRING,
|
||||
* .required = true,
|
||||
* .help_string = "the file to read"
|
||||
* ));
|
||||
*
|
||||
* error_t error = argparser_parse(&parser, argc, argv, &result);
|
||||
* if (error.domain != null) {
|
||||
* argresult_print_error(&result, error, stderr);
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
#include "malunal/allocator.h"
|
||||
#include "malunal/strview.h"
|
||||
#include "malunal/containers/vector.h"
|
||||
#include "malunal/types/error.h"
|
||||
|
||||
#ifndef MALUNAL_ARGPARSE_HEADER
|
||||
#define MALUNAL_ARGPARSE_HEADER
|
||||
|
||||
|
||||
/**
|
||||
* @brief Imports the @c argparse error domain for error checking.
|
||||
* @details The @c argparse error domain is specific to defining and parsing
|
||||
* command-line arguments.
|
||||
*/
|
||||
extern
|
||||
const error_domain_t
|
||||
ERROR_DOMAIN_ARGPARSE_T;
|
||||
|
||||
/**
|
||||
* @brief Defines the set of errors that may be triggered by argparse.
|
||||
* @details The first group is produced while defining a parser, the second
|
||||
* while parsing a command line, and the last while reading values
|
||||
* back out of a result.
|
||||
*/
|
||||
typedef enum {
|
||||
ARGPARSE_ERROR_NULL_INPUT,
|
||||
ARGPARSE_ERROR_INVALID_DEFINITION,
|
||||
ARGPARSE_ERROR_DUPLICATE_DEFINITION,
|
||||
|
||||
ARGPARSE_ERROR_HELP_REQUESTED,
|
||||
ARGPARSE_ERROR_UNKNOWN_OPTION,
|
||||
ARGPARSE_ERROR_AMBIGUOUS_OPTION,
|
||||
ARGPARSE_ERROR_UNKNOWN_SUBCOMMAND,
|
||||
ARGPARSE_ERROR_MISSING_VALUE,
|
||||
ARGPARSE_ERROR_UNEXPECTED_VALUE,
|
||||
ARGPARSE_ERROR_INVALID_VALUE,
|
||||
ARGPARSE_ERROR_MISSING_REQUIRED_OPTION,
|
||||
ARGPARSE_ERROR_MISSING_REQUIRED_POSITIONAL,
|
||||
ARGPARSE_ERROR_TOO_MANY_POSITIONALS,
|
||||
|
||||
ARGPARSE_ERROR_UNKNOWN_NAME,
|
||||
ARGPARSE_ERROR_TYPE_MISMATCH,
|
||||
ARGPARSE_ERROR_NOT_PRESENT,
|
||||
ARGPARSE_ERROR_OUT_OF_RANGE,
|
||||
} argparse_error_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Defines the type of value an option or positional argument holds.
|
||||
* @details Only options may be @c ARGOPTION_TYPE_FLAG, which takes no value
|
||||
* and instead counts its occurrences (like Python's @c store_true
|
||||
* and @c count actions combined).
|
||||
*/
|
||||
typedef enum {
|
||||
ARGOPTION_TYPE_FLAG, /**< No value; occurrences are counted. */
|
||||
ARGOPTION_TYPE_STRING, /**< A @c strview_t into the original argv. */
|
||||
ARGOPTION_TYPE_NUMBER, /**< A @c malunal_int64_t, in base 10. */
|
||||
ARGOPTION_TYPE_FLOAT, /**< A @c malunal_double_t. */
|
||||
ARGOPTION_TYPE_BOOLEAN, /**< true/false, yes/no, on/off, or 1/0. */
|
||||
} argoption_type_t;
|
||||
|
||||
/**
|
||||
* @struct argoption
|
||||
* @brief Defines a single argument accepted by a parser.
|
||||
* @details When @c isflag is set, this is an option matched by name on the
|
||||
* command line (@c -o or @c --output) and the @c flag member is used.
|
||||
* Otherwise it is a positional argument matched by order, and the
|
||||
* @c pos member is used. Prefer the @c ARGOPTION_FLAG and
|
||||
* @c ARGOPTION_POSITIONAL macros to construct these.
|
||||
* @remarks All strings are borrowed, not copied, and must outlive the parser.
|
||||
*/
|
||||
define_struct(argoption) {
|
||||
malunal_char_t short_name;
|
||||
malunal_cstr_t long_name;
|
||||
malunal_cstr_t meta_name;
|
||||
malunal_cstr_t help_string;
|
||||
malunal_cstr_t default_value;
|
||||
argoption_type_t value_type;
|
||||
malunal_uint8_t isflag : 1;
|
||||
malunal_uint8_t required : 1;
|
||||
malunal_uint8_t multiple : 1;
|
||||
malunal_uint8_t reserved : 5;
|
||||
};
|
||||
|
||||
/**
|
||||
* @def ARGOPTION_FLAG
|
||||
* @brief Constructs an option (named) @c argoption_t compound literal.
|
||||
* @param ... Designated initializers for the @c flag member.
|
||||
*/
|
||||
#define ARGOPTION_FLAG(...) \
|
||||
((argoption_t){ .isflag = true, __VA_ARGS__ })
|
||||
|
||||
/**
|
||||
* @def ARGOPTION_POSITIONAL
|
||||
* @brief Constructs a positional @c argoption_t compound literal.
|
||||
* @param ... Designated initializers for the @c pos member.
|
||||
*/
|
||||
#define ARGOPTION_POSITIONAL(...) \
|
||||
((argoption_t){ .isflag = false, __VA_ARGS__ })
|
||||
|
||||
|
||||
/**
|
||||
* @struct argparser
|
||||
* @brief Defines a command-line parser.
|
||||
* @details It consists of a name, the options for this particular parser, and a
|
||||
* set of positionals. The positionals can be either of values or sub-
|
||||
* commands, but not both. Every parser is given a @c -h / @c --help
|
||||
* flag when initialized.
|
||||
*/
|
||||
define_struct(argparser) {
|
||||
malunal_size_t __opaque[22];
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct argresult
|
||||
* @brief Holds the outcome of parsing a command line against a parser.
|
||||
* @details Values are looked up by an argument's long name, short name, or
|
||||
* positional value name. Leading dashes on the name are ignored, so
|
||||
* "output", "--output", "o", and "-o" all find the same option.
|
||||
* @remarks A result refers to its parser and to the argv it was parsed from;
|
||||
* both must outlive it.
|
||||
*/
|
||||
define_struct(argresult) {
|
||||
malunal_size_t __opaque[19];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes a parser with the given @c name and @c desc.
|
||||
* @param name The name of the program, shown in usage output.
|
||||
* @param desc A description shown in help output, or null.
|
||||
* @param allocator The allocator to obtain memory from, or null to use
|
||||
* @c libc_allocator().
|
||||
* @param parser A pointer to the parser to initialize.
|
||||
* @returns An error if the parser could not be initialized.
|
||||
*/
|
||||
error_t
|
||||
argparser_init(
|
||||
malunal_cstr_t name,
|
||||
malunal_cstr_t desc,
|
||||
allocator_mptr_t allocator,
|
||||
argparser_mptr_t parser
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Frees the memory owned by a parser, including its subcommands.
|
||||
* @param parser A pointer to the parser to free.
|
||||
* @returns An error if the parser could not be freed.
|
||||
*/
|
||||
error_t
|
||||
argparser_free(
|
||||
argparser_mptr_t parser
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Registers an option or positional argument with the parser.
|
||||
* @param parser A pointer to the parser to register the argument with.
|
||||
* @param option A pointer to the argument definition, which is copied.
|
||||
* @returns @c ARGPARSE_ERROR_INVALID_DEFINITION if the definition is
|
||||
* inconsistent, @c ARGPARSE_ERROR_DUPLICATE_DEFINITION if any of its
|
||||
* names are already in use, or an allocation error.
|
||||
*/
|
||||
error_t
|
||||
argparser_add_option(
|
||||
argparser_mptr_t parser,
|
||||
argoption_iptr_t option
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Registers a nested subcommand parser (e.g. "git commit").
|
||||
* @param parser A pointer to the parent parser.
|
||||
* @param name The subcommand's name, as typed on the command line.
|
||||
* @param desc A description shown in both parsers' help output, or null.
|
||||
* @param out Receives the subcommand's parser, which is owned by
|
||||
* @c parser and freed along with it.
|
||||
* @returns An error if the parser already has positionals, the name is in
|
||||
* use by a sibling, or allocation failed.
|
||||
*/
|
||||
error_t
|
||||
argparser_add_subcommand(
|
||||
argparser_mptr_t parser,
|
||||
malunal_cstr_t name,
|
||||
malunal_cstr_t desc,
|
||||
argparser_mptr_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Parses a command line against a parser's definitions.
|
||||
* @details Options and positionals may be interleaved. A @c -- stops option
|
||||
* processing. Long options may be abbreviated to any unique prefix.
|
||||
* The first bare argument given to a parser with subcommands selects
|
||||
* the subcommand, which parses everything after it.
|
||||
* @param parser The parser to parse against.
|
||||
* @param argc The number of entries in @c argv.
|
||||
* @param argv The arguments, as passed to @c main(); index 0 is the
|
||||
* program name and is skipped.
|
||||
* @param result Populated with the parsed values. It is always initialized,
|
||||
* so it must be freed with @c argresult_free() whether or not
|
||||
* parsing succeeded.
|
||||
* @returns An error describing the first problem encountered. When help was
|
||||
* requested, @c ARGPARSE_ERROR_HELP_REQUESTED is returned.
|
||||
*/
|
||||
error_t
|
||||
argparser_parse(
|
||||
argparser_iptr_t parser,
|
||||
malunal_int32_t argc,
|
||||
malunal_cstr_t* argv,
|
||||
argresult_mptr_t result
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Writes a one-line usage summary of the parser to @c stream.
|
||||
* @param parser The parser to describe.
|
||||
* @returns An error if either input was null.
|
||||
*/
|
||||
error_t
|
||||
argparser_print_usage(
|
||||
argparser_iptr_t parser
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Writes the full help text of the parser to @c stream.
|
||||
* @param parser The parser to describe.
|
||||
* @returns An error if either input was null.
|
||||
*/
|
||||
error_t
|
||||
argparser_print_help(
|
||||
argparser_iptr_t parser
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Frees the memory owned by a result, including subcommand results.
|
||||
* @param result A pointer to the result to free.
|
||||
* @returns An error if the result could not be freed.
|
||||
*/
|
||||
error_t
|
||||
argresult_free(
|
||||
argresult_mptr_t result
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Reports a parse error the way Python's argparse does.
|
||||
* @details Writes the usage of the parser that failed followed by a
|
||||
* "prog: error: ..." line. If @c error is
|
||||
* @c ARGPARSE_ERROR_HELP_REQUESTED, the full help of the parser that
|
||||
* saw the help flag is written instead.
|
||||
* @param result The result that @c error was produced for.
|
||||
* @param error The error returned by @c argparser_parse().
|
||||
* @param stream The stream to write to.
|
||||
* @returns An error if @c result or @c stream was null.
|
||||
*/
|
||||
error_t
|
||||
argresult_print_error(
|
||||
argresult_iptr_t result,
|
||||
error_t error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Indicates whether an argument was given on the command line.
|
||||
* @param result The result to check.
|
||||
* @param name The argument's name.
|
||||
* @retval true If the argument was given at least once.
|
||||
* @retval false If it was absent (even if it has a default), or unknown.
|
||||
*/
|
||||
malunal_bool_t
|
||||
argresult_has(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Provides how many times a flag was given, or how many values an
|
||||
* option or positional holds (including a default).
|
||||
* @param result The result to check.
|
||||
* @param name The argument's name.
|
||||
* @returns The count, or zero if the argument is unknown.
|
||||
*/
|
||||
malunal_size_t
|
||||
argresult_count(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the first value of a string argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param out Populated with a view into the original argv.
|
||||
* @returns @c ARGPARSE_ERROR_UNKNOWN_NAME, @c ARGPARSE_ERROR_TYPE_MISMATCH,
|
||||
* or @c ARGPARSE_ERROR_NOT_PRESENT if no value can be provided.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_string(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
strview_mptr_t out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the first value of a number argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see @c argresult_get_string.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_number(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_int64_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the first value of a float argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see @c argresult_get_string.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_float(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_double_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the first value of a boolean argument.
|
||||
* @details This also accepts flags, reporting whether they were given.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see @c argresult_get_string.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_boolean(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_bool_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value at @c index of a string argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param index The index of the value, below @c argresult_count().
|
||||
* @param out Populated with a view into the original argv.
|
||||
* @returns An error if no value can be provided, including
|
||||
* @c ARGPARSE_ERROR_OUT_OF_RANGE when @c index is too large.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_string_at(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_size_t index,
|
||||
strview_mptr_t out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value at @c index of a number argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param index The index of the value, below @c argresult_count().
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see
|
||||
* @c argresult_get_string_at.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_number_at(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_size_t index,
|
||||
malunal_int64_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value at @c index of a float argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param index The index of the value, below @c argresult_count().
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see
|
||||
* @c argresult_get_string_at.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_float_at(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_size_t index,
|
||||
malunal_double_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the value at @c index of a boolean argument.
|
||||
* @param result The result to read from.
|
||||
* @param name The argument's name.
|
||||
* @param index The index of the value, below @c argresult_count().
|
||||
* @param out Populated with the value.
|
||||
* @returns An error if no value can be provided, see
|
||||
* @c argresult_get_string_at.
|
||||
*/
|
||||
error_t
|
||||
argresult_get_boolean_at(
|
||||
argresult_iptr_t result,
|
||||
malunal_cstr_t name,
|
||||
malunal_size_t index,
|
||||
malunal_bool_t* out
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Provides the name of the subcommand that was invoked, if any.
|
||||
* @param result The result to read from.
|
||||
* @returns The subcommand's name, or null if none was invoked.
|
||||
*/
|
||||
malunal_cstr_t
|
||||
argresult_subcommand(
|
||||
argresult_iptr_t result
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Provides the result of the subcommand that was invoked, if any.
|
||||
* @param result The result to read from.
|
||||
* @returns The subcommand's result, owned by @c result, or null.
|
||||
*/
|
||||
argresult_iptr_t
|
||||
argresult_subresult(
|
||||
argresult_iptr_t result
|
||||
);
|
||||
|
||||
#endif /* MALUNAL_ARGPARSE_HEADER */
|
||||
Reference in New Issue
Block a user