Create option parser interface

This is an interface that combines option parsing with help text formatting.
pull/185/head
Andri Yngvason 2022-11-13 15:43:46 +00:00
parent 33f98048d5
commit 125121613e
4 changed files with 256 additions and 51 deletions

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <stdio.h>
#include <getopt.h>
struct wv_option {
char short_opt;
const char* long_opt;
const char* schema;
const char* help;
};
struct option_parser {
const struct wv_option* options;
char short_opts[128];
struct option long_opts[128];
int n_opts;
};
void option_parser_init(struct option_parser* self, const struct wv_option* options);
void option_parser_print_usage(struct option_parser* self, FILE* stream);
int option_parser_getopt(struct option_parser* self, int argc, char* argv[]);

View File

@ -93,6 +93,7 @@ sources = [
'src/util.c', 'src/util.c',
'src/json-ipc.c', 'src/json-ipc.c',
'src/ctl-server.c', 'src/ctl-server.c',
'src/option-parser.c',
] ]
dependencies = [ dependencies = [

View File

@ -52,6 +52,7 @@
#include "usdt.h" #include "usdt.h"
#include "ctl-server.h" #include "ctl-server.h"
#include "util.h" #include "util.h"
#include "option-parser.h"
#ifdef ENABLE_PAM #ifdef ENABLE_PAM
#include "pam_auth.h" #include "pam_auth.h"
@ -738,36 +739,11 @@ void on_capture_done(struct screencopy* sc)
} }
} }
int wayvnc_usage(FILE* stream, int rc) int wayvnc_usage(struct option_parser* parser, FILE* stream, int rc)
{ {
static const char* usage = fprintf(stream, "Usage: wayvnc [options] [address [port]]\n\n");
"Usage: wayvnc [options] [address [port]]\n" option_parser_print_usage(parser, stream);
"\n" fprintf(stream, "\n");
" -C,--config=<path> Select a config file.\n"
" -g,--gpu Enable features that need GPU.\n"
" -o,--output=<name> Select output to capture.\n"
" -k,--keyboard=<layout>[-<variant>] Select keyboard layout with an\n"
" optional variant.\n"
" -s,--seat=<name> Select seat by name.\n"
" -S,--socket=<path> Wayvnc control socket path.\n"
" Default: $XDG_RUNTIME_DIR/wayvncctl\n"
" -r,--render-cursor Enable overlay cursor rendering.\n"
" -f,--max-fps=<fps> Set the rate limit (default 30).\n"
" -p,--show-performance Show performance counters.\n"
" -u,--unix-socket Create a UNIX domain socket\n"
" instead of TCP.\n"
" -d,--disable-input Disable all remote input\n"
" -V,--version Show version info.\n"
" -v,--verbose Be more verbose. Same as setting\n"
" --log-level=info.\n"
" -L,--log-level=<level> Set log level. The levels are:\n"
" error, warning, info, debug,\n"
" trace and quiet.\n"
" -h,--help Get help (this text).\n"
"\n";
fprintf(stream, "%s", usage);
return rc; return rc;
} }
@ -1011,32 +987,49 @@ int main(int argc, char* argv[])
int max_rate = 30; int max_rate = 30;
bool disable_input = false; bool disable_input = false;
static const char* shortopts = "C:go:k:s:S:rf:hpudVvL:";
int drm_fd MAYBE_UNUSED = -1; int drm_fd MAYBE_UNUSED = -1;
int log_level = NVNC_LOG_WARNING; int log_level = NVNC_LOG_WARNING;
static const struct option longopts[] = { static const struct wv_option opts[] = {
{ "config", required_argument, NULL, 'C' }, { 'C', "config", "<path>",
{ "gpu", no_argument, NULL, 'g' }, "Select a config file." },
{ "output", required_argument, NULL, 'o' }, { 'g', "gpu", NULL,
{ "keyboard", required_argument, NULL, 'k' }, "Enable features that need GPU." },
{ "seat", required_argument, NULL, 's' }, { 'o', "output", "<name>",
{ "socket", required_argument, NULL, 'S' }, "Select output to capture." },
{ "render-cursor", no_argument, NULL, 'r' }, { 'k', "keyboard", "<layout>[-<variant>]",
{ "max-fps", required_argument, NULL, 'f' }, "Select keyboard layout with an optional variant." },
{ "help", no_argument, NULL, 'h' }, { 's', "seat", "<name>",
{ "show-performance", no_argument, NULL, 'p' }, "Select seat by name." },
{ "unix-socket", no_argument, NULL, 'u' }, { 'S', "socket", "<path>",
{ "disable-input", no_argument, NULL, 'd' }, "Control socket path." },
{ "version", no_argument, NULL, 'V' }, { 'r', "render-cursor", NULL,
{ "verbose", no_argument, NULL, 'v' }, "Enable overlay cursor rendering." },
{ "log-level", required_argument, NULL, 'L' }, { 'f', "max-fps", "<fps>",
{ NULL, 0, NULL, 0 } "Set rate limit (default 30)." },
{ 'p', "performance", NULL,
"Show performance counters." },
{ 'u', "unix-socket", NULL,
"Create unix domain socket." },
{ 'd', "disable-input", NULL,
"Disable all remote input." },
{ 'V', "version", NULL,
"Show version info." },
{ 'v', "verbose", NULL,
"Be more verbose. Same as setting --log-level=info" },
{ 'L', "log-level", "<level>",
"Set log level. The levels are: error, warning, info, debug trace and quiet." },
{ 'h', "help", NULL,
"Get help (this text)." },
{ '\0', NULL, NULL, NULL }
}; };
struct option_parser option_parser;
option_parser_init(&option_parser, opts);
while (1) { while (1) {
int c = getopt_long(argc, argv, shortopts, longopts, NULL); int c = option_parser_getopt(&option_parser, argc, argv);
if (c < 0) if (c < 0)
break; break;
@ -1082,15 +1075,15 @@ int main(int argc, char* argv[])
if (log_level < 0) { if (log_level < 0) {
fprintf(stderr, "Invalid log level: %s\n", fprintf(stderr, "Invalid log level: %s\n",
optarg); optarg);
return wayvnc_usage(stderr, 1); return wayvnc_usage(&option_parser, stderr, 1);
} }
break; break;
case 'V': case 'V':
return show_version(); return show_version();
case 'h': case 'h':
return wayvnc_usage(stdout, 0); return wayvnc_usage(&option_parser, stdout, 0);
default: default:
return wayvnc_usage(stderr, 1); return wayvnc_usage(&option_parser, stderr, 1);
} }
} }

