Create option parser interface
This is an interface that combines option parsing with help text formatting.pull/185/head
parent
33f98048d5
commit
125121613e
|
@ -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[]);
|
|
@ -93,6 +93,7 @@ sources = [
|
|||
'src/util.c',
|
||||
'src/json-ipc.c',
|
||||
'src/ctl-server.c',
|
||||
'src/option-parser.c',
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
|
|
95
src/main.c
95
src/main.c
|
@ -52,6 +52,7 @@
|
|||
#include "usdt.h"
|
||||
#include "ctl-server.h"
|
||||
#include "util.h"
|
||||
#include "option-parser.h"
|
||||
|
||||
#ifdef ENABLE_PAM
|
||||
#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 =
|
||||
"Usage: wayvnc [options] [address [port]]\n"
|
||||
"\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);
|
||||
|
||||
fprintf(stream, "Usage: wayvnc [options] [address [port]]\n\n");
|
||||
option_parser_print_usage(parser, stream);
|
||||
fprintf(stream, "\n");
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -1011,32 +987,49 @@ int main(int argc, char* argv[])
|
|||
int max_rate = 30;
|
||||
bool disable_input = false;
|
||||
|
||||
static const char* shortopts = "C:go:k:s:S:rf:hpudVvL:";
|
||||
int drm_fd MAYBE_UNUSED = -1;
|
||||
|
||||
int log_level = NVNC_LOG_WARNING;
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{ "config", required_argument, NULL, 'C' },
|
||||
{ "gpu", no_argument, NULL, 'g' },
|
||||
{ "output", required_argument, NULL, 'o' },
|
||||
{ "keyboard", required_argument, NULL, 'k' },
|
||||
{ "seat", required_argument, NULL, 's' },
|
||||
{ "socket", required_argument, NULL, 'S' },
|
||||
{ "render-cursor", no_argument, NULL, 'r' },
|
||||
{ "max-fps", required_argument, NULL, 'f' },
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "show-performance", no_argument, NULL, 'p' },
|
||||
{ "unix-socket", no_argument, NULL, 'u' },
|
||||
{ "disable-input", no_argument, NULL, 'd' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "verbose", no_argument, NULL, 'v' },
|
||||
{ "log-level", required_argument, NULL, 'L' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
static const struct wv_option opts[] = {
|
||||
{ 'C', "config", "<path>",
|
||||
"Select a config file." },
|
||||
{ 'g', "gpu", NULL,
|
||||
"Enable features that need GPU." },
|
||||
{ 'o', "output", "<name>",
|
||||
"Select output to capture." },
|
||||
{ 'k', "keyboard", "<layout>[-<variant>]",
|
||||
"Select keyboard layout with an optional variant." },
|
||||
{ 's', "seat", "<name>",
|
||||
"Select seat by name." },
|
||||
{ 'S', "socket", "<path>",
|
||||
"Control socket path." },
|
||||
{ 'r', "render-cursor", NULL,
|
||||
"Enable overlay cursor rendering." },
|
||||
{ 'f', "max-fps", "<fps>",
|
||||
"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) {
|
||||
int c = getopt_long(argc, argv, shortopts, longopts, NULL);
|
||||
int c = option_parser_getopt(&option_parser, argc, argv);
|
||||
if (c < 0)
|
||||
break;
|
||||
|
||||
|
@ -1082,15 +1075,15 @@ int main(int argc, char* argv[])
|
|||
if (log_level < 0) {
|
||||
fprintf(stderr, "Invalid log level: %s\n",
|
||||
optarg);
|
||||
return wayvnc_usage(stderr, 1);
|
||||
return wayvnc_usage(&option_parser, stderr, 1);
|
||||
}
|
||||
break;
|
||||
case 'V':
|
||||
return show_version();
|
||||
case 'h':
|
||||
return wayvnc_usage(stdout, 0);
|
||||
return wayvnc_usage(&option_parser, stdout, 0);
|
||||
default:
|
||||
return wayvnc_usage(stderr, 1);
|
||||
return wayvnc_usage(&option_parser, stderr, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue