improve command line parsing

This commit is contained in:
IntelOrca 2014-10-09 01:36:59 +01:00
parent e57a625c9f
commit a35ebea72a
11 changed files with 546 additions and 54 deletions

323
lib/argparse/argparse.c Normal file
View File

@ -0,0 +1,323 @@
#include "argparse.h"
#define OPT_UNSET 1
static const char *
prefix_skip(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
int
prefix_cmp(const char *str, const char *prefix)
{
for (;; str++, prefix++)
if (!*prefix)
return 0;
else if (*str != *prefix)
return (unsigned char)*prefix - (unsigned char)*str;
}
static void
argparse_error(struct argparse *this, const struct argparse_option *opt,
const char *reason)
{
if (!strncmp(this->argv[0], "--", 2)) {
fprintf(stderr, "error: option `%s` %s\n", opt->long_name, reason);
exit(1);
} else {
fprintf(stderr, "error: option `%c` %s\n", opt->short_name, reason);
exit(1);
}
}
static int
argparse_getvalue(struct argparse *this, const struct argparse_option *opt,
int flags)
{
const char *s = NULL;
if (!opt->value)
goto skipped;
switch (opt->type) {
case ARGPARSE_OPT_BOOLEAN:
if (flags & OPT_UNSET) {
*(int *)opt->value = *(int *)opt->value - 1;
} else {
*(int *)opt->value = *(int *)opt->value + 1;
}
if (*(int *)opt->value < 0) {
*(int *)opt->value = 0;
}
break;
case ARGPARSE_OPT_BIT:
if (flags & OPT_UNSET) {
*(int *)opt->value &= ~opt->data;
} else {
*(int *)opt->value |= opt->data;
}
break;
case ARGPARSE_OPT_STRING:
if (this->optvalue) {
*(const char **)opt->value = this->optvalue;
this->optvalue = NULL;
} else if (this->argc > 1) {
this->argc--;
*(const char **)opt->value = *++this->argv;
} else {
argparse_error(this, opt, "requires a value");
}
break;
case ARGPARSE_OPT_INTEGER:
if (this->optvalue) {
*(int *)opt->value = strtol(this->optvalue, (char **)&s, 0);
this->optvalue = NULL;
} else if (this->argc > 1) {
this->argc--;
*(int *)opt->value = strtol(*++this->argv, (char **)&s, 0);
} else {
argparse_error(this, opt, "requires a value");
}
if (s[0] != '\0')
argparse_error(this, opt, "expects a numerical value");
break;
default:
assert(0);
}
skipped:
if (opt->callback) {
return opt->callback(this, opt);
}
return 0;
}
static void
argparse_options_check(const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
switch (options->type) {
case ARGPARSE_OPT_END:
case ARGPARSE_OPT_BOOLEAN:
case ARGPARSE_OPT_BIT:
case ARGPARSE_OPT_INTEGER:
case ARGPARSE_OPT_STRING:
case ARGPARSE_OPT_GROUP:
continue;
default:
fprintf(stderr, "wrong option type: %d", options->type);
break;
}
}
}
static int
argparse_short_opt(struct argparse *this, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
if (options->short_name == *this->optvalue) {
this->optvalue = this->optvalue[1] ? this->optvalue + 1 : NULL;
return argparse_getvalue(this, options, 0);
}
}
return -2;
}
static int
argparse_long_opt(struct argparse *this, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
const char *rest;
int opt_flags = 0;
if (!options->long_name)
continue;
rest = prefix_skip(this->argv[0] + 2, options->long_name);
if (!rest) {
// Negation allowed?
if (options->flags & OPT_NONEG) {
continue;
}
// Only boolean/bit allow negation.
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != ARGPARSE_OPT_BIT) {
continue;
}
if (!prefix_cmp(this->argv[0] + 2, "no-")) {
rest = prefix_skip(this->argv[0] + 2 + 3, options->long_name);
if (!rest)
continue;
opt_flags |= OPT_UNSET;
} else {
continue;
}
}
if (*rest) {
if (*rest != '=')
continue;
this->optvalue = rest + 1;
}
return argparse_getvalue(this, options, opt_flags);
}
return -2;
}
int
argparse_init(struct argparse *this, struct argparse_option *options,
const char *const *usage, int flags)
{
memset(this, 0, sizeof(*this));
this->options = options;
this->usage = usage;
this->flags = flags;
return 0;
}
int
argparse_parse(struct argparse *this, int argc, const char **argv)
{
this->argc = argc - 1;
this->argv = argv + 1;
this->out = argv;
argparse_options_check(this->options);
for (; this->argc; this->argc--, this->argv++) {
const char *arg = this->argv[0];
if (arg[0] != '-' || !arg[1]) {
if (this->flags & ARGPARSE_STOP_AT_NON_OPTION) {
goto end;
}
// if it's not option or is a single char '-', copy verbatimly
this->out[this->cpidx++] = this->argv[0];
continue;
}
// short option
if (arg[1] != '-') {
this->optvalue = arg + 1;
switch (argparse_short_opt(this, this->options)) {
case -1:
break;
case -2:
goto unknown;
}
while (this->optvalue) {
switch (argparse_short_opt(this, this->options)) {
case -1:
break;
case -2:
goto unknown;
}
}
continue;
}
// if '--' presents
if (!arg[2]) {
this->argc--;
this->argv++;
break;
}
// long option
switch (argparse_long_opt(this, this->options)) {
case -1:
break;
case -2:
goto unknown;
}
continue;
unknown:
fprintf(stderr, "error: unknown option `%s`\n", this->argv[0]);
argparse_usage(this);
exit(1);
}
end:
memmove(this->out + this->cpidx, this->argv,
this->argc * sizeof(*this->out));
this->out[this->cpidx + this->argc] = NULL;
return this->cpidx + this->argc;
}
void
argparse_usage(struct argparse *this)
{
fprintf(stdout, "Usage: %s\n", *this->usage++);
while (*this->usage && **this->usage)
fprintf(stdout, " or: %s\n", *this->usage++);
fputc('\n', stdout);
const struct argparse_option *options;
// figure out best width
size_t usage_opts_width = 0;
size_t len;
options = this->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
len = 0;
if ((options)->short_name) {
len += 2;
}
if ((options)->short_name && (options)->long_name) {
len += 2; // separator ", "
}
if ((options)->long_name) {
len += strlen((options)->long_name) + 2;
}
if (options->type == ARGPARSE_OPT_INTEGER) {
len += strlen("=<int>");
} else if (options->type == ARGPARSE_OPT_STRING) {
len += strlen("=<str>");
}
len = ceil((float)len / 4) * 4;
if (usage_opts_width < len) {
usage_opts_width = len;
}
}
usage_opts_width += 4; // 4 spaces prefix
options = this->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
size_t pos = 0;
int pad = 0;
if (options->type == ARGPARSE_OPT_GROUP) {
fputc('\n', stdout);
pos += fprintf(stdout, "%s", options->help);
fputc('\n', stdout);
continue;
}
pos = fprintf(stdout, " ");
if (options->short_name) {
pos += fprintf(stdout, "-%c", options->short_name);
}
if (options->long_name && options->short_name) {
pos += fprintf(stdout, ", ");
}
if (options->long_name) {
pos += fprintf(stdout, "--%s", options->long_name);
}
if (options->type == ARGPARSE_OPT_INTEGER) {
pos += fprintf(stdout, "=<int>");
} else if (options->type == ARGPARSE_OPT_STRING) {
pos += fprintf(stdout, "=<str>");
}
if (pos <= usage_opts_width) {
pad = usage_opts_width - pos;
} else {
fputc('\n', stdout);
pad = usage_opts_width;
}
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
}
}
int
argparse_help_cb(struct argparse *this, const struct argparse_option *option)
{
(void)option;
argparse_usage(this);
exit(0);
return 0;
}

