Skip to content

Commit a0f2dfa

Browse files
committed
Use CEFS for command line event routing.
1. Use CEFS for command line event routing. 2. Fix some bugs in CommandEventFS. 3. Add Rubisco CommandEventFS Debugger CLI. 4. Partially add Rubisco CommandEventFS fstab support. (TODO: Finish fstab support)
1 parent 5954e1a commit a0f2dfa

25 files changed

+2052
-400
lines changed

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@ pyyaml >= 5.4.1
77
requests >= 2.18.4
88
rich >= 6.2.0
99
rich-argparse >= 1.0.0
10+
GitPython >= 3.1.20
1011
pytest >= 6.2.4
12+
pygments >= 2.10.0
13+
prompt_toolkit >= 3.0.23

rubisco/cli/argparse_generator.py

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# -*- mode: python -*-
2+
# vi: set ft=python :
3+
4+
# Copyright (C) 2024 The C++ Plus Project.
5+
# This file is part of the Rubisco.
6+
#
7+
# Rubisco is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published
9+
# by the Free Software Foundation, either version 3 of the License,
10+
# or (at your option) any later version.
11+
#
12+
# Rubisco is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
"""Generate argparser from CommandEventFS."""
21+
22+
from argparse import ArgumentParser, Namespace
23+
from typing import TYPE_CHECKING, TypeAlias
24+
25+
from rubisco.cli.main.help_formatter import RUHelpFormatter
26+
from rubisco.kernel.command_event.event_file_data import EventFileData
27+
from rubisco.kernel.command_event.event_path import EventPath
28+
29+
if TYPE_CHECKING:
30+
from argparse import _SubParsersAction # type: ignore[attr-defined]
31+
32+
# After Python 3.14 released. We will only support Python 3.12 or later.
33+
# We will use `type SubParser = xxx` syntax.
34+
SubParser: TypeAlias = (
35+
"_SubParsersAction[ArgumentParser]" # type: ignore[valid-type] # pylint: disable=line-too-long
36+
)
37+
38+
_subparsers: dict[str, SubParser] = {}
39+
40+
41+
def _get_subparser(
42+
path: EventPath,
43+
parent: ArgumentParser,
44+
) -> SubParser:
45+
name = path.parent.normpath().as_posix()
46+
val = _subparsers.get(name)
47+
if val:
48+
return val
49+
subparser = parent.add_subparsers(
50+
metavar="",
51+
required=True,
52+
)
53+
_subparsers[name] = subparser
54+
return subparser
55+
56+
57+
def _fill_options(
58+
path: EventPath,
59+
args: Namespace,
60+
) -> None:
61+
for option in path.stat().options:
62+
if not hasattr(args, option.name) and option.default is None:
63+
msg = f"Option {option.name} is not provided in the namespace."
64+
raise ValueError(msg)
65+
path.set_option(option.name, getattr(args, option.name, option.default))
66+
67+
if path.normpath().as_posix() != "/":
68+
_fill_options(path.parent, args)
69+
70+
71+
def _fill_arguments(
72+
data: EventFileData,
73+
args: Namespace,
74+
) -> list[str]:
75+
args_list: list[str] = []
76+
77+
if isinstance(data.args, list):
78+
for arg in data.args:
79+
if not hasattr(args, arg.name):
80+
msg = f"Argument {arg.name} is not provided in the namespace."
81+
raise ValueError(msg)
82+
args_list.append(getattr(args, arg.name))
83+
else:
84+
if not hasattr(args, data.args.name):
85+
msg = f"Argument {data.args.name} is not provided in the namespace."
86+
raise ValueError(msg)
87+
args_list.extend(getattr(args, data.args.name))
88+
89+
return args_list
90+
91+
92+
def _call_event(path: EventPath, args: Namespace) -> None:
93+
"""Call event function.
94+
95+
Args:
96+
path (EventPath): The event path.
97+
args (Namespace): The namespace.
98+
99+
"""
100+
data = path.read()
101+
if not data:
102+
msg = f"Not a file, this should never happen: {path}"
103+
raise ValueError(msg)
104+
105+
_fill_options(path, args)
106+
path.execute(_fill_arguments(data, args))
107+
108+
109+
def _gen_options(event_path: EventPath, parser: ArgumentParser) -> None:
110+
for options in event_path.stat().options:
111+
names: list[str] = []
112+
for name in [options.name, *options.aliases]:
113+
names.append(f"--{name}" if len(name) > 1 else f"-{name}") # noqa: PERF401
114+
parser.add_argument(
115+
*names,
116+
type=options.typecheck,
117+
help=options.description,
118+
action="store",
119+
dest=options.name,
120+
default=options.default,
121+
)
122+
123+
124+
def _gen_args(
125+
event_path: EventPath,
126+
parser: ArgumentParser,
127+
) -> None:
128+
data = event_path.read()
129+
if not data:
130+
msg = f"Not a file, this should never happen: {event_path}"
131+
raise ValueError(msg)
132+
if isinstance(data.args, list):
133+
for arg in data.args:
134+
parser.add_argument(
135+
arg.name,
136+
type=arg.typecheck,
137+
help=arg.description,
138+
action="store",
139+
default=arg.default,
140+
)
141+
else:
142+
parser.add_argument(
143+
data.args.name,
144+
help=data.args.description,
145+
action="store",
146+
nargs="+" if data.args.mincount == 0 else "*",
147+
)
148+
149+
150+
def _gen_from_file(event_path: EventPath, parent: ArgumentParser) -> None:
151+
data = event_path.read()
152+
if not data:
153+
msg = f"Not a file, this should never happen: {event_path}"
154+
raise ValueError(msg)
155+
156+
subparser = _get_subparser(event_path, parent)
157+
parser = subparser.add_parser(
158+
event_path.name,
159+
help=event_path.stat().description,
160+
formatter_class=RUHelpFormatter,
161+
allow_abbrev=True,
162+
)
163+
164+
def _callback(args: Namespace) -> None:
165+
_call_event(event_path, args)
166+
167+
parser.set_defaults(callback=_callback)
168+
_gen_options(event_path, parser)
169+
_gen_args(event_path, parser)
170+
171+
172+
def _gen_from_dir(event_path: EventPath, parent: ArgumentParser) -> None:
173+
subparser = _get_subparser(event_path, parent)
174+
parser = subparser.add_parser(
175+
event_path.name,
176+
help=event_path.stat().description,
177+
formatter_class=RUHelpFormatter,
178+
allow_abbrev=True,
179+
)
180+
_gen_options(event_path, parser)
181+
for subdir in event_path.list_dirs():
182+
_gen_from_dir(subdir, parser)
183+
for file in event_path.list_files():
184+
_gen_from_file(file, parser)
185+
186+
187+
def _gen_from_link(event_path: EventPath, parent: ArgumentParser) -> None:
188+
if not event_path.stat().alias_to or not event_path.is_alias():
189+
msg = f"Not a link: {event_path}, this should never happen."
190+
raise ValueError(msg)
191+
if event_path.is_dir():
192+
_gen_from_dir(event_path, parent)
193+
elif event_path.is_file():
194+
_gen_from_file(event_path, parent)
195+
elif event_path.is_mount_point():
196+
pass # Mount point will be mounted later.
197+
else:
198+
msg = f"Unknown event type: {event_path}, this should never happen."
199+
raise ValueError(msg)
200+
201+
202+
def gen_argparse(
203+
event_path: EventPath,
204+
parent: ArgumentParser,
205+
) -> None:
206+
"""Generate argparser from CommandEventFS.
207+
208+
Args:
209+
event_path (EventPath): The event path.
210+
parent (ArgumentParser): The parent argument parser.
211+
212+
"""
213+
for subdir in event_path.list_dirs():
214+
_gen_from_dir(subdir, parent)
215+
for file in event_path.list_files():
216+
_gen_from_file(file, parent)
217+
for link in event_path.list_aliases():
218+
_gen_from_link(link, parent)

rubisco/cli/cefs_dbg/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# -*- mode: python -*-
2+
# vi: set ft=python :
3+
4+
# Copyright (C) 2024 The C++ Plus Project.
5+
# This file is part of the Rubisco.
6+
#
7+
# Rubisco is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU General Public License as published
9+
# by the Free Software Foundation, either version 3 of the License,
10+
# or (at your option) any later version.
11+
#
12+
# Rubisco is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU General Public License
18+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
20+
"""Rubisco CLI for CommandEventFS debugging."""

0 commit comments

Comments
 (0)