Cache parsing of command line specifications

Change option specification from dict to tuple for better performance
and immutability and better type checking.
This commit is contained in:
Kovid Goyal
2025-09-29 12:31:10 +05:30
parent 1252098016
commit d8b524c692
7 changed files with 130 additions and 125 deletions

View File

@@ -247,7 +247,7 @@ def clone_safe_launch_opts() -> Sequence[GoOption]:
ans = []
allowed = clone_safe_opts()
for o in go_options_for_seq(parse_option_spec(options_spec())[0]):
if o.obj_dict['name'] in allowed:
if o.obj_defn.name in allowed:
ans.append(o)
return tuple(ans)

View File

@@ -29,16 +29,16 @@ def migrate_help(x: str) -> str:
def help_of(x: str) -> str:
return migrate_help(panel_opts[x]['help'])
return migrate_help(panel_opts[x].help)
agr('qat', 'Window appearance')
opt('lines', '25', long_text=panel_opts['lines']['help'])
opt('lines', '25', long_text=panel_opts['lines'].help)
opt('columns', '80', long_text=panel_opts['columns']['help'])
opt('columns', '80', long_text=panel_opts['columns'].help)
opt('edge', 'top', choices=panel_opts['edge']['choices'], long_text=help_of('edge'))
opt('edge', 'top', choices=panel_opts['edge'].choices, long_text=help_of('edge'))
opt('background_opacity', '0.85', option_type='unit_float', long_text='''
The background opacity of the window. This works the same as the kitty
@@ -80,7 +80,7 @@ opt('output_name', '', long_text=help_of('output_name'))
opt('start_as_hidden', 'no', option_type='to_bool',
long_text='Whether to start the quick access terminal hidden. Useful if you are starting it as part of system startup.')
opt('focus_policy', 'exclusive', choices=panel_opts['focus_policy']['choices'], long_text=help_of('focus_policy'))
opt('focus_policy', 'exclusive', choices=panel_opts['focus_policy'].choices, long_text=help_of('focus_policy'))

View File

@@ -15,7 +15,7 @@ from .fast_data_types import parse_cli_from_spec, wcswidth
from .options.types import Options as KittyOpts
from .simple_cli_definitions import (
CompletionType,
OptionDict,
OptionDefinition,
OptionSpecSeq,
defval_for_opt,
get_option_maps,
@@ -34,34 +34,34 @@ go_type_map = {
class GoOption:
def __init__(self, x: OptionDict) -> None:
flags = sorted(x['aliases'], key=len)
def __init__(self, x: OptionDefinition) -> None:
flags = sorted(x.aliases, key=len)
short = ''
self.aliases = []
if len(flags) > 1 and not flags[0].startswith("--"):
short = flags[0][1:]
self.short, self.long = short, x['name'].replace('_', '-')
self.short, self.long = short, x.name.replace('_', '-')
for f in flags:
q = f[2:] if f.startswith('--') else f[1:]
self.aliases.append(q)
self.type = x['type']
if x['choices']:
self.type = x.type
if x.choices:
self.type = 'choices'
self.default = x['default']
self.obj_dict = x
self.default = x.default
self.obj_defn = x
self.go_type = go_type_map[self.type]
if x['dest']:
self.go_var_name = ''.join(x.capitalize() for x in x['dest'].replace('-', '_').split('_'))
if x.dest:
self.go_var_name = ''.join(x.capitalize() for x in x.dest.replace('-', '_').split('_'))
else:
self.go_var_name = ''.join(x.capitalize() for x in self.long.replace('-', '_').split('_'))
self.help_text = serialize_as_go_string(self.obj_dict['help'].strip())
self.help_text = serialize_as_go_string(self.obj_defn.help.strip())
def struct_declaration(self) -> str:
return f'{self.go_var_name} {self.go_type}'
@property
def flags(self) -> list[str]:
return sorted(self.obj_dict['aliases'])
return sorted(self.obj_defn.aliases)
def as_option(self, cmd_name: str = 'cmd', depth: int = 0, group: str = '') -> str:
add = f'AddToGroup("{serialize_as_go_string(group)}", ' if group else 'Add('
@@ -77,8 +77,8 @@ class GoOption:
cx = ', '.join(f'"{serialize_as_go_string(x)}"' for x in self.sorted_choices)
ans += f'\nChoices: "{serialize_as_go_string(c)}",\n'
ans += f'\nCompleter: cli.NamesCompleter("Choices for {self.long}", {cx}),'
elif self.obj_dict['completion'].type is not CompletionType.none:
ans += ''.join(self.obj_dict['completion'].as_go_code('Completer', ': ')) + ','
elif self.obj_defn.completion.type is not CompletionType.none:
ans += ''.join(self.obj_defn.completion.as_go_code('Completer', ': ')) + ','
if depth > 0:
ans += f'\n\tDepth: {depth},\n'
if self.default:
@@ -111,7 +111,7 @@ class GoOption:
@property
def sorted_choices(self) -> list[str]:
choices = sorted(self.obj_dict['choices'])
choices = sorted(self.obj_defn.choices)
choices.remove(self.default or '')
choices.insert(0, self.default or '')
return choices
@@ -374,7 +374,7 @@ def get_defaults_from_seq(seq: OptionSpecSeq) -> dict[str, Any]:
ans: dict[str, Any] = {}
for opt in seq:
if not isinstance(opt, str):
ans[opt['dest']] = defval_for_opt(opt)
ans[opt.dest] = defval_for_opt(opt)
return ans
@@ -432,24 +432,24 @@ class PrintHelpForSeq:
if isinstance(opt, str):
a(f'{title(opt)}:')
continue
help_text = opt['help']
help_text = opt.help
if help_text == '!':
continue # hidden option
a(' ' + ', '.join(map(green, sorted(opt['aliases'], reverse=True))))
defval = opt.get('default')
if (otype := opt.get('type', '')).startswith('bool-'):
a(' ' + ', '.join(map(green, sorted(opt.aliases, reverse=True))))
defval = opt.default
if (otype := opt.type).startswith('bool-'):
blocks[-1] += italic(f'[={help_defval_for_bool(otype)}]')
else:
dt = f'''=[{italic(defval or '""')}]'''
blocks[-1] += dt
if opt.get('help'):
if opt.help:
t = help_text.replace('%default', str(defval)).strip()
# replace rst literal code block syntax
t = t.replace('::\n\n', ':\n\n')
t = t.replace('#placeholder_for_formatting#', '')
wa(prettify(t), indent=4)
if opt.get('choices'):
wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4)
if opt.choices:
wa('Choices: {}'.format(', '.join(opt.choices)), indent=4)
a('')
text = '\n'.join(blocks) + '\n\n' + version()
@@ -510,25 +510,25 @@ def seq_as_rst(
a(opt)
a('~' * (len(opt) + 10))
continue
help_text = opt['help']
help_text = opt.help
if help_text == '!':
continue # hidden option
defn = '.. option:: '
if (otype := opt.get('type', '')).startswith('bool-'):
if (otype := opt.type).startswith('bool-'):
val_name = f' [={help_defval_for_bool(otype)}]'
else:
val_name = ' <{}>'.format(opt['dest'].upper())
a(defn + ', '.join(o + val_name for o in sorted(opt['aliases'])))
if opt.get('help'):
defval = opt.get('default')
val_name = ' <{}>'.format(opt.dest.upper())
a(defn + ', '.join(o + val_name for o in sorted(opt.aliases)))
if opt.help:
defval = opt.default
t = help_text.replace('%default', ':code:`' + escape_rst(str(defval)) + '`').strip()
t = t.replace('#placeholder_for_formatting#', '')
a('')
a(textwrap.indent(prettify_rst(t), ' ' * 4))
if defval is not None:
a(textwrap.indent(f'Default: :code:`{escape_rst(str(defval))}`', ' ' * 4))
if opt.get('choices'):
a(textwrap.indent('Choices: {}'.format(', '.join(f':code:`{escape_rst(c)}`' for c in sorted(opt['choices']))), ' ' * 4))
if opt.choices:
a(textwrap.indent('Choices: {}'.format(', '.join(f':code:`{escape_rst(c)}`' for c in sorted(opt.choices))), ' ' * 4))
a('')
text = '\n'.join(blocks)
@@ -541,8 +541,8 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, e
for opt in chain(seq, disabled):
if isinstance(opt, str):
continue
name = opt['dest']
otype = opt['type'] or 'str'
name = opt.dest
otype = opt.type or 'str'
if otype in ('str', 'int', 'float'):
t = otype
if t == 'str' and defval_for_opt(opt) is None:
@@ -550,8 +550,8 @@ def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str, e
elif otype == 'list':
t = 'typing.Sequence[str]'
elif otype in ('choice', 'choices'):
if opt['choices']:
t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt['choices']))
if opt.choices:
t = 'typing.Literal[{}]'.format(','.join(f'{x!r}' for x in opt.choices))
else:
t = 'str'
elif otype.startswith('bool-'):
@@ -616,14 +616,14 @@ def apply_preparsed_cli_flags(
def parse_cmdline_inner(
args: list[str], oc: Options, disabled: OptionSpecSeq, names_map: dict[str, OptionDict],
values_map: dict[str, OptionDict], ans: Any, track_seen_options: dict[str, Any] | None = None
args: list[str], oc: Options, disabled: OptionSpecSeq, names_map: dict[str, OptionDefinition],
values_map: dict[str, OptionDefinition], ans: Any, track_seen_options: dict[str, Any] | None = None
) -> list[str]:
preparsed = parse_cli_from_spec(args, names_map, values_map)
leftover_args = apply_preparsed_cli_flags(preparsed, ans, lambda: oc, track_seen_options)
for opt in disabled:
if not isinstance(opt, str):
setattr(ans, opt['dest'], defval_for_opt(opt))
setattr(ans, opt.dest, defval_for_opt(opt))
return leftover_args
@@ -634,10 +634,10 @@ def parse_cmdline(
names_map = oc.names_map.copy()
values_map = oc.values_map.copy()
if 'help' not in names_map:
names_map['help'] = {'type': 'bool-set', 'aliases': ('--help', '-h')} # type: ignore
names_map['help'] = OptionDefinition(type='bool-set', aliases=('--help', '-h'))
values_map['help'] = False
if 'version' not in names_map:
names_map['version'] = {'type': 'bool-set', 'aliases': ('--version', '-v')} # type: ignore
names_map['version'] = OptionDefinition(type='bool-set', aliases=('--version', '-v'))
values_map['version'] = False
try:
return parse_cmdline_inner(sys.argv[1:] if args is None else args, oc, disabled, names_map, values_map, ans, track_seen_options)

View File

@@ -8,7 +8,7 @@ from kitty.fonts.render import FontObject
from kitty.marks import MarkerFunc
from kitty.notifications import MacOSNotificationCategory
from kitty.options.types import Options
from kitty.simple_cli_definitions import OptionDict
from kitty.simple_cli_definitions import OptionDefinition
from kitty.types import LayerShellConfig, SignalInfo
from kitty.typing_compat import EdgeLiteral, NotRequired, ReadableBuffer, WriteableBuffer
@@ -1720,7 +1720,9 @@ def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ...
def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ...
def run_with_activation_token(func: Callable[[str], None]) -> bool: ...
def toggle_os_window_visibility(os_window_id: int, visible: bool | Literal[-1] = -1, move_to_active_screen: bool = 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 parse_cli_from_spec(
args: list[str], names_map: dict[str, OptionDefinition], 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]: ...

View File

@@ -559,14 +559,16 @@ parse_cli_from_python_spec(PyObject *self, PyObject *args) {
memcpy(argv[i + 1], src, sz);
}
argv[++argc] = 0;
PyObject *key = NULL, *opt = NULL;
PyObject *key = NULL, *optdef = NULL;
Py_ssize_t pos = 0;
while (PyDict_Next(names_map, &pos, &key, &opt)) {
while (PyDict_Next(names_map, &pos, &key, &optdef)) {
FlagSpec flag = {.dest=PyUnicode_AsUTF8(key)};
PyObject *pytype = PyDict_GetItemString(opt, "type");
const char *type = pytype ? PyUnicode_AsUTF8(pytype) : "";
RAII_PyObject(pytype, PyObject_GetAttrString(optdef, "type"));
if (!pytype) return NULL;
const char *type = PyUnicode_AsUTF8(pytype);
PyObject *defval = PyDict_GetItemWithError(defval_map, key); if (!defval && PyErr_Occurred()) return NULL;
PyObject *pyaliases = PyDict_GetItemString(opt, "aliases");
RAII_PyObject(pyaliases, PyObject_GetAttrString(optdef, "aliases"));
if (!pyaliases) return NULL;
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();
@@ -588,7 +590,8 @@ parse_cli_from_python_spec(PyObject *self, PyObject *args) {
} else if (strcmp(type, "choices") == 0) {
flag.defval.type = CLI_VALUE_CHOICE;
flag.defval.strval = PyUnicode_AsUTF8(defval);
PyObject *pyc = PyDict_GetItemString(opt, "choices");
RAII_PyObject(pyc, PyObject_GetAttrString(optdef, "choices"));
if (!pyc) return NULL;
flag.defval.listval.items = alloc_for_cli(&spec, PyTuple_GET_SIZE(pyc) * sizeof(char*));
if (!flag.defval.listval.items) return PyErr_NoMemory();
flag.defval.listval.count = PyTuple_GET_SIZE(pyc);

View File

@@ -82,8 +82,8 @@ CmdGenerator = Iterator[CmdReturnType]
PayloadType = Optional[Union[CmdReturnType, CmdGenerator]]
PayloadGetType = PayloadGetter
ArgsType = list[str]
ImageCompletion = CompletionSpec.from_string('type:file group:"Images"')
ImageCompletion.extensions = 'png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff'
ImageCompletion = CompletionSpec.from_string('type:file group:"Images"')._replace(
extensions=('png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff'))
SUPPORTED_IMAGE_FORMATS = tuple(x.upper() for x in ImageCompletion.extensions if x != 'jpg')

View File

@@ -6,9 +6,9 @@
import re
import sys
from dataclasses import dataclass
from enum import Enum, auto
from typing import Any, Iterator, Sequence, TypedDict
from functools import lru_cache
from typing import Any, Iterator, NamedTuple, Sequence
try:
from kitty.constants import appname, is_macos
@@ -42,8 +42,7 @@ class CompletionRelativeTo(Enum):
config_dir = auto()
@dataclass
class CompletionSpec:
class CompletionSpec(NamedTuple):
type: CompletionType = CompletionType.none
kwds: tuple[str,...] = ()
@@ -54,27 +53,33 @@ class CompletionSpec:
@staticmethod
def from_string(raw: str) -> 'CompletionSpec':
self = CompletionSpec()
typ = CompletionType.none
kwds: tuple[str, ...] = ()
extensions: tuple[str, ...] = ()
mime_patterns: tuple[str, ...] = ()
group = ''
relative_to = CompletionRelativeTo.cwd
for x in shlex_split(raw):
ck, vv = x.split(':', 1)
if ck == 'type':
self.type = getattr(CompletionType, vv)
typ = getattr(CompletionType, vv)
elif ck == 'kwds':
self.kwds += tuple(vv.split(','))
kwds += tuple(vv.split(','))
elif ck == 'ext':
self.extensions += tuple(vv.split(','))
extensions += tuple(vv.split(','))
elif ck == 'group':
self.group = vv
group = vv
elif ck == 'mime':
self.mime_patterns += tuple(vv.split(','))
mime_patterns += tuple(vv.split(','))
elif ck == 'relative':
if vv == 'conf':
self.relative_to = CompletionRelativeTo.config_dir
relative_to = CompletionRelativeTo.config_dir
else:
raise ValueError(f'Unknown completion relative to value: {vv}')
else:
raise KeyError(f'Unknown completion property: {ck}')
return self
return CompletionSpec(
type=typ, kwds=kwds, extensions=extensions, mime_patterns=mime_patterns, group=group, relative_to=relative_to)
def as_go_code(self, go_name: str, sep: str = ': ') -> Iterator[str]:
completers = []
@@ -106,21 +111,23 @@ class CompletionSpec:
yield f'{go_name}{sep}{completers[0]}'
class OptionDict(TypedDict):
dest: str
name: str
aliases: tuple[str, ...]
help: str
choices: tuple[str, ...]
type: str
default: str | None
condition: bool
completion: CompletionSpec
class OptionDefinition(NamedTuple):
dest: str = ''
name: str = ''
aliases: tuple[str, ...] = ()
help: str = ''
choices: tuple[str, ...] = ()
type: str = ''
default: str | None = None
condition: bool = False
completion: CompletionSpec = CompletionSpec()
OptionSpecSeq = Sequence[str | OptionDict]
OptionSpecSeq = Sequence[str | OptionDefinition]
@lru_cache(64)
def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpecSeq]:
if spec is None:
spec = kitty_options_spec()
@@ -129,14 +136,10 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
lines = spec.splitlines()
prev_line = ''
prev_indent = 0
seq: list[str | OptionDict] = []
disabled: list[str | OptionDict] = []
seq: list[str | OptionDefinition] = []
disabled: list[str | OptionDefinition] = []
mpat = re.compile('([a-z]+)=(.+)')
current_cmd: OptionDict = {
'dest': '', 'aliases': (), 'help': '', 'choices': (),
'type': '', 'condition': False, 'default': None, 'completion': CompletionSpec(), 'name': ''
}
empty_cmd = current_cmd
current_cmd = empty_cmd = OptionDefinition()
def indent_of_line(x: str) -> int:
return len(x) - len(x.lstrip())
@@ -152,11 +155,7 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
if line.startswith('--'):
parts = line.split(' ')
defdest = parts[0][2:].replace('-', '_')
current_cmd = {
'dest': defdest, 'aliases': tuple(parts), 'help': '',
'choices': tuple(), 'type': '', 'name': defdest,
'default': None, 'condition': True, 'completion': CompletionSpec(),
}
current_cmd = OptionDefinition(dest=defdest, aliases=tuple(parts), name=defdest, condition=True)
state = METADATA
continue
raise ValueError(f'Invalid option spec, unexpected line: {line}')
@@ -164,60 +163,61 @@ def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpe
m = mpat.match(line)
if m is None:
state = HELP
current_cmd['help'] += line
current_cmd = current_cmd._replace(help=current_cmd.help + line)
else:
k, v = m.group(1), m.group(2)
if k == 'choices':
vals = tuple(x.strip() for x in v.split(','))
if not current_cmd['type']:
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'] = tuple(vals)
if current_cmd['default'] is None:
current_cmd['default'] = vals[0]
if not current_cmd.type:
current_cmd = current_cmd._replace(type='choices')
if current_cmd.type != 'choices':
raise ValueError(f'Cannot specify choices for an option of type: {current_cmd.type}')
current_cmd = current_cmd._replace(choices=tuple(vals))
if current_cmd.default is None:
current_cmd = current_cmd._replace(default=vals[0])
else:
if k == 'default':
current_cmd['default'] = v
current_cmd = current_cmd._replace(default=v)
elif k == 'type':
if v == 'choice':
v = 'choices'
current_cmd['type'] = v
current_cmd = current_cmd._replace(type=v)
elif k == 'dest':
current_cmd['dest'] = v
current_cmd = current_cmd._replace(dest=v)
elif k == 'condition':
current_cmd['condition'] = bool(eval(v))
current_cmd = current_cmd._replace(condition=bool(eval(v)))
elif k == 'completion':
current_cmd['completion'] = CompletionSpec.from_string(v)
current_cmd = current_cmd._replace(completion=CompletionSpec.from_string(v))
elif state is HELP:
if line:
current_indent = indent_of_line(line)
if current_indent > 1:
if prev_indent == 0:
current_cmd['help'] += '\n'
current_cmd = current_cmd._replace(help=current_cmd.help + '\n')
else:
line = line.strip()
prev_indent = current_indent
spc = '' if current_cmd['help'].endswith('\n') else ' '
current_cmd['help'] += spc + line
spc = '' if current_cmd.help.endswith('\n') else ' '
current_cmd = current_cmd._replace(help=current_cmd.help + spc + line)
else:
prev_indent = 0
if prev_line:
current_cmd['help'] += '\n' if current_cmd['help'].endswith('::') else '\n\n'
h = '\n' if current_cmd.help.endswith('::') else '\n\n'
current_cmd = current_cmd._replace(help=current_cmd.help + h)
else:
state = NORMAL
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
(seq if current_cmd.condition else disabled).append(current_cmd)
current_cmd = empty_cmd
prev_line = line
if current_cmd is not empty_cmd:
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
(seq if current_cmd.condition else disabled).append(current_cmd)
return seq, disabled
def defval_for_opt(opt: OptionDict) -> Any:
dv: Any = opt.get('default')
typ = opt.get('type', '')
def defval_for_opt(opt: OptionDefinition) -> Any:
dv: Any = opt.default
typ = opt.type
if typ.startswith('bool-'):
if dv is None:
dv = False if typ == 'bool-set' else True
@@ -230,16 +230,16 @@ def defval_for_opt(opt: OptionDict) -> Any:
return dv
def get_option_maps(seq: OptionSpecSeq) -> tuple[dict[str, OptionDict], dict[str, OptionDict], dict[str, Any]]:
names_map: dict[str, OptionDict] = {}
alias_map: dict[str, OptionDict] = {}
def get_option_maps(seq: OptionSpecSeq) -> tuple[dict[str, OptionDefinition], dict[str, OptionDefinition], dict[str, Any]]:
names_map: dict[str, OptionDefinition] = {}
alias_map: dict[str, OptionDefinition] = {}
values_map: dict[str, Any] = {}
for opt in seq:
if isinstance(opt, str):
continue
for alias in opt['aliases']:
for alias in opt.aliases:
alias_map[alias] = opt
name = opt['dest']
name = opt.dest
names_map[name] = opt
values_map[name] = defval_for_opt(opt)
return names_map, alias_map, values_map
@@ -259,9 +259,9 @@ 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]:
def generate_c_for_opt(name: str, defval: Any, opt: OptionDefinition) -> Iterator[str]:
yield f'\tflag = (FlagSpec){{.dest={c_str(name)},}};'
match opt['type']:
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"};'
@@ -278,7 +278,7 @@ def generate_c_for_opt(name: str, defval: Any, opt: OptionDict) -> Iterator[str]
case 'choices':
yield '\tflag.defval.type = CLI_VALUE_CHOICE;'
yield f'\tflag.defval.strval = {c_str(defval)};'
yield from add_list_values(*opt['choices'])
yield from add_list_values(*opt.choices)
case _:
yield '\tflag.defval.type = CLI_VALUE_STRING;'
if defval is not None:
@@ -289,22 +289,22 @@ def generate_c_parser_for(funcname: str, spec: str) -> Iterator[str]:
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
names_map['help'] = OptionDefinition(type='bool-set', aliases=('--help', '-h'))
defaults_map['help'] = False
if 'version' not in names_map:
names_map['version'] = {'type': 'bool-set', 'aliases': ('--version', '-v')} # type: ignore
names_map['version'] = OptionDefinition(type='bool-set', aliases=('--version', '-v'))
defaults_map['version'] = False
yield f'static void\nparse_cli_for_{funcname}(CLISpec *spec, int argc, char **argv) {{' # }}
yield '\tFlagSpec flag;'
for name, opt in names_map.items():
for alias in opt['aliases']:
for alias in opt.aliases:
yield f'\tif (vt_is_end(vt_insert(&spec->alias_map, {c_str(alias)}, {c_str(name)}))) OOM;'
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 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);'