132
lib/argparse/argparse.h Normal file
View File

@ -0,0 +1,132 @@
#ifndef ARGPARSE_H
#define ARGPARSE_H
/**
* Command-line arguments parsing library.
*
* This module is inspired by parse-options.c (git) and python's argparse
* module.
*
* Arguments parsing is common task in cli program, but traditional `getopt`
* libraries are not easy to use. This library provides high-level arguments
* parsing solutions.
*
* The program defines what arguments it requires, and `argparse` will figure
* out how to parse those out of `argc` and `argv`, it also automatically
* generates help and usage messages and issues errors when users give the
* program invalid arguments.
*
* Reserved namespaces:
* argparse
* OPT
* Author: Yecheng Fu <cofyc.jackson@gmail.com>
*/
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct argparse;
struct argparse_option;
typedef int argparse_callback(struct argparse *this,
const struct argparse_option *option);
enum argparse_flag {
ARGPARSE_STOP_AT_NON_OPTION = 1,
};
enum argparse_option_type {
/* special */
ARGPARSE_OPT_END,
ARGPARSE_OPT_GROUP,
/* options with no arguments */
ARGPARSE_OPT_BOOLEAN,
ARGPARSE_OPT_BIT,
/* options with arguments (optional or required) */
ARGPARSE_OPT_INTEGER,
ARGPARSE_OPT_STRING,
};
enum argparse_option_flags {
OPT_NONEG = 1, /* Negation disabled. */
};
/*
* Argparse option struct.
*
* `type`:
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
* array.
*
* `short_name`:
* the character to use as a short option name, '\0' if none.
*
* `long_name`:
* the long option name, without the leading dash, NULL if none.
*
* `value`:
* stores pointer to the value to be filled.
*
* `help`:
* the short help message associated to what the option does.
* Must never be NULL (except for ARGPARSE_OPT_END).
*
* `callback`:
* function is called when corresponding argument is parsed.
*
* `data`:
* associated data. Callbacks can use it like they want.
*
* `flags`:
* option flags.
*
*/
struct argparse_option {
enum argparse_option_type type;
const char short_name;
const char *long_name;
void *value;
const char *help;
argparse_callback *callback;
intptr_t data;
int flags;
};
/*
* argpparse
*/
struct argparse {
// user supplied
const struct argparse_option *options;
const char *const *usage;
int flags;
// internal context
int argc;
const char **argv;
const char **out;
int cpidx;
const char *optvalue; // current option value
};
// builtin callbacks
int argparse_help_cb(struct argparse *this,
const struct argparse_option *option);
// builtin option macros
#define OPT_END() { ARGPARSE_OPT_END }
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL }
#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, "show this help message and exit", argparse_help_cb)
int argparse_init(struct argparse *this, struct argparse_option *options,
const char *const *usage, int flags);
int argparse_parse(struct argparse *this, int argc, const char **argv);
void argparse_usage(struct argparse *this);
#endif

