mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-21 12:42:25 +00:00
Get basic C cli parsing working
This commit is contained in:
parent
527f30ffcc
commit
62699f6799
5 changed files with 187 additions and 101 deletions
98
kitty/cli.py
98
kitty/cli.py
|
|
@ -4,17 +4,16 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import deque
|
||||
from collections.abc import Callable, Iterator, Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from re import Match
|
||||
from typing import Any, TypeVar, Union, cast
|
||||
from typing import Any, NoReturn, TypeVar, Union, cast
|
||||
|
||||
from .cli_stub import CLIOptions
|
||||
from .conf.utils import resolve_config
|
||||
from .constants import appname, clear_handled_signals, config_dir, default_pager_for_help, defconf, is_macos, str_version, website_url
|
||||
from .fast_data_types import wcswidth
|
||||
from .fast_data_types import parse_cli_from_spec, wcswidth
|
||||
from .options.types import Options as KittyOpts
|
||||
from .types import run_once
|
||||
from .typing import BadLineType, TypedDict
|
||||
|
|
@ -101,9 +100,9 @@ class CompletionSpec:
|
|||
class OptionDict(TypedDict):
|
||||
dest: str
|
||||
name: str
|
||||
aliases: frozenset[str]
|
||||
aliases: tuple[str, ...]
|
||||
help: str
|
||||
choices: frozenset[str]
|
||||
choices: tuple[str, ...]
|
||||
type: str
|
||||
default: str | None
|
||||
condition: bool
|
||||
|
|
@ -410,7 +409,7 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
|
|||
disabled: OptionSpecSeq = []
|
||||
mpat = re.compile('([a-z]+)=(.+)')
|
||||
current_cmd: OptionDict = {
|
||||
'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(),
|
||||
'dest': '', 'aliases': (), 'help': '', 'choices': (),
|
||||
'type': '', 'condition': False, 'default': None, 'completion': CompletionSpec(), 'name': ''
|
||||
}
|
||||
empty_cmd = current_cmd
|
||||
|
|
@ -430,8 +429,8 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
|
|||
parts = line.split(' ')
|
||||
defdest = parts[0][2:].replace('-', '_')
|
||||
current_cmd = {
|
||||
'dest': defdest, 'aliases': frozenset(parts), 'help': '',
|
||||
'choices': frozenset(), 'type': '', 'name': defdest,
|
||||
'dest': defdest, 'aliases': tuple(parts), 'help': '',
|
||||
'choices': tuple(), 'type': '', 'name': defdest,
|
||||
'default': None, 'condition': True, 'completion': CompletionSpec(),
|
||||
}
|
||||
state = METADATA
|
||||
|
|
@ -450,7 +449,7 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
|
|||
current_cmd['type'] = 'choices'
|
||||
if current_cmd['type'] != 'choices':
|
||||
raise ValueError(f'Cannot specify choices for an option of type: {current_cmd["type"]}')
|
||||
current_cmd['choices'] = frozenset(vals)
|
||||
current_cmd['choices'] = tuple(vals)
|
||||
if current_cmd['default'] is None:
|
||||
current_cmd['default'] = vals[0]
|
||||
else:
|
||||
|
|
@ -800,18 +799,15 @@ class Options:
|
|||
self.names_map[name] = opt
|
||||
self.values_map[name] = defval_for_opt(opt)
|
||||
|
||||
def check_for_standard_flag(self, alias: str) -> None:
|
||||
if alias in ('-h', '--help'):
|
||||
if self.do_print:
|
||||
print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname)
|
||||
raise SystemExit(0)
|
||||
opt = self.alias_map.get(alias)
|
||||
if opt is None:
|
||||
raise SystemExit(f'Unknown option: {emph(alias)}')
|
||||
if opt['dest'] == 'version':
|
||||
if self.do_print:
|
||||
print(version())
|
||||
raise SystemExit(0)
|
||||
def handle_help(self) -> NoReturn:
|
||||
if self.do_print:
|
||||
print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname)
|
||||
raise SystemExit(0)
|
||||
|
||||
def handle_version(self) -> NoReturn:
|
||||
if self.do_print:
|
||||
print(version())
|
||||
raise SystemExit(0)
|
||||
|
||||
def is_bool(self, alias: str) -> bool:
|
||||
opt = self.alias_map[alias]
|
||||
|
|
@ -854,58 +850,16 @@ class Options:
|
|||
|
||||
|
||||
def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: list[str] | None = None) -> list[str]:
|
||||
NORMAL, EXPECTING_ARG = 'NORMAL', 'EXPECTING_ARG'
|
||||
state = NORMAL
|
||||
dargs = deque(sys.argv[1:] if args is None else args)
|
||||
leftover_args: list[str] = []
|
||||
current_option = None
|
||||
payload: str | None = None
|
||||
try:
|
||||
vals, leftover_args = parse_cli_from_spec(sys.argv[1:] if args is None else args, oc.names_map, oc.values_map)
|
||||
except Exception as e:
|
||||
raise SystemExit(str(e))
|
||||
|
||||
while dargs:
|
||||
arg = dargs.popleft()
|
||||
if state is NORMAL:
|
||||
if arg.startswith('-'):
|
||||
is_long_opt = arg.startswith('--')
|
||||
if is_long_opt and arg == '--':
|
||||
leftover_args = list(dargs)
|
||||
break
|
||||
flag, has_equal, payload = arg.partition('=')
|
||||
if not has_equal:
|
||||
payload = None
|
||||
if is_long_opt:
|
||||
oc.check_for_standard_flag(flag)
|
||||
if oc.is_bool(flag):
|
||||
oc.process_arg(flag, payload)
|
||||
continue
|
||||
if not has_equal:
|
||||
current_option = flag
|
||||
state = EXPECTING_ARG
|
||||
continue
|
||||
oc.process_arg(flag, payload)
|
||||
else:
|
||||
letters = flag[1:]
|
||||
for letter in letters[:-1]:
|
||||
flag = f'-{letter}'
|
||||
oc.check_for_standard_flag(flag)
|
||||
oc.process_arg(flag)
|
||||
flag = f'-{letters[-1]}'
|
||||
oc.check_for_standard_flag(flag)
|
||||
if oc.is_bool(flag) or payload is not None:
|
||||
oc.process_arg(flag, payload)
|
||||
else:
|
||||
current_option = flag
|
||||
state = EXPECTING_ARG
|
||||
continue
|
||||
else:
|
||||
leftover_args = [arg] + list(dargs)
|
||||
break
|
||||
elif current_option is not None:
|
||||
oc.process_arg(current_option, arg)
|
||||
current_option, state = None, NORMAL
|
||||
if state is EXPECTING_ARG:
|
||||
raise SystemExit(f'An argument is required for the option: {emph(arg)}')
|
||||
|
||||
for key, val in oc.values_map.items():
|
||||
for key, (val, is_seen) in vals.items():
|
||||
if key == 'version' and is_seen and val:
|
||||
oc.handle_version()
|
||||
elif key == 'help' and is_seen and val:
|
||||
oc.handle_help()
|
||||
setattr(ans, key, val)
|
||||
for opt in disabled:
|
||||
if not isinstance(opt, str):
|
||||
|
|
|
|||
|
|
@ -678,10 +678,12 @@ py_get_config_dir(PyObject *self UNUSED, PyObject *args UNUSED) {
|
|||
return PyUnicode_FromString("");
|
||||
}
|
||||
|
||||
#include "launcher/cli-parser.h"
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(replace_c0_codes_except_nl_space_tab, METH_O),
|
||||
METHODB(read_file, METH_O),
|
||||
{"parse_cli_from_spec", parse_cli_from_python_spec, METH_VARARGS, ""},
|
||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||
{"expanduser", (PyCFunction)expanduser, METH_O, ""},
|
||||
{"abspath", (PyCFunction)abspath, METH_O, ""},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import termios
|
|||
from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload
|
||||
|
||||
from kitty.boss import Boss
|
||||
from kitty.cli import OptionDict
|
||||
from kitty.fonts import VariableData
|
||||
from kitty.fonts.render import FontObject
|
||||
from kitty.marks import MarkerFunc
|
||||
|
|
@ -1704,6 +1705,7 @@ def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes],
|
|||
def run_with_activation_token(func: Callable[[str], None]) -> bool: ...
|
||||
def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ...
|
||||
def toggle_os_window_visibility(os_window_id: int, visible: Literal[True, False] = ...) -> bool: ...
|
||||
def parse_cli_from_spec(args: list[str], names_map: dict[str, OptionDict], defval_map: dict[str, Any]) -> tuple[dict[str, tuple[Any, bool]], list[str]]: ...
|
||||
def layer_shell_config_for_os_window(os_window_id: int) -> dict[str, Any] | None: ...
|
||||
def set_layer_shell_config(os_window_id: int, cfg: LayerShellConfig) -> bool: ...
|
||||
def wrapped_kitten_names() -> List[str]: ...
|
||||
|
|
|
|||
|
|
@ -7,12 +7,29 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "listobject.h"
|
||||
#include <Python.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef RAII_ALLOC
|
||||
static inline void cleanup_decref2(PyObject **p) { Py_CLEAR(*p); }
|
||||
#define RAII_PyObject(name, initializer) __attribute__((cleanup(cleanup_decref2))) PyObject *name = initializer
|
||||
|
||||
static inline void cleanup_free(void *p) { free(*(void**)p); }
|
||||
#define RAII_ALLOC(type, name, initializer) __attribute__((cleanup(cleanup_free))) type *name = initializer
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
cleanup_argv(void *p) {
|
||||
for (char **argv = *(void **)p; *argv; argv++) free(*argv);
|
||||
free(*(void**)p);
|
||||
}
|
||||
#define RAII_ARGV(name, argc) __attribute__((cleanup(cleanup_argv))) char** name = calloc(argc + 1, sizeof(char*))
|
||||
|
||||
typedef enum CLIValueType { CLI_VALUE_STRING, CLI_VALUE_BOOL, CLI_VALUE_INT, CLI_VALUE_FLOAT, CLI_VALUE_LIST, CLI_VALUE_CHOICE } CLIValueType;
|
||||
typedef struct CLIValue {
|
||||
CLIValueType type;
|
||||
|
|
@ -23,6 +40,7 @@ typedef struct CLIValue {
|
|||
struct {
|
||||
const char* * items;
|
||||
size_t count, capacity;
|
||||
bool needs_free;
|
||||
} listval;
|
||||
} CLIValue;
|
||||
|
||||
|
|
@ -40,7 +58,7 @@ typedef struct CLIValue {
|
|||
|
||||
typedef struct FlagSpec {
|
||||
CLIValue defval;
|
||||
const char *dest, *choices, *aliases;
|
||||
const char *dest;
|
||||
} FlagSpec;
|
||||
|
||||
#define NAME flag_hash
|
||||
|
|
@ -55,7 +73,7 @@ typedef struct CLISpec {
|
|||
alias_hash alias_map;
|
||||
flag_hash flag_map;
|
||||
char **argv; int argc; // leftover args
|
||||
char err[512];
|
||||
char err[1024];
|
||||
} CLISpec;
|
||||
|
||||
static void
|
||||
|
|
@ -66,16 +84,21 @@ out_of_memory(int line) {
|
|||
#define OOM out_of_memory(__LINE__)
|
||||
|
||||
|
||||
static bool
|
||||
report_unknown_alias(CLISpec *spec, const char *alias) {
|
||||
snprintf(spec->err, sizeof(spec->err), "Unknown flag: %s use --help", alias);
|
||||
return false;
|
||||
static const char*
|
||||
dest_for_alias(CLISpec *spec, const char *alias) {
|
||||
alias_hash_itr itr = vt_get(&spec->alias_map, alias);
|
||||
if (vt_is_end(itr)) {
|
||||
snprintf(spec->err, sizeof(spec->err), "Unknown flag: %s use --help", alias);
|
||||
return NULL;
|
||||
}
|
||||
return itr.data->val;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_alias_bool(CLISpec* spec, const char *alias) {
|
||||
flag_hash_itr itr = vt_get(&spec->flag_map, alias);
|
||||
if (!itr.data) return report_unknown_alias(spec, alias);
|
||||
const char *dest = dest_for_alias(spec, alias);
|
||||
if (!dest) return false;
|
||||
flag_hash_itr itr = vt_get(&spec->flag_map, dest);
|
||||
return itr.data->val->defval.type == CLI_VALUE_BOOL;
|
||||
}
|
||||
|
||||
|
|
@ -97,8 +120,9 @@ add_list_value(CLISpec *spec, const char *dest, const char *val) {
|
|||
|
||||
static bool
|
||||
process_cli_arg(CLISpec* spec, const char *alias, const char *payload) {
|
||||
flag_hash_itr itr = vt_get(&spec->flag_map, alias);
|
||||
if (!itr.data) return report_unknown_alias(spec, alias);
|
||||
const char *dest = dest_for_alias(spec, alias);
|
||||
if (!dest) return false;
|
||||
flag_hash_itr itr = vt_get(&spec->flag_map, dest);
|
||||
const FlagSpec *flag = itr.data->val;
|
||||
CLIValue val = {.type=flag->defval.type};
|
||||
#define streq(q) (strcmp(payload, #q) == 0)
|
||||
|
|
@ -117,11 +141,14 @@ process_cli_arg(CLISpec* spec, const char *alias, const char *payload) {
|
|||
break;
|
||||
case CLI_VALUE_CHOICE:
|
||||
val.strval = NULL;
|
||||
for (const char* q = flag->choices; q; q++) if (streq(q)) { val.strval = payload; break; }
|
||||
for (size_t c = 0; c < flag->defval.listval.count; c++) {
|
||||
if (strcmp(payload, flag->defval.listval.items[c]) == 0) { val.strval = payload; break; }
|
||||
}
|
||||
if (!val.strval) {
|
||||
int n = snprintf(spec->err, sizeof(spec->err), "%s is an invalid value for %s. Valid values are:",
|
||||
payload[0] ? payload : "<empty>", alias);
|
||||
for (const char* q = flag->choices; q; q++) n += snprintf(spec->err + n, sizeof(spec->err) - n, " %s,", q);
|
||||
for (size_t c = 0; c < flag->defval.listval.count; c++)
|
||||
n += snprintf(spec->err + n, sizeof(spec->err) - n, " %s,", flag->defval.listval.items[c]);
|
||||
spec->err[n-1] = '.';
|
||||
return false;
|
||||
}
|
||||
|
|
@ -146,37 +173,37 @@ process_cli_arg(CLISpec* spec, const char *alias, const char *payload) {
|
|||
}
|
||||
|
||||
static void
|
||||
alloc_cli_spec(CLISpec *spec, const FlagSpec *flags) {
|
||||
alloc_cli_spec(CLISpec *spec) {
|
||||
vt_init(&spec->value_map);
|
||||
vt_init(&spec->alias_map);
|
||||
vt_init(&spec->flag_map);
|
||||
for (const FlagSpec *flag = flags; flag != NULL; flag++) {
|
||||
if (vt_is_end(vt_insert(&spec->flag_map, flag->dest, flag))) OOM;
|
||||
for (const char *alias = flag->aliases; alias != NULL; alias++) {
|
||||
if (vt_is_end(vt_insert(&spec->alias_map, alias, flag->dest))) OOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dealloc_cli_value(CLIValue v) {
|
||||
free((void*)v.listval.items);
|
||||
if (v.listval.needs_free) free((void*)v.listval.items);
|
||||
}
|
||||
|
||||
static void
|
||||
dealloc_cli_spec(CLISpec *spec) {
|
||||
dealloc_cli_spec(void *v) {
|
||||
CLISpec *spec = v;
|
||||
value_map_for_loop(&spec->value_map) {
|
||||
dealloc_cli_value(itr.data->val);
|
||||
}
|
||||
flag_map_for_loop(&spec->flag_map) {
|
||||
dealloc_cli_value(itr.data->val->defval);
|
||||
}
|
||||
vt_cleanup(&spec->value_map);
|
||||
vt_cleanup(&spec->alias_map);
|
||||
vt_cleanup(&spec->flag_map);
|
||||
}
|
||||
|
||||
#define RAII_CLISpec(name) __attribute__((cleanup(dealloc_cli_spec))) CLISpec name = {0}; alloc_cli_spec(&name)
|
||||
|
||||
static bool
|
||||
parse_cli_loop(CLISpec *spec, int argc, char **argv) { // argv must contain arg1 and beyond
|
||||
enum { NORMAL, EXPECTING_ARG } state = NORMAL;
|
||||
spec->argc = 0; spec->argv = NULL;
|
||||
spec->argc = 0; spec->argv = NULL; spec->err[0] = 0;
|
||||
char flag[3] = {'-', 0, 0};
|
||||
const char *current_option = NULL;
|
||||
for (int i = 0; i < argc; i++) {
|
||||
|
|
@ -239,3 +266,105 @@ parse_cli_loop(CLISpec *spec, int argc, char **argv) { // argv must contain arg
|
|||
if (state == EXPECTING_ARG) snprintf(spec->err, sizeof(spec->err), "The %s flag must be followed by an argument.", current_option ? current_option : "");
|
||||
return spec->err[0] == 0;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cli_parse_result_as_python(CLISpec *spec) {
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
if (spec->err[0]) {
|
||||
PyErr_SetString(PyExc_ValueError, spec->err); return NULL;
|
||||
}
|
||||
RAII_PyObject(ans, PyDict_New()); if (!ans) return NULL;
|
||||
flag_map_for_loop(&spec->flag_map) {
|
||||
const FlagSpec *flag = itr.data->val;
|
||||
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;
|
||||
}
|
||||
}
|
||||
#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]);
|
||||
if (!t) return NULL;
|
||||
PyList_SET_ITEM(leftover_args, i, t);
|
||||
}
|
||||
return Py_BuildValue("OO", ans, leftover_args);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
parse_cli_from_python_spec(PyObject *self, PyObject *args) {
|
||||
(void)self; PyObject *pyargs, *names_map, *defval_map;
|
||||
if (!PyArg_ParseTuple(args, "O!O!O!", &PyList_Type, &pyargs, &PyDict_Type, &names_map, &PyDict_Type, &defval_map)) return NULL;
|
||||
int argc = PyList_GET_SIZE(pyargs);
|
||||
RAII_ARGV(argv, argc); if (!argv) return PyErr_NoMemory();
|
||||
for (int i = 0; i < argc; i++) {
|
||||
argv[i] = strdup(PyUnicode_AsUTF8(PyList_GET_ITEM(pyargs, i)));
|
||||
if (!argv[i]) return PyErr_NoMemory();
|
||||
}
|
||||
RAII_ALLOC(FlagSpec, flags, calloc(PyDict_GET_SIZE(names_map), sizeof(FlagSpec))); if (!flags) return PyErr_NoMemory();
|
||||
RAII_CLISpec(spec);
|
||||
PyObject *key = NULL, *opt = NULL;
|
||||
Py_ssize_t pos = 0, flag_num = 0;
|
||||
while (PyDict_Next(names_map, &pos, &key, &opt)) {
|
||||
FlagSpec *flag = &flags[flag_num++];
|
||||
flag->dest = PyUnicode_AsUTF8(key);
|
||||
PyObject *pytype = PyDict_GetItemString(opt, "type");
|
||||
const char *type = pytype ? PyUnicode_AsUTF8(pytype) : "";
|
||||
PyObject *defval = PyDict_GetItemWithError(defval_map, key); if (!defval && PyErr_Occurred()) return NULL;
|
||||
PyObject *pyaliases = PyDict_GetItemString(opt, "aliases");
|
||||
for (int a = 0; a < PyTuple_GET_SIZE(pyaliases); a++) {
|
||||
const char *alias = PyUnicode_AsUTF8(PyTuple_GET_ITEM(pyaliases, a));
|
||||
if (vt_is_end(vt_insert(&spec.alias_map, alias, flag->dest))) return PyErr_NoMemory();
|
||||
}
|
||||
if (strstr(type, "bool-") == type) {
|
||||
flag->defval.type = CLI_VALUE_BOOL;
|
||||
flag->defval.boolval = PyObject_IsTrue(defval);
|
||||
} else if (strcmp(type, "int") == 0) {
|
||||
flag->defval.type = CLI_VALUE_INT;
|
||||
flag->defval.intval = PyLong_AsLongLong(defval);
|
||||
} else if (strcmp(type, "float") == 0) {
|
||||
flag->defval.type = CLI_VALUE_FLOAT;
|
||||
flag->defval.floatval = PyFloat_AsDouble(defval);
|
||||
} else if (strcmp(type, "list") == 0) {
|
||||
flag->defval.type = CLI_VALUE_LIST;
|
||||
} else if (strcmp(type, "choices") == 0) {
|
||||
flag->defval.type = CLI_VALUE_CHOICE;
|
||||
flag->defval.strval = PyUnicode_AsUTF8(defval);
|
||||
PyObject *pyc = PyDict_GetItemString(opt, "choices");
|
||||
flag->defval.listval.items = malloc(PyTuple_GET_SIZE(pyc) * sizeof(char*));
|
||||
if (!flag->defval.listval.items) return PyErr_NoMemory();
|
||||
flag->defval.listval.count = PyTuple_GET_SIZE(pyc);
|
||||
flag->defval.listval.needs_free = true;
|
||||
flag->defval.listval.capacity = PyTuple_GET_SIZE(pyc);
|
||||
for (size_t n = 0; n < flag->defval.listval.count; n++) {
|
||||
flag->defval.listval.items[n] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(pyc, n));
|
||||
if (!flag->defval.listval.items[n]) return NULL;
|
||||
}
|
||||
} else {
|
||||
flag->defval.type = CLI_VALUE_STRING;
|
||||
flag->defval.strval = PyUnicode_Check(defval) ? PyUnicode_AsUTF8(defval) : NULL;
|
||||
}
|
||||
if (vt_is_end(vt_insert(&spec.flag_map, flag->dest, flag))) return PyErr_NoMemory();
|
||||
}
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
parse_cli_loop(&spec, argc, argv);
|
||||
PyObject *ans = cli_parse_result_as_python(&spec);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ version
|
|||
oc.do_print = False
|
||||
|
||||
def t(args, leftover=(), fails=False, **expected):
|
||||
oc.values_map['list'] = []
|
||||
args = list(shlex_split(args))
|
||||
ans = CLIOptions()
|
||||
if fails:
|
||||
|
|
@ -89,13 +88,8 @@ version
|
|||
self.assertEqual(tuple(leftover), tuple(actual_leftover), f'{args}\n{ans}')
|
||||
for dest, defval in oc.values_map.items():
|
||||
val = expected.get(dest, defval)
|
||||
self.assertEqual(val, getattr(ans, dest), f'Failed to parse {dest} correctly for: {args} \n{ans}')
|
||||
self.assertEqual(val, getattr(ans, dest, BaseTest), f'Failed to parse {dest} correctly for: {args} \n{ans}')
|
||||
|
||||
t('-1 -h', fails=True)
|
||||
t('-1 --help', fails=True)
|
||||
t('-1 -0v', fails=True)
|
||||
t('-1 -v0', fails=True)
|
||||
t('-1 --version', fails=True)
|
||||
t('-1', bool_set=True)
|
||||
t('-01', bool_reset=False, bool_set=True)
|
||||
t('-01=y', bool_reset=False, bool_set=True)
|
||||
|
|
@ -110,6 +104,11 @@ version
|
|||
t('--choice moo', fails=True)
|
||||
t('-1c moo', fails=True)
|
||||
t('-10c=moo', fails=True)
|
||||
t('-1 -h', fails=True)
|
||||
t('-1 --help', fails=True)
|
||||
t('-1 -0v', fails=True)
|
||||
t('-1 -v0', fails=True)
|
||||
t('-1 --version', fails=True)
|
||||
|
||||
|
||||
def conf_parsing(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue