280 lines
9.8 KiB
C
280 lines
9.8 KiB
C
|
/***************************************************************************************************
|
||
|
|
||
|
Zyan Core Library (Zycore-C)
|
||
|
|
||
|
Original Author : Joel Hoener
|
||
|
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in all
|
||
|
* copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
* SOFTWARE.
|
||
|
|
||
|
***************************************************************************************************/
|
||
|
|
||
|
#include <Zycore/ArgParse.h>
|
||
|
#include <Zycore/LibC.h>
|
||
|
|
||
|
/* ============================================================================================== */
|
||
|
/* Exported functions */
|
||
|
/* ============================================================================================== */
|
||
|
|
||
|
#ifndef ZYAN_NO_LIBC
|
||
|
|
||
|
ZyanStatus ZyanArgParse(const ZyanArgParseConfig *cfg, ZyanVector* parsed,
|
||
|
const char** error_token)
|
||
|
{
|
||
|
return ZyanArgParseEx(cfg, parsed, error_token, ZyanAllocatorDefault());
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
|
||
|
ZyanStatus ZyanArgParseEx(const ZyanArgParseConfig *cfg, ZyanVector* parsed,
|
||
|
const char** error_token, ZyanAllocator* allocator)
|
||
|
{
|
||
|
# define ZYAN_ERR_TOK(tok) if (error_token) { *error_token = tok; }
|
||
|
|
||
|
ZYAN_ASSERT(cfg);
|
||
|
ZYAN_ASSERT(parsed);
|
||
|
|
||
|
// TODO: Once we have a decent hash map impl, refactor this to use it. The majority of for
|
||
|
// loops through the argument list could be avoided.
|
||
|
|
||
|
if (cfg->min_unnamed_args > cfg->max_unnamed_args)
|
||
|
{
|
||
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
||
|
}
|
||
|
|
||
|
// Check argument syntax.
|
||
|
for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
|
||
|
{
|
||
|
// TODO: Duplicate check
|
||
|
|
||
|
if (!def->name)
|
||
|
{
|
||
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
||
|
}
|
||
|
|
||
|
ZyanUSize arg_len = ZYAN_STRLEN(def->name);
|
||
|
if (arg_len < 2 || def->name[0] != '-')
|
||
|
{
|
||
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
||
|
}
|
||
|
|
||
|
// Single dash arguments only accept a single char name.
|
||
|
if (def->name[1] != '-' && arg_len != 2)
|
||
|
{
|
||
|
return ZYAN_STATUS_INVALID_ARGUMENT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Initialize output vector.
|
||
|
ZYAN_CHECK(ZyanVectorInitEx(parsed, sizeof(ZyanArgParseArg), cfg->argc, ZYAN_NULL, allocator,
|
||
|
ZYAN_VECTOR_DEFAULT_GROWTH_FACTOR, ZYAN_VECTOR_DEFAULT_SHRINK_THRESHOLD));
|
||
|
|
||
|
ZyanStatus err;
|
||
|
ZyanBool accept_dash_args = ZYAN_TRUE;
|
||
|
ZyanUSize num_unnamed_args = 0;
|
||
|
for (ZyanUSize i = 1; i < cfg->argc; ++i)
|
||
|
{
|
||
|
const char* cur_arg = cfg->argv[i];
|
||
|
ZyanUSize arg_len = ZYAN_STRLEN(cfg->argv[i]);
|
||
|
|
||
|
// Double-dash argument?
|
||
|
if (accept_dash_args && arg_len >= 2 && ZYAN_MEMCMP(cur_arg, "--", 2) == 0)
|
||
|
{
|
||
|
// GNU style end of argument parsing.
|
||
|
if (arg_len == 2)
|
||
|
{
|
||
|
accept_dash_args = ZYAN_FALSE;
|
||
|
}
|
||
|
// Regular double-dash argument.
|
||
|
else
|
||
|
{
|
||
|
// Allocate parsed argument struct.
|
||
|
ZyanArgParseArg* parsed_arg;
|
||
|
ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
|
||
|
ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
|
||
|
|
||
|
// Find corresponding argument definition.
|
||
|
for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
|
||
|
{
|
||
|
if (ZYAN_STRCMP(def->name, cur_arg) == 0)
|
||
|
{
|
||
|
parsed_arg->def = def;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Search exhausted & argument not found. RIP.
|
||
|
if (!parsed_arg->def)
|
||
|
{
|
||
|
err = ZYAN_STATUS_ARG_NOT_UNDERSTOOD;
|
||
|
ZYAN_ERR_TOK(cur_arg);
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
// Does the argument expect a value? If yes, consume next token.
|
||
|
if (!parsed_arg->def->boolean)
|
||
|
{
|
||
|
if (i == cfg->argc - 1)
|
||
|
{
|
||
|
err = ZYAN_STATUS_ARG_MISSES_VALUE;
|
||
|
ZYAN_ERR_TOK(cur_arg);
|
||
|
goto failure;
|
||
|
}
|
||
|
parsed_arg->has_value = ZYAN_TRUE;
|
||
|
ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cfg->argv[++i]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Continue parsing at next token.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Single-dash argument?
|
||
|
// TODO: How to deal with just dashes? Current code treats it as unnamed arg.
|
||
|
if (accept_dash_args && arg_len > 1 && cur_arg[0] == '-')
|
||
|
{
|
||
|
// Iterate argument token chars until there are either no more chars left
|
||
|
// or we encounter a non-boolean argument, in which case we consume the
|
||
|
// remaining chars as its value.
|
||
|
for (const char* read_ptr = cur_arg + 1; *read_ptr; ++read_ptr)
|
||
|
{
|
||
|
// Allocate parsed argument struct.
|
||
|
ZyanArgParseArg* parsed_arg;
|
||
|
ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
|
||
|
ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
|
||
|
|
||
|
// Find corresponding argument definition.
|
||
|
for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
|
||
|
{
|
||
|
if (ZYAN_STRLEN(def->name) == 2 &&
|
||
|
def->name[0] == '-' &&
|
||
|
def->name[1] == *read_ptr)
|
||
|
{
|
||
|
parsed_arg->def = def;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Search exhausted, no match found?
|
||
|
if (!parsed_arg->def)
|
||
|
{
|
||
|
err = ZYAN_STATUS_ARG_NOT_UNDERSTOOD;
|
||
|
ZYAN_ERR_TOK(cur_arg);
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
// Requires value?
|
||
|
if (!parsed_arg->def->boolean)
|
||
|
{
|
||
|
// If there are chars left, consume them (e.g. `-n1000`).
|
||
|
if (read_ptr[1])
|
||
|
{
|
||
|
parsed_arg->has_value = ZYAN_TRUE;
|
||
|
ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, read_ptr + 1));
|
||
|
}
|
||
|
// If not, consume next token (e.g. `-n 1000`).
|
||
|
else
|
||
|
{
|
||
|
if (i == cfg->argc - 1)
|
||
|
{
|
||
|
err = ZYAN_STATUS_ARG_MISSES_VALUE;
|
||
|
ZYAN_ERR_TOK(cur_arg)
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
parsed_arg->has_value = ZYAN_TRUE;
|
||
|
ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cfg->argv[++i]));
|
||
|
}
|
||
|
|
||
|
// Either way, continue with next argument.
|
||
|
goto continue_main_loop;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Still here? We're looking at an unnamed argument.
|
||
|
++num_unnamed_args;
|
||
|
if (num_unnamed_args > cfg->max_unnamed_args)
|
||
|
{
|
||
|
err = ZYAN_STATUS_TOO_MANY_ARGS;
|
||
|
ZYAN_ERR_TOK(cur_arg);
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
// Allocate parsed argument struct.
|
||
|
ZyanArgParseArg* parsed_arg;
|
||
|
ZYAN_CHECK(ZyanVectorEmplace(parsed, (void**)&parsed_arg, ZYAN_NULL));
|
||
|
ZYAN_MEMSET(parsed_arg, 0, sizeof(*parsed_arg));
|
||
|
parsed_arg->has_value = ZYAN_TRUE;
|
||
|
ZYAN_CHECK(ZyanStringViewInsideBuffer(&parsed_arg->value, cur_arg));
|
||
|
|
||
|
continue_main_loop:;
|
||
|
}
|
||
|
|
||
|
// All tokens processed. Do we have enough unnamed arguments?
|
||
|
if (num_unnamed_args < cfg->min_unnamed_args)
|
||
|
{
|
||
|
err = ZYAN_STATUS_TOO_FEW_ARGS;
|
||
|
// No sensible error token for this error type.
|
||
|
goto failure;
|
||
|
}
|
||
|
|
||
|
// Check whether all required arguments are present.
|
||
|
ZyanUSize num_parsed_args;
|
||
|
ZYAN_CHECK(ZyanVectorGetSize(parsed, &num_parsed_args));
|
||
|
for (const ZyanArgParseDefinition* def = cfg->args; def && def->name; ++def)
|
||
|
{
|
||
|
if (!def->required) continue;
|
||
|
|
||
|
ZyanBool arg_found = ZYAN_FALSE;
|
||
|
for (ZyanUSize i = 0; i < num_parsed_args; ++i)
|
||
|
{
|
||
|
const ZyanArgParseArg* arg = ZYAN_NULL;
|
||
|
ZYAN_CHECK(ZyanVectorGetPointer(parsed, i, (const void**)&arg));
|
||
|
|
||
|
// Skip unnamed args.
|
||
|
if (!arg->def) continue;
|
||
|
|
||
|
if (arg->def == def)
|
||
|
{
|
||
|
arg_found = ZYAN_TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!arg_found)
|
||
|
{
|
||
|
err = ZYAN_STATUS_REQUIRED_ARG_MISSING;
|
||
|
ZYAN_ERR_TOK(def->name);
|
||
|
goto failure;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Yay!
|
||
|
ZYAN_ERR_TOK(ZYAN_NULL);
|
||
|
return ZYAN_STATUS_SUCCESS;
|
||
|
|
||
|
failure:
|
||
|
ZYAN_CHECK(ZyanVectorDestroy(parsed));
|
||
|
return err;
|
||
|
|
||
|
# undef ZYAN_ERR_TOK
|
||
|
}
|
||
|
|
||
|
/* ============================================================================================== */
|