View File

@ -28,6 +28,7 @@
<Text Include="..\data\language\swedish.txt" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\argparse\argparse.h" />
<ClInclude Include="..\lib\libspeex\arch.h" />
<ClInclude Include="..\lib\libspeex\config.h" />
<ClInclude Include="..\lib\libspeex\os_support.h" />
@ -35,6 +36,7 @@
<ClInclude Include="..\lib\libspeex\speex\speex_types.h" />
<ClInclude Include="..\lib\libspeex\stack_alloc.h" />
<ClInclude Include="..\lib\lodepng\lodepng.h" />
<ClCompile Include="..\lib\argparse\argparse.c" />
<ClCompile Include="..\lib\libspeex\resample.c;..\lib\lodepng\lodepng.c">
<WarningLevel Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">TurnOffAllWarnings</WarningLevel>
</ClCompile>

View File

@ -59,6 +59,9 @@
<Filter Include="Source\Peep">
<UniqueIdentifier>{51e38783-5334-464c-8f90-61d725dc8013}</UniqueIdentifier>
</Filter>
<Filter Include="Libraries\argparse">
<UniqueIdentifier>{7e9587b2-333f-42ca-8a56-b77070828b17}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="..\openrct2.exe">
@ -419,6 +422,9 @@
<ClCompile Include="..\src\openrct2.c">
<Filter>Source</Filter>
</ClCompile>
<ClCompile Include="..\lib\argparse\argparse.c">
<Filter>Libraries\argparse</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\management\award.h">
@ -610,5 +616,8 @@
<ClInclude Include="..\src\openrct2.h">
<Filter>Source</Filter>
</ClInclude>
<ClInclude Include="..\lib\argparse\argparse.h">
<Filter>Libraries\argparse</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -22,15 +22,28 @@
#ifdef _MSC_VER
#include <time.h>
#endif
#include <argparse/argparse.h>
#include "addresses.h"
#include "cmdline.h"
#include "openrct2.h"
#include "platform/osinterface.h"
typedef struct tm tm_t;
typedef struct argparse_option argparse_option_t;
typedef struct argparse argparse_t;
int gExitCode = 0;
static void print_launch_information();
static const char *const usage[] = {
"openrct2 <command> [options] [<args>]",
"openrct2 <path> [options]",
"openrct2 intro [options]",
"openrct2 edit [path] [options]",
NULL
};
/**
* A shared entry point to OpenRCT2. The command lines must be parsed before any further action is done. Invalid command lines
* will then terminate before any initialisation has even been done.
@ -38,19 +51,49 @@ static void print_launch_information();
*/
int cmdline_run(char *argv[], int argc)
{
print_launch_information();
// For argparse's sake, add virtual first argument process path
argc++;
argv--;
if (argc > 0) {
if (_stricmp(argv[0], "edit") == 0) {
//
int version = 0, width = 0, height = 0;
argparse_option_t options[] = {
OPT_HELP(),
OPT_BOOLEAN('v', "version", &version, "show version information and exit"),
OPT_END()
};
argparse_t argparse;
argparse_init(&argparse, options, usage, 0);
argc = argparse_parse(&argparse, argc, argv);
if (version) {
printf("%s v%s\n", OPENRCT2_NAME, OPENRCT2_VERSION);
printf("%s (%s)\n", OPENRCT2_PLATFORM, OPENRCT2_ARCHITECTURE);
printf("%s\n", OPENRCT2_TIMESTAMP);
return 0;
}
if (argc != 0) {
if (_stricmp(argv[0], "intro") == 0) {
gOpenRCT2StartupAction = STARTUP_ACTION_INTRO;
} else if (_stricmp(argv[0], "edit") == 0) {
gOpenRCT2StartupAction = STARTUP_ACTION_EDIT;
if (argc >= 2)
strcpy(gOpenRCT2StartupActionPath, argv[1]);
} else {
gOpenRCT2StartupAction = STARTUP_ACTION_OPEN;
strcpy(gOpenRCT2StartupActionPath, argv[0]);
if (osinterface_file_exists(argv[0])) {
gOpenRCT2StartupAction = STARTUP_ACTION_OPEN;
strcpy(gOpenRCT2StartupActionPath, argv[0]);
} else {
fprintf(stderr, "error: %s does not exist\n", argv[0]);
return 0;
}
}
}
print_launch_information();
return 1;
}
@ -72,28 +115,4 @@ static void print_launch_information()
printf("Time: %s\n", buffer);
// TODO Print other potential information (e.g. user, hardware)
}
//void check_cmdline_arg()
//{
// int argc;
// char **argv;
// char *args;
//
// args = RCT2_GLOBAL(0x009AC310, char*);
// if (args == (char*)0xFFFFFFFF)
// return;
// RCT2_GLOBAL(0x009AC310, char*) = (char*)0xFFFFFFFF;
//
// argv = CommandLineToArgvA(args, &argc);
// if (argc > 0) {
// if (_stricmp(argv[0], "edit") == 0) {
// if (argc >= 1)
// editor_load_landscape(argv[1]);
// } else {
// rct2_open_file(argv[0]);
// }
// }
//
// LocalFree(argv);
//}
}

