From 2f921d6dec2894d7211c5be46e870931e90cb9d2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 29 Apr 2025 10:26:28 +0530 Subject: [PATCH] Proper handling of disabled cli flags --- kitty/cli_stub.py | 3 -- kitty/launcher/cli-parser.h | 52 +++++++++++++++++++----------- kitty/simple_cli_definitions.py | 57 +++++++++++++++++++-------------- 3 files changed, 66 insertions(+), 46 deletions(-) diff --git a/kitty/cli_stub.py b/kitty/cli_stub.py index aa4f15db1..5944b8555 100644 --- a/kitty/cli_stub.py +++ b/kitty/cli_stub.py @@ -6,9 +6,6 @@ from collections.abc import Sequence class CLIOptions: - # these are present here because the parser doesn't set them on macOS - cls: str = '' - name: str = '' def __repr__(self) -> str: return repr(vars(self)) diff --git a/kitty/launcher/cli-parser.h b/kitty/launcher/cli-parser.h index 6226ce4f1..54a6a620d 100644 --- a/kitty/launcher/cli-parser.h +++ b/kitty/launcher/cli-parser.h @@ -64,7 +64,7 @@ typedef struct FlagSpec { typedef struct CLISpec { cli_hash value_map; alias_hash alias_map; - flag_hash flag_map; + flag_hash flag_map, disabled_map; char **argv; int argc; // leftover args char **original_argv; int original_argc; // original args const char* errmsg; @@ -274,6 +274,7 @@ alloc_cli_spec(CLISpec *spec) { vt_init(&spec->value_map); vt_init(&spec->alias_map); vt_init(&spec->flag_map); + vt_init(&spec->disabled_map); } static void @@ -284,6 +285,7 @@ dealloc_cli_spec(void *v) { vt_cleanup(&spec->value_map); vt_cleanup(&spec->alias_map); vt_cleanup(&spec->flag_map); + vt_cleanup(&spec->disabled_map); } #define RAII_CLISpec(name) __attribute__((cleanup(dealloc_cli_spec))) CLISpec name = {0}; alloc_cli_spec(&name) @@ -428,6 +430,31 @@ get_string_cli_val(CLISpec *spec, const char *name) { } #endif +static bool +clival_as_python(const CLIValue *v, PyObject *is_seen, const char *dest, PyObject *ans) { +#define S(fv) { \ + RAII_PyObject(temp, Py_BuildValue("NO", fv, is_seen)); if (!temp) return false; \ + if (PyDict_SetItemString(ans, dest, temp) != 0) return false; \ +} + switch (v->type) { + case CLI_VALUE_BOOL: S(PyBool_FromLong((long)v->boolval)); break; + case CLI_VALUE_STRING: if (v->strval) { S(PyUnicode_FromString(v->strval)); } else { S(Py_NewRef(Py_None)); } break; + case CLI_VALUE_CHOICE: S(PyUnicode_FromString(v->strval)); break; + case CLI_VALUE_INT: S(PyLong_FromLongLong(v->intval)); break; + case CLI_VALUE_FLOAT: S(PyFloat_FromDouble(v->floatval)); break; + case CLI_VALUE_LIST: { + RAII_PyObject(l, PyList_New(v->listval.count)); if (!l) return false; + for (size_t i = 0; i < v->listval.count; i++) { + PyObject *x = PyUnicode_FromString(v->listval.items[i]); if (!x) return false; + PyList_SET_ITEM(l, i, x); + } + S(Py_NewRef(l)); + } break; + } +#undef S + return true; +} + static PyObject* cli_parse_result_as_python(CLISpec *spec) { if (PyErr_Occurred()) return NULL; @@ -440,25 +467,12 @@ cli_parse_result_as_python(CLISpec *spec) { cli_hash_itr i = vt_get(&spec->value_map, flag->dest); PyObject *is_seen = vt_is_end(i) ? Py_False : Py_True; const CLIValue *v = is_seen == Py_True ? &i.data->val : &flag->defval; -#define S(fv) { RAII_PyObject(temp, Py_BuildValue("NO", fv, is_seen)); if (!temp) return NULL; \ - if (PyDict_SetItemString(ans, flag->dest, temp) != 0) return NULL;} - switch (v->type) { - case CLI_VALUE_BOOL: S(PyBool_FromLong((long)v->boolval)); break; - case CLI_VALUE_STRING: if (v->strval) { S(PyUnicode_FromString(v->strval)); } else { S(Py_NewRef(Py_None)); } break; - case CLI_VALUE_CHOICE: S(PyUnicode_FromString(v->strval)); break; - case CLI_VALUE_INT: S(PyLong_FromLongLong(v->intval)); break; - case CLI_VALUE_FLOAT: S(PyFloat_FromDouble(v->floatval)); break; - case CLI_VALUE_LIST: { - RAII_PyObject(l, PyList_New(v->listval.count)); if (!l) return NULL; - for (size_t i = 0; i < v->listval.count; i++) { - PyObject *x = PyUnicode_FromString(v->listval.items[i]); if (!x) return NULL; - PyList_SET_ITEM(l, i, x); - } - S(Py_NewRef(l)); - } break; - } + if (!clival_as_python(v, is_seen, flag->dest, ans)) return NULL; + } + flag_map_for_loop(&spec->disabled_map) { + const FlagSpec *flag = &itr.data->val; + if (!clival_as_python(&flag->defval, Py_False, flag->dest, ans)) return NULL; } -#undef S RAII_PyObject(leftover_args, PyList_New(spec->argc)); if (!leftover_args) return NULL; for (int i = 0; i < spec->argc; i++) { PyObject *t = PyUnicode_FromString(spec->argv[i]); diff --git a/kitty/simple_cli_definitions.py b/kitty/simple_cli_definitions.py index 9b52a87ba..f9347fb8d 100644 --- a/kitty/simple_cli_definitions.py +++ b/kitty/simple_cli_definitions.py @@ -259,8 +259,34 @@ def add_list_values(*values: str) -> Iterator[str]: yield f'\tflag.defval.listval.items[{n}] = {c_str(value)};' +def generate_c_for_opt(name: str, defval: Any, opt: OptionDict) -> Iterator[str]: + yield f'\tflag = (FlagSpec){{.dest={c_str(name)},}};' + match opt['type']: + case 'bool-set' | 'bool-reset': + yield '\tflag.defval.type = CLI_VALUE_BOOL;' + yield f'\tflag.defval.boolval = {"true" if defval else "false"};' + case 'int': + yield '\tflag.defval.type = CLI_VALUE_INT;' + yield f'\tflag.defval.intval = {defval};' + case 'float': + yield '\tflag.defval.type = CLI_VALUE_FLOAT;' + yield f'\tflag.defval.floatval = {defval};' + case 'list': + yield '\tflag.defval.type = CLI_VALUE_LIST;' + if defval: + yield from add_list_values(*defval) + case 'choices': + yield '\tflag.defval.type = CLI_VALUE_CHOICE;' + yield f'\tflag.defval.strval = {c_str(defval)};' + yield from add_list_values(*opt['choices']) + case _: + yield '\tflag.defval.type = CLI_VALUE_STRING;' + yield f'\tflag.defval.strval = {"NULL" if defval is None else c_str(defval)};' + + def generate_c_parser_for(funcname: str, spec: str) -> Iterator[str]: - names_map, _, defaults_map = get_option_maps(parse_option_spec(spec)[0]) + seq, disabled = parse_option_spec(spec) + names_map, _, defaults_map = get_option_maps(seq) if 'help' not in names_map: names_map['help'] = {'type': 'bool-set', 'aliases': ('--help', '-h')} # type: ignore defaults_map['help'] = False @@ -273,30 +299,13 @@ def generate_c_parser_for(funcname: str, spec: str) -> Iterator[str]: for name, opt in names_map.items(): for alias in opt['aliases']: yield f'\tif (vt_is_end(vt_insert(&spec->alias_map, {c_str(alias)}, {c_str(name)}))) OOM;' - yield f'\tflag = (FlagSpec){{.dest={c_str(name)},}};' - defval = defaults_map[name] - match opt['type']: - case 'bool-set' | 'bool-reset': - yield '\tflag.defval.type = CLI_VALUE_BOOL;' - yield f'\tflag.defval.boolval = {"true" if defval else "false"};' - case 'int': - yield '\tflag.defval.type = CLI_VALUE_INT;' - yield f'\tflag.defval.intval = {defval};' - case 'float': - yield '\tflag.defval.type = CLI_VALUE_FLOAT;' - yield f'\tflag.defval.floatval = {defval};' - case 'list': - yield '\tflag.defval.type = CLI_VALUE_LIST;' - if defval: - yield from add_list_values(*defval) - case 'choices': - yield '\tflag.defval.type = CLI_VALUE_CHOICE;' - yield f'\tflag.defval.strval = {c_str(defval)};' - yield from add_list_values(*opt['choices']) - case _: - yield '\tflag.defval.type = CLI_VALUE_STRING;' - yield f'\tflag.defval.strval = {"NULL" if defval is None else c_str(defval)};' + yield from generate_c_for_opt(name, defaults_map[name], opt) yield '\tif (vt_is_end(vt_insert(&spec->flag_map, flag.dest, flag))) OOM;' + for d in disabled: + if not isinstance(d, str): + yield from generate_c_for_opt(d['dest'], defval_for_opt(d), d) + yield '\tif (vt_is_end(vt_insert(&spec->disabled_map, flag.dest, flag))) OOM;' + yield '\tparse_cli_loop(spec, true, argc, argv);' yield '}'