Implement custom option parser
parent
86652c8a42
commit
474ce23d42
|
@ -17,26 +17,40 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <getopt.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct wv_option {
|
struct wv_option {
|
||||||
char short_opt;
|
char short_opt;
|
||||||
const char* long_opt;
|
const char* long_opt;
|
||||||
const char* schema;
|
const char* schema;
|
||||||
const char* help;
|
const char* help;
|
||||||
|
const char* default_;
|
||||||
|
const char* positional;
|
||||||
|
bool is_subcommand;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct wv_option_value {
|
||||||
|
const struct wv_option* option;
|
||||||
|
char value[256];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct option_parser {
|
struct option_parser {
|
||||||
const struct wv_option* options;
|
const struct wv_option* options;
|
||||||
char short_opts[128];
|
|
||||||
struct option long_opts[128];
|
|
||||||
int n_opts;
|
int n_opts;
|
||||||
|
|
||||||
|
struct wv_option_value values[128];
|
||||||
|
int n_values;
|
||||||
|
int position;
|
||||||
|
|
||||||
|
int endpos;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define OPTION_PARSER_CHECK_ALL_ARGUMENTS 0
|
void option_parser_init(struct option_parser* self,
|
||||||
#define OPTION_PARSER_STOP_ON_FIRST_NONOPTION 1
|
const struct wv_option* options);
|
||||||
|
|
||||||
void option_parser_init(struct option_parser* self, const struct wv_option* options, unsigned flags);
|
|
||||||
|
|
||||||
void option_parser_print_options(struct option_parser* self, FILE* stream);
|
void option_parser_print_options(struct option_parser* self, FILE* stream);
|
||||||
int option_parser_getopt(struct option_parser* self, int argc, char* argv[]);
|
|
||||||
|
int option_parser_parse(struct option_parser* self, int argc,
|
||||||
|
const char* const* argv);
|
||||||
|
const char* option_parser_get_value(const struct option_parser* self,
|
||||||
|
const char* name);
|
||||||
|
|
|
@ -19,6 +19,7 @@ c_args = [
|
||||||
'-DAML_UNSTABLE_API=1',
|
'-DAML_UNSTABLE_API=1',
|
||||||
|
|
||||||
'-Wno-unused-parameter',
|
'-Wno-unused-parameter',
|
||||||
|
'-Wno-missing-field-initializers',
|
||||||
]
|
]
|
||||||
|
|
||||||
git = find_program('git', native: true, required: false)
|
git = find_program('git', native: true, required: false)
|
||||||
|
@ -197,3 +198,7 @@ if scdoc.found()
|
||||||
)
|
)
|
||||||
endforeach
|
endforeach
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if get_option('tests')
|
||||||
|
subdir('test')
|
||||||
|
endif
|
||||||
|
|
|
@ -6,3 +6,5 @@ option('man-pages', type: 'feature', value: 'auto',
|
||||||
description: 'Generate and install man pages')
|
description: 'Generate and install man pages')
|
||||||
option('systemtap', type: 'boolean', value: false,
|
option('systemtap', type: 'boolean', value: false,
|
||||||
description: 'Enable tracing using sdt')
|
description: 'Enable tracing using sdt')
|
||||||
|
option('tests', type: 'boolean', value: false,
|
||||||
|
description: 'Build unit tests')
|
||||||
|
|
106
src/main.c
106
src/main.c
|
@ -20,7 +20,6 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
@ -1069,7 +1068,7 @@ static void on_nvnc_client_new(struct nvnc_client* client)
|
||||||
self->nr_clients);
|
self->nr_clients);
|
||||||
}
|
}
|
||||||
|
|
||||||
void parse_keyboard_option(struct wayvnc* self, char* arg)
|
void parse_keyboard_option(struct wayvnc* self, const char* arg)
|
||||||
{
|
{
|
||||||
// Find optional variant, separated by -
|
// Find optional variant, separated by -
|
||||||
char* index = strchr(arg, '-');
|
char* index = strchr(arg, '-');
|
||||||
|
@ -1252,6 +1251,7 @@ int main(int argc, char* argv[])
|
||||||
const char* output_name = NULL;
|
const char* output_name = NULL;
|
||||||
const char* seat_name = NULL;
|
const char* seat_name = NULL;
|
||||||
const char* socket_path = NULL;
|
const char* socket_path = NULL;
|
||||||
|
const char* keyboard_options = NULL;
|
||||||
|
|
||||||
bool overlay_cursor = false;
|
bool overlay_cursor = false;
|
||||||
bool show_performance = false;
|
bool show_performance = false;
|
||||||
|
@ -1263,6 +1263,8 @@ int main(int argc, char* argv[])
|
||||||
int log_level = NVNC_LOG_WARNING;
|
int log_level = NVNC_LOG_WARNING;
|
||||||
|
|
||||||
static const struct wv_option opts[] = {
|
static const struct wv_option opts[] = {
|
||||||
|
{ .positional = "address" },
|
||||||
|
{ .positional = "port" },
|
||||||
{ 'C', "config", "<path>",
|
{ 'C', "config", "<path>",
|
||||||
"Select a config file." },
|
"Select a config file." },
|
||||||
{ 'g', "gpu", NULL,
|
{ 'g', "gpu", NULL,
|
||||||
|
@ -1278,7 +1280,8 @@ int main(int argc, char* argv[])
|
||||||
{ 'r', "render-cursor", NULL,
|
{ 'r', "render-cursor", NULL,
|
||||||
"Enable overlay cursor rendering." },
|
"Enable overlay cursor rendering." },
|
||||||
{ 'f', "max-fps", "<fps>",
|
{ 'f', "max-fps", "<fps>",
|
||||||
"Set rate limit (default 30)." },
|
"Set rate limit (default 30).",
|
||||||
|
.default_ = "30" },
|
||||||
{ 'p', "performance", NULL,
|
{ 'p', "performance", NULL,
|
||||||
"Show performance counters." },
|
"Show performance counters." },
|
||||||
{ 'u', "unix-socket", NULL,
|
{ 'u', "unix-socket", NULL,
|
||||||
|
@ -1290,84 +1293,53 @@ int main(int argc, char* argv[])
|
||||||
{ 'v', "verbose", NULL,
|
{ 'v', "verbose", NULL,
|
||||||
"Be more verbose. Same as setting --log-level=info" },
|
"Be more verbose. Same as setting --log-level=info" },
|
||||||
{ 'L', "log-level", "<level>",
|
{ 'L', "log-level", "<level>",
|
||||||
"Set log level. The levels are: error, warning, info, debug trace and quiet." },
|
"Set log level. The levels are: error, warning, info, debug trace and quiet.",
|
||||||
|
.default_ = "warning" },
|
||||||
{ 'h', "help", NULL,
|
{ 'h', "help", NULL,
|
||||||
"Get help (this text)." },
|
"Get help (this text)." },
|
||||||
{ '\0', NULL, NULL, NULL }
|
{ '\0', NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct option_parser option_parser;
|
struct option_parser option_parser;
|
||||||
option_parser_init(&option_parser, opts,
|
option_parser_init(&option_parser, opts);
|
||||||
OPTION_PARSER_CHECK_ALL_ARGUMENTS);
|
|
||||||
|
|
||||||
while (1) {
|
if (option_parser_parse(&option_parser, argc,
|
||||||
int c = option_parser_getopt(&option_parser, argc, argv);
|
(const char* const*)argv) < 0)
|
||||||
if (c < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch (c) {
|
|
||||||
case 'C':
|
|
||||||
cfg_file = optarg;
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
enable_gpu_features = true;
|
|
||||||
break;
|
|
||||||
case 'o':
|
|
||||||
output_name = optarg;
|
|
||||||
break;
|
|
||||||
case 'k':
|
|
||||||
parse_keyboard_option(&self, optarg);
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
seat_name = optarg;
|
|
||||||
break;
|
|
||||||
case 'S':
|
|
||||||
socket_path = optarg;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
overlay_cursor = true;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
max_rate = atoi(optarg);
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
show_performance = true;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
use_unix_socket = true;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
disable_input = true;
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
log_level = NVNC_LOG_INFO;
|
|
||||||
break;
|
|
||||||
case 'L':
|
|
||||||
log_level = log_level_from_string(optarg);
|
|
||||||
if (log_level < 0) {
|
|
||||||
fprintf(stderr, "Invalid log level: %s\n",
|
|
||||||
optarg);
|
|
||||||
return wayvnc_usage(&option_parser, stderr, 1);
|
return wayvnc_usage(&option_parser, stderr, 1);
|
||||||
}
|
|
||||||
break;
|
if (option_parser_get_value(&option_parser, "version")) {
|
||||||
case 'V':
|
|
||||||
return show_version();
|
return show_version();
|
||||||
case 'h':
|
}
|
||||||
|
|
||||||
|
if (option_parser_get_value(&option_parser, "help")) {
|
||||||
return wayvnc_usage(&option_parser, stdout, 0);
|
return wayvnc_usage(&option_parser, stdout, 0);
|
||||||
default:
|
|
||||||
return wayvnc_usage(&option_parser, stderr, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg_file = option_parser_get_value(&option_parser, "config");
|
||||||
|
enable_gpu_features = !!option_parser_get_value(&option_parser, "gpu");
|
||||||
|
output_name = option_parser_get_value(&option_parser, "output");
|
||||||
|
seat_name = option_parser_get_value(&option_parser, "seat");
|
||||||
|
socket_path = option_parser_get_value(&option_parser, "socket");
|
||||||
|
overlay_cursor = !!option_parser_get_value(&option_parser, "render-cursor");
|
||||||
|
show_performance = !!option_parser_get_value(&option_parser, "performance");
|
||||||
|
use_unix_socket = !!option_parser_get_value(&option_parser, "unix");
|
||||||
|
disable_input = !!option_parser_get_value(&option_parser, "disable-input");
|
||||||
|
log_level = option_parser_get_value(&option_parser, "verbose")
|
||||||
|
? NVNC_LOG_INFO : NVNC_LOG_WARNING;
|
||||||
|
log_level = log_level_from_string(
|
||||||
|
option_parser_get_value(&option_parser, "log-level"));
|
||||||
|
max_rate = atoi(option_parser_get_value(&option_parser, "max-fps"));
|
||||||
|
|
||||||
|
keyboard_options = option_parser_get_value(&option_parser, "keyboard");
|
||||||
|
if (keyboard_options)
|
||||||
|
parse_keyboard_option(&self, keyboard_options);
|
||||||
|
|
||||||
nvnc_set_log_level(log_level);
|
nvnc_set_log_level(log_level);
|
||||||
|
|
||||||
int n_args = argc - optind;
|
address = option_parser_get_value(&option_parser, "address");
|
||||||
|
const char* port_str = option_parser_get_value(&option_parser, "port");
|
||||||
if (n_args >= 1)
|
if (port_str)
|
||||||
address = argv[optind];
|
port = atoi(port_str);
|
||||||
|
|
||||||
if (n_args >= 2)
|
|
||||||
port = atoi(argv[optind + 1]);
|
|
||||||
|
|
||||||
if (seat_name && disable_input) {
|
if (seat_name && disable_input) {
|
||||||
nvnc_log(NVNC_LOG_ERROR, "seat and disable-input are conflicting options");
|
nvnc_log(NVNC_LOG_ERROR, "seat and disable-input are conflicting options");
|
||||||
|
|
|
@ -15,53 +15,30 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "option-parser.h"
|
#include "option-parser.h"
|
||||||
|
#include "strlcpy.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
static int count_options(const struct wv_option* opts)
|
static int count_options(const struct wv_option* opts)
|
||||||
{
|
{
|
||||||
int n = 0;
|
int n = 0;
|
||||||
while (opts[n].short_opt || opts[n].long_opt)
|
while (opts[n].short_opt || opts[n].long_opt || opts[n].positional)
|
||||||
n++;
|
n++;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void option_parser_init(struct option_parser* self,
|
void option_parser_init(struct option_parser* self,
|
||||||
const struct wv_option* options,
|
const struct wv_option* options)
|
||||||
unsigned flags)
|
|
||||||
{
|
{
|
||||||
memset(self, 0, sizeof(*self));
|
memset(self, 0, sizeof(*self));
|
||||||
|
|
||||||
self->options = options;
|
self->options = options;
|
||||||
self->n_opts = count_options(options);
|
self->n_opts = count_options(options);
|
||||||
|
|
||||||
int short_opt_index = 0;
|
|
||||||
int long_opt_index = 0;
|
|
||||||
|
|
||||||
if (flags && OPTION_PARSER_STOP_ON_FIRST_NONOPTION)
|
|
||||||
self->short_opts[short_opt_index++] = '+';
|
|
||||||
|
|
||||||
for (int i = 0; i < self->n_opts; ++i) {
|
|
||||||
assert(options[i].short_opt); // TODO: Make this optional?
|
|
||||||
|
|
||||||
self->short_opts[short_opt_index++] = options[i].short_opt;
|
|
||||||
if (options[i].schema)
|
|
||||||
self->short_opts[short_opt_index++] = ':';
|
|
||||||
|
|
||||||
if (!options[i].long_opt)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const struct wv_option* src_opt = &options[i];
|
|
||||||
struct option* dst_opt = &self->long_opts[long_opt_index++];
|
|
||||||
|
|
||||||
dst_opt->val = src_opt->short_opt;
|
|
||||||
dst_opt->name = src_opt->long_opt;
|
|
||||||
dst_opt->has_arg = src_opt->schema ?
|
|
||||||
required_argument : no_argument;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int get_left_col_width(const struct wv_option* opts, int n)
|
static int get_left_col_width(const struct wv_option* opts, int n)
|
||||||
|
@ -129,7 +106,8 @@ static void reflow_text(char* dst, const char* src, int width)
|
||||||
static void format_option(const struct wv_option* opt, int left_col_width,
|
static void format_option(const struct wv_option* opt, int left_col_width,
|
||||||
FILE* stream)
|
FILE* stream)
|
||||||
{
|
{
|
||||||
assert(opt->help);
|
if (!opt->help)
|
||||||
|
return;
|
||||||
|
|
||||||
int n_chars = fprintf(stream, " ");
|
int n_chars = fprintf(stream, " ");
|
||||||
if (opt->short_opt)
|
if (opt->short_opt)
|
||||||
|
@ -171,7 +149,223 @@ void option_parser_print_options(struct option_parser* self, FILE* stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int option_parser_getopt(struct option_parser* self, int argc, char* argv[])
|
static const struct wv_option* find_long_option(
|
||||||
|
const struct option_parser* self, const char* name)
|
||||||
{
|
{
|
||||||
return getopt_long(argc, argv, self->short_opts, self->long_opts, NULL);
|
for (int i = 0; i < self->n_opts; ++i) {
|
||||||
|
if (!self->options[i].long_opt)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strcmp(self->options[i].long_opt, name) == 0)
|
||||||
|
return &self->options[i];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wv_option* find_short_option(
|
||||||
|
const struct option_parser* self, char name)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < self->n_opts; ++i)
|
||||||
|
if (self->options[i].short_opt == name)
|
||||||
|
return &self->options[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct wv_option* find_positional_option(
|
||||||
|
struct option_parser* self, int position)
|
||||||
|
{
|
||||||
|
int current_pos = 0;
|
||||||
|
for (int i = 0; i < self->n_opts; ++i) {
|
||||||
|
if (!self->options[i].positional)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (current_pos == position)
|
||||||
|
return &self->options[i];
|
||||||
|
|
||||||
|
current_pos += 1;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int append_value(struct option_parser* self,
|
||||||
|
const struct wv_option* option, const char* value)
|
||||||
|
{
|
||||||
|
if ((size_t)self->n_values >= ARRAY_SIZE(self->values)) {
|
||||||
|
fprintf(stderr, "ERROR: Too many arguments!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct wv_option_value* dst = &self->values[self->n_values++];
|
||||||
|
dst->option = option;
|
||||||
|
strlcpy(dst->value, value, sizeof(dst->value));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_long_arg(struct option_parser* self, int argc,
|
||||||
|
const char* const* argv, int index)
|
||||||
|
{
|
||||||
|
int count = 1;
|
||||||
|
char name[256];
|
||||||
|
strlcpy(name, argv[index] + 2, sizeof(name));
|
||||||
|
char* eq = strchr(name, '=');
|
||||||
|
if (eq)
|
||||||
|
*eq = '\0';
|
||||||
|
|
||||||
|
const struct wv_option* opt = find_long_option(self, name);
|
||||||
|
if (!opt) {
|
||||||
|
fprintf(stderr, "ERROR: Unknown option: \"%s\"\n", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* value = "1";
|
||||||
|
if (opt->schema) {
|
||||||
|
if (eq) {
|
||||||
|
value = eq + 1;
|
||||||
|
} else {
|
||||||
|
if (index + 1 >= argc) {
|
||||||
|
fprintf(stderr, "ERROR: An argument is required for the \"%s\" option\n",
|
||||||
|
opt->long_opt);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = argv[index + 1];
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append_value(self, opt, value) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_short_args(struct option_parser* self, char argc,
|
||||||
|
const char* const* argv, int index)
|
||||||
|
{
|
||||||
|
int count = 1;
|
||||||
|
int len = strlen(argv[index]);
|
||||||
|
|
||||||
|
for (int i = 1; i < len; ++i) {
|
||||||
|
char name = argv[index][i];
|
||||||
|
const struct wv_option* opt = find_short_option(self, name);
|
||||||
|
if (!opt) {
|
||||||
|
fprintf(stderr, "ERROR: Unknown option: \"%c\"\n", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* value = "1";
|
||||||
|
if (opt->schema) {
|
||||||
|
const char* tail = argv[index] + i + 1;
|
||||||
|
if (tail[0] == '=') {
|
||||||
|
value = tail + 1;
|
||||||
|
} else if (tail[0]) {
|
||||||
|
value = tail;
|
||||||
|
} else {
|
||||||
|
if (index + 1 >= argc) {
|
||||||
|
fprintf(stderr, "ERROR: An argument is required for the \"%c\" option\n",
|
||||||
|
opt->short_opt);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = argv[index + 1];
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append_value(self, opt, value) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (opt->schema)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_positional_arg(struct option_parser* self, char argc,
|
||||||
|
const char* const* argv, int i)
|
||||||
|
{
|
||||||
|
const struct wv_option* opt = find_positional_option(self, self->position);
|
||||||
|
if (!opt)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (append_value(self, opt, argv[i]) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
self->position += 1;
|
||||||
|
|
||||||
|
return opt->is_subcommand ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int option_parser_parse(struct option_parser* self, int argc,
|
||||||
|
const char* const* argv)
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
while (i < argc) {
|
||||||
|
if (argv[i][0] == '-') {
|
||||||
|
if (argv[i][1] == '-') {
|
||||||
|
if (argv[i][2] == '\0')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int rc = parse_long_arg(self, argc, argv, i);
|
||||||
|
if (rc < 0)
|
||||||
|
return -1;
|
||||||
|
i += rc;
|
||||||
|
} else {
|
||||||
|
int rc = parse_short_args(self, argc, argv, i);
|
||||||
|
if (rc < 0)
|
||||||
|
return -1;
|
||||||
|
i += rc;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int rc = parse_positional_arg(self, argc, argv, i);
|
||||||
|
if (rc < 0)
|
||||||
|
return -1;
|
||||||
|
if (rc == 0)
|
||||||
|
break;
|
||||||
|
i += rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self->endpos = i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* option_parser_get_value(const struct option_parser* self,
|
||||||
|
const char* name)
|
||||||
|
{
|
||||||
|
const struct wv_option* opt;
|
||||||
|
|
||||||
|
bool is_short = name[0] && !name[1];
|
||||||
|
|
||||||
|
for (int i = 0; i < self->n_values; ++i) {
|
||||||
|
const struct wv_option_value* value = &self->values[i];
|
||||||
|
opt = value->option;
|
||||||
|
|
||||||
|
if (is_short) {
|
||||||
|
if (opt->short_opt && opt->short_opt == *name)
|
||||||
|
return value->value;
|
||||||
|
} else {
|
||||||
|
if (opt->long_opt && strcmp(opt->long_opt, name) == 0)
|
||||||
|
return value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt->positional && strcmp(opt->positional, name) == 0)
|
||||||
|
return value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_short) {
|
||||||
|
opt = find_short_option(self, name[0]);
|
||||||
|
if (opt)
|
||||||
|
return opt->default_;
|
||||||
|
} else {
|
||||||
|
opt = find_long_option(self, name);
|
||||||
|
if (opt)
|
||||||
|
return opt->default_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add positional option?
|
||||||
|
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <getopt.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
@ -71,6 +70,8 @@ int main(int argc, char* argv[])
|
||||||
unsigned flags = 0;
|
unsigned flags = 0;
|
||||||
|
|
||||||
static const struct wv_option opts[] = {
|
static const struct wv_option opts[] = {
|
||||||
|
{ .positional = "command",
|
||||||
|
.is_subcommand = true },
|
||||||
{ 'S', "socket", "<path>",
|
{ 'S', "socket", "<path>",
|
||||||
"Control socket path." },
|
"Control socket path." },
|
||||||
{ 'w', "wait", NULL,
|
{ 'w', "wait", NULL,
|
||||||
|
@ -85,46 +86,36 @@ int main(int argc, char* argv[])
|
||||||
"Be more verbose." },
|
"Be more verbose." },
|
||||||
{ 'h', "help", NULL,
|
{ 'h', "help", NULL,
|
||||||
"Get help (this text)." },
|
"Get help (this text)." },
|
||||||
{ '\0', NULL, NULL, NULL }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct option_parser option_parser;
|
struct option_parser option_parser;
|
||||||
option_parser_init(&option_parser, opts,
|
option_parser_init(&option_parser, opts);
|
||||||
OPTION_PARSER_STOP_ON_FIRST_NONOPTION);
|
if (option_parser_parse(&option_parser, argc,
|
||||||
|
(const char* const*)argv) < 0)
|
||||||
|
return wayvncctl_usage(stderr, &option_parser, 1);
|
||||||
|
|
||||||
while (1) {
|
if (option_parser_get_value(&option_parser, "help"))
|
||||||
int c = option_parser_getopt(&option_parser, argc, argv);
|
|
||||||
if (c < 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
switch (c) {
|
|
||||||
case 'S':
|
|
||||||
socket_path = optarg;
|
|
||||||
break;
|
|
||||||
case 'w':
|
|
||||||
flags |= CTL_CLIENT_SOCKET_WAIT;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
flags |= CTL_CLIENT_RECONNECT;
|
|
||||||
break;
|
|
||||||
case 'j':
|
|
||||||
flags |= CTL_CLIENT_PRINT_JSON;
|
|
||||||
break;
|
|
||||||
case 'v':
|
|
||||||
verbose = true;
|
|
||||||
break;
|
|
||||||
case 'V':
|
|
||||||
return show_version();
|
|
||||||
case 'h':
|
|
||||||
return wayvncctl_usage(stdout, &option_parser, 0);
|
return wayvncctl_usage(stdout, &option_parser, 0);
|
||||||
default:
|
|
||||||
return wayvncctl_usage(stderr, &option_parser, 1);
|
if (option_parser_get_value(&option_parser, "version"))
|
||||||
}
|
return show_version();
|
||||||
}
|
|
||||||
argc -= optind;
|
socket_path = option_parser_get_value(&option_parser, "socket");
|
||||||
if (argc <= 0)
|
flags |= option_parser_get_value(&option_parser, "wait")
|
||||||
return wayvncctl_usage(stderr, &option_parser, 1);
|
? CTL_CLIENT_SOCKET_WAIT : 0;
|
||||||
argv = &argv[optind];
|
flags |= option_parser_get_value(&option_parser, "reconnect")
|
||||||
|
? CTL_CLIENT_RECONNECT : 0;
|
||||||
|
flags |= option_parser_get_value(&option_parser, "json")
|
||||||
|
? CTL_CLIENT_PRINT_JSON : 0;
|
||||||
|
verbose = !!option_parser_get_value(&option_parser, "verbose");
|
||||||
|
|
||||||
|
// No command; nothing to do...
|
||||||
|
if (!option_parser_get_value(&option_parser, "command"))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
argc -= option_parser.endpos;
|
||||||
|
argv += option_parser.endpos;
|
||||||
|
|
||||||
ctl_client_debug_log(verbose);
|
ctl_client_debug_log(verbose);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
#include "tst.h"
|
||||||
|
|
||||||
|
#include "option-parser.h"
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
|
||||||
|
static const struct wv_option options[] = {
|
||||||
|
{ .positional = "first" },
|
||||||
|
{ .positional = "second" },
|
||||||
|
{ .positional = "third" },
|
||||||
|
{ .positional = "command", .is_subcommand = true },
|
||||||
|
{ 'a', "option-a", NULL, "Description of a" },
|
||||||
|
{ 'b', "option-b", NULL, "Description of b" },
|
||||||
|
{ 'v', "value-option", "value", "Description of v" },
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_simple(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
|
||||||
|
const char* argv[] = {
|
||||||
|
"executable",
|
||||||
|
"-a",
|
||||||
|
"-b",
|
||||||
|
"pos 1",
|
||||||
|
"pos 2",
|
||||||
|
};
|
||||||
|
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("pos 1", option_parser_get_value(&parser, "first"));
|
||||||
|
ASSERT_STR_EQ("pos 2", option_parser_get_value(&parser, "second"));
|
||||||
|
ASSERT_FALSE(option_parser_get_value(&parser, "third"));
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "option-b"));
|
||||||
|
ASSERT_FALSE(option_parser_get_value(&parser, "value-option"));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_short_value_option_with_space(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-v", "value" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_short_value_option_without_space(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-vvalue" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_short_value_option_with_eq(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-v=value" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_long_value_option_with_space(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "--value-option", "value" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_long_value_option_without_space(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "--value-option=value" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_STR_EQ("value", option_parser_get_value(&parser, "value-option"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_multi_short_option(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-ab" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_multi_short_option_with_value(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-abvthe-value" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "b"));
|
||||||
|
ASSERT_STR_EQ("the-value", option_parser_get_value(&parser, "v"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_stop(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "exec", "-a", "--", "-b"};
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
|
||||||
|
ASSERT_TRUE(option_parser_get_value(&parser, "a"));
|
||||||
|
ASSERT_FALSE(option_parser_get_value(&parser, "b"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_unknown_short_option(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-x" };
|
||||||
|
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_unknown_long_option(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "--an-unknown-long-option" };
|
||||||
|
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_missing_short_value(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-v" };
|
||||||
|
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_missing_long_value(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "--value-option" };
|
||||||
|
ASSERT_INT_EQ(-1, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_subcommand_without_arguments(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-ab", "first", "second", "third",
|
||||||
|
"do-stuff" };
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
ASSERT_INT_EQ(5, parser.endpos);
|
||||||
|
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_subcommand_with_arguments(void)
|
||||||
|
{
|
||||||
|
struct option_parser parser;
|
||||||
|
option_parser_init(&parser, options);
|
||||||
|
const char* argv[] = { "executable", "-ab", "first", "second", "third",
|
||||||
|
"do-stuff", "--some-option", "another-argument"};
|
||||||
|
ASSERT_INT_EQ(0, option_parser_parse(&parser, ARRAY_SIZE(argv), argv));
|
||||||
|
ASSERT_INT_EQ(5, parser.endpos);
|
||||||
|
ASSERT_STR_EQ("do-stuff", option_parser_get_value(&parser, "command"));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
int r = 0;
|
||||||
|
RUN_TEST(test_simple);
|
||||||
|
RUN_TEST(test_short_value_option_with_space);
|
||||||
|
RUN_TEST(test_short_value_option_without_space);
|
||||||
|
RUN_TEST(test_short_value_option_with_eq);
|
||||||
|
RUN_TEST(test_long_value_option_with_space);
|
||||||
|
RUN_TEST(test_long_value_option_without_space);
|
||||||
|
RUN_TEST(test_multi_short_option);
|
||||||
|
RUN_TEST(test_multi_short_option_with_value);
|
||||||
|
RUN_TEST(test_stop);
|
||||||
|
RUN_TEST(test_unknown_short_option);
|
||||||
|
RUN_TEST(test_unknown_long_option);
|
||||||
|
RUN_TEST(test_missing_short_value);
|
||||||
|
RUN_TEST(test_missing_long_value);
|
||||||
|
RUN_TEST(test_subcommand_without_arguments);
|
||||||
|
RUN_TEST(test_subcommand_with_arguments);
|
||||||
|
return r;
|
||||||
|
}
|
Loading…
Reference in New Issue