View File

@ -74,21 +74,23 @@ static const uint16 _defaultShortcutKeys[SHORTCUT_COUNT] = {
general_configuration_t gGeneral_config;
general_configuration_t gGeneral_config_default = {
0, // play_intro
1, // confirmation_prompt
SCREENSHOT_FORMAT_PNG, // screenshot_format
"", // game_path
MEASUREMENT_FORMAT_IMPERIAL, // measurement_format
TEMPERATURE_FORMAT_F, // temperature_format
CURRENCY_POUNDS, // currency_format
0, // construction_marker_colour
1, // edge_scrolling
0, // always_show_gridlines
1, // landscape_smoothing
0, // show_height_as_units
1, // save_plugin_data
0, // fullscreen mode (default: windowed)
LANGUAGE_ENGLISH_UK
0, // play_intro
1, // confirmation_prompt
SCREENSHOT_FORMAT_PNG, // screenshot_format
"", // game_path
MEASUREMENT_FORMAT_IMPERIAL, // measurement_format
TEMPERATURE_FORMAT_F, // temperature_format
CURRENCY_POUNDS, // currency_format
0, // construction_marker_colour
1, // edge_scrolling
0, // always_show_gridlines
1, // landscape_smoothing
0, // show_height_as_units
1, // save_plugin_data
0, // fullscreen mode (default: windowed)
-1, // window_width
-1, // window_height
LANGUAGE_ENGLISH_UK // language
};
sound_configuration_t gSound_config;

View File

@ -130,6 +130,8 @@ typedef struct general_configuration {
//new
uint8 fullscreen_mode;
sint16 window_width;
sint16 window_height;
uint16 language;
} general_configuration_t;

View File

@ -59,8 +59,6 @@ void openrct2_launch()
RCT2_GLOBAL(RCT2_ADDRESS_RUN_INTRO_TICK_PART, uint8) = 0;
RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) = SCREEN_FLAGS_PLAYING;
// TODO fix, crashes on first game logic update
break;
case STARTUP_ACTION_EDIT:
if (strlen(gOpenRCT2StartupActionPath) == 0)