172
src/option-parser.c 100644
View File

@ -0,0 +1,172 @@
/*
* Copyright (c) 2022 Andri Yngvason
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "option-parser.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
static int count_options(const struct wv_option* opts)
{
int n = 0;
while (opts[n].short_opt || opts[n].long_opt)
n++;
return n;
}
void option_parser_init(struct option_parser* self,
const struct wv_option* options)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->n_opts = count_options(options);
int short_opt_index = 0;
int long_opt_index = 0;
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)
{
int max_width = 0;
for (int i = 0; i < n; ++i) {
int width = 0;
if (opts[i].short_opt)
width += 2;
if (opts[i].long_opt)
width += 2 + strlen(opts[i].long_opt);
if (opts[i].short_opt && opts[i].long_opt)
width += 1; // for ','
if (opts[i].schema) {
width += strlen(opts[i].schema);
if (opts[i].long_opt)
width += 1; // for '='
}
if (width > max_width)
max_width = width;
}
return max_width;
}
static void reflow_text(char* dst, const char* src, int width)
{
int line_len = 0;
int last_space_pos = 0;
int dst_len = 0;
int i = 0;
while (src[i]) {
char c = src[i];
if (line_len > width) {
assert(last_space_pos > 0);
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
}
if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
++i;
++line_len;
}
dst[dst_len] = '\0';
}
static void format_option(const struct wv_option* opt, int left_col_width,
FILE* stream)
{
assert(opt->help);
int n_chars = fprintf(stream, " ");
if (opt->short_opt)
n_chars += fprintf(stream, "-%c", opt->short_opt);
if (opt->long_opt)
n_chars += fprintf(stream, "%s--%s",
opt->short_opt ? "," : "", opt->long_opt);
if (opt->schema)
n_chars += fprintf(stream, "%s%s",
opt->long_opt ? "=" : "", opt->schema);
n_chars += fprintf(stream, "%*s", left_col_width - n_chars + 8, "");
int right_col_width = 80 - 8 - left_col_width;
assert(right_col_width >= 0);
char help[256];
reflow_text(help, opt->help, right_col_width);
char* line = strtok(help, "\n");
fprintf(stream, "%s\n", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", left_col_width + 8, "", line);
}
}
void option_parser_print_usage(struct option_parser* self, FILE* stream)
{
int left_col_width = get_left_col_width(self->options, self->n_opts);
for (int i = 0; i < self->n_opts; ++i) {
format_option(&self->options[i], left_col_width, stream);
}
}
int option_parser_getopt(struct option_parser* self, int argc, char* argv[])
{
return getopt_long(argc, argv, self->short_opts, self->long_opts, NULL);
}