diff --git a/openrct2.sln b/openrct2.sln index b3adc8b5f0..559ca44fee 100644 --- a/openrct2.sln +++ b/openrct2.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openrct2", "openrct2.vcxproj", "{D24D94F6-2A74-480C-B512-629C306CE92F}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libs", "libs.vcxproj", "{074DC930-05C6-4B7F-B5DD-DD237E6E44DB}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -23,14 +21,6 @@ Global {D24D94F6-2A74-480C-B512-629C306CE92F}.Release XP|Win32.Build.0 = Release XP|Win32 {D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.ActiveCfg = Release|Win32 {D24D94F6-2A74-480C-B512-629C306CE92F}.Release|Win32.Build.0 = Release|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Debug|Win32.ActiveCfg = Debug|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Debug|Win32.Build.0 = Debug|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release with Tests|Win32.ActiveCfg = Release with Tests|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release with Tests|Win32.Build.0 = Release with Tests|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release XP|Win32.ActiveCfg = Release XP|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release XP|Win32.Build.0 = Release XP|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release|Win32.ActiveCfg = Release|Win32 - {074DC930-05C6-4B7F-B5DD-DD237E6E44DB}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 0427c85709..eb4fb080d2 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -28,6 +28,7 @@ + @@ -116,9 +117,6 @@ - - - @@ -292,11 +290,6 @@ - - - {074dc930-05c6-4b7f-b5dd-dd237e6e44db} - - {D24D94F6-2A74-480C-B512-629C306CE92F} openrct2 @@ -348,14 +341,15 @@ - $(SolutionDir)lib;$(SolutionDir)lib\libspeex;$(SolutionDir)lib\sdl\include;$(SolutionDir)lib\libcurl\include;$(SolutionDir)lib\jansson;$(SolutionDir)lib\cutest;$(SolutionDir)lib\SDL2_ttf\include;$(IncludePath) - $(SolutionDir)lib\sdl\lib\x86;$(SolutionDir)lib\libcurl\lib;$(LibraryPath) + $(SolutionDir)lib\include;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(IncludePath) + $(SolutionDir)lib;$(LibraryPath) $(SolutionDir)bin\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ + - $(SolutionDir)lib;$(SolutionDir)lib\libspeex;$(SolutionDir)lib\sdl\include;$(SolutionDir)lib\libcurl\include;$(SolutionDir)lib\jansson;$(SolutionDir)lib\cutest;$(SolutionDir)lib\SDL2_ttf\include;$(IncludePath) - $(SolutionDir)lib\sdl\lib\x86;$(SolutionDir)lib\libcurl\lib;$(LibraryPath) + $(SolutionDir)lib\include;$(SolutionDir)lib\include\libspeex;$(SolutionDir)lib\include\sdl;$(SolutionDir)lib\include\jansson;$(SolutionDir)lib\include\sdl_ttf;$(SolutionDir)lib\include\libpng;$(IncludePath) + $(SolutionDir)lib;$(LibraryPath) $(SolutionDir)bin\ $(SolutionDir)obj\$(ProjectName)\$(Configuration)\ @@ -377,7 +371,7 @@ Disabled true 1Byte - $(OpenRCT2_DEFINES);DEBUG;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) + $(OpenRCT2_DEFINES);DEBUG;USE_LIBPNG;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) MultiThreaded true $(IntDir)\%(RelativeDir) @@ -386,7 +380,9 @@ true - winmm.lib;sdl2.lib;%(AdditionalDependencies) + openrct2-libs-vs2015.lib;imm32.lib;version.lib;winmm.lib;%(AdditionalDependencies) + UseFastLinkTimeCodeGeneration + /OPT:NOLBR %(AdditionalOptions) @@ -403,7 +399,7 @@ false - $(OpenRCT2_DEFINES);_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;%(PreprocessorDefinitions) + $(OpenRCT2_DEFINES);USE_LIBPNG;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;_USE_MATH_DEFINES;CURL_STATICLIB;SDL_MAIN_HANDLED;%(PreprocessorDefinitions) $(IntDir)fake\%(RelativeDir) true Speed @@ -412,7 +408,7 @@ true true true - winmm.lib;sdl2.lib;%(AdditionalDependencies) + openrct2-libs-vs2015.lib;imm32.lib;version.lib;winmm.lib;%(AdditionalDependencies) /ignore:4099 %(AdditionalOptions) diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index 9c8b9cd76b..e14e10b1c1 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -459,15 +459,6 @@ Source\Windows - - Test - - - Test\Management - - - Test\Ride - Source\Windows @@ -564,6 +555,7 @@ Source\Core + diff --git a/src/argparse/argparse.c b/src/argparse/argparse.c new file mode 100644 index 0000000000..3ecd4d1f2e --- /dev/null +++ b/src/argparse/argparse.c @@ -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("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + 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, "="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "="); + } + 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; +} diff --git a/src/argparse/argparse.h b/src/argparse/argparse.h new file mode 100644 index 0000000000..350ee58df4 --- /dev/null +++ b/src/argparse/argparse.h @@ -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 + */ + +#include +#include +#include +#include +#include +#include + +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 diff --git a/src/cmdline.c b/src/cmdline.c index 003f6faba4..c4fb495947 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -19,7 +19,7 @@ *****************************************************************************/ #include -#include +#include "argparse/argparse.h" #include "addresses.h" #include "cmdline.h" #include "interface/screenshot.h"