View File

@ -178,11 +178,11 @@ static void osinterface_create_window()
osinterface_load_cursors();
RCT2_CALLPROC_EBPSAFE(0x0068371D);
width = RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_RESOLUTION_WIDTH, sint16);
height = RCT2_GLOBAL(RCT2_ADDRESS_CONFIG_RESOLUTION_HEIGHT, sint16);
width = gGeneral_config.window_width;
height = gGeneral_config.window_height;
width = 640;
height = 480;
if (width == -1) width = 640;
if (height == -1) height = 480;
}
RCT2_GLOBAL(0x009E2D8C, sint32) = 0;

View File

@ -226,16 +226,19 @@ int rct2_open_file(const char *path)
return 0;
extension++;
if (_stricmp(extension, "sv6")) {
if (_stricmp(extension, "sv6") == 0) {
game_load_save(path);
} else if (!_stricmp(extension, "sc6")) {
return 1;
} else if (_stricmp(extension, "sc6") == 0) {
// TODO scenario install
rct_scenario_basic scenarioBasic;
strcpy(scenarioBasic.path, path);
scenario_load_and_play_from_path(scenarioBasic.path);
} else if (!_stricmp(extension, "td6") || !_stricmp(extension, "td4")) {
} else if (_stricmp(extension, "td6") == 0 || _stricmp(extension, "td4") == 0) {
// TODO track design install
}
return 0;
}
// rct2: 0x00407DB0

View File

@ -318,6 +318,8 @@ int scenario_load_and_play_from_path(const char *path)
gfx_invalidate_screen();
RCT2_GLOBAL(0x009DEA66, uint16) = 0;
RCT2_GLOBAL(0x009DEA5C, uint16) = 62000; // (doesn't appear to ever be read)
return 1;
}