ruyi 0.39.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ruyi/__init__.py +21 -0
- ruyi/__main__.py +98 -0
- ruyi/cli/__init__.py +5 -0
- ruyi/cli/builtin_commands.py +14 -0
- ruyi/cli/cmd.py +224 -0
- ruyi/cli/completer.py +50 -0
- ruyi/cli/completion.py +26 -0
- ruyi/cli/config_cli.py +153 -0
- ruyi/cli/main.py +111 -0
- ruyi/cli/self_cli.py +295 -0
- ruyi/cli/user_input.py +127 -0
- ruyi/cli/version_cli.py +45 -0
- ruyi/config/__init__.py +401 -0
- ruyi/config/editor.py +92 -0
- ruyi/config/errors.py +76 -0
- ruyi/config/news.py +39 -0
- ruyi/config/schema.py +197 -0
- ruyi/device/__init__.py +0 -0
- ruyi/device/provision.py +591 -0
- ruyi/device/provision_cli.py +40 -0
- ruyi/log/__init__.py +272 -0
- ruyi/mux/.gitignore +1 -0
- ruyi/mux/__init__.py +0 -0
- ruyi/mux/runtime.py +213 -0
- ruyi/mux/venv/__init__.py +12 -0
- ruyi/mux/venv/emulator_cfg.py +41 -0
- ruyi/mux/venv/maker.py +782 -0
- ruyi/mux/venv/venv_cli.py +92 -0
- ruyi/mux/venv_cfg.py +214 -0
- ruyi/pluginhost/__init__.py +0 -0
- ruyi/pluginhost/api.py +206 -0
- ruyi/pluginhost/ctx.py +222 -0
- ruyi/pluginhost/paths.py +135 -0
- ruyi/pluginhost/plugin_cli.py +37 -0
- ruyi/pluginhost/unsandboxed.py +246 -0
- ruyi/py.typed +0 -0
- ruyi/resource_bundle/__init__.py +20 -0
- ruyi/resource_bundle/__main__.py +55 -0
- ruyi/resource_bundle/data.py +26 -0
- ruyi/ruyipkg/__init__.py +0 -0
- ruyi/ruyipkg/admin_checksum.py +88 -0
- ruyi/ruyipkg/admin_cli.py +83 -0
- ruyi/ruyipkg/atom.py +184 -0
- ruyi/ruyipkg/augmented_pkg.py +212 -0
- ruyi/ruyipkg/canonical_dump.py +320 -0
- ruyi/ruyipkg/checksum.py +39 -0
- ruyi/ruyipkg/cli_completion.py +42 -0
- ruyi/ruyipkg/distfile.py +208 -0
- ruyi/ruyipkg/entity.py +387 -0
- ruyi/ruyipkg/entity_cli.py +123 -0
- ruyi/ruyipkg/entity_provider.py +273 -0
- ruyi/ruyipkg/fetch.py +271 -0
- ruyi/ruyipkg/host.py +55 -0
- ruyi/ruyipkg/install.py +554 -0
- ruyi/ruyipkg/install_cli.py +150 -0
- ruyi/ruyipkg/list.py +126 -0
- ruyi/ruyipkg/list_cli.py +79 -0
- ruyi/ruyipkg/list_filter.py +173 -0
- ruyi/ruyipkg/msg.py +99 -0
- ruyi/ruyipkg/news.py +123 -0
- ruyi/ruyipkg/news_cli.py +78 -0
- ruyi/ruyipkg/news_store.py +183 -0
- ruyi/ruyipkg/pkg_manifest.py +657 -0
- ruyi/ruyipkg/profile.py +208 -0
- ruyi/ruyipkg/profile_cli.py +33 -0
- ruyi/ruyipkg/protocols.py +55 -0
- ruyi/ruyipkg/repo.py +763 -0
- ruyi/ruyipkg/state.py +345 -0
- ruyi/ruyipkg/unpack.py +369 -0
- ruyi/ruyipkg/unpack_method.py +91 -0
- ruyi/ruyipkg/update_cli.py +54 -0
- ruyi/telemetry/__init__.py +0 -0
- ruyi/telemetry/aggregate.py +72 -0
- ruyi/telemetry/event.py +41 -0
- ruyi/telemetry/node_info.py +192 -0
- ruyi/telemetry/provider.py +411 -0
- ruyi/telemetry/scope.py +43 -0
- ruyi/telemetry/store.py +238 -0
- ruyi/telemetry/telemetry_cli.py +127 -0
- ruyi/utils/__init__.py +0 -0
- ruyi/utils/ar.py +74 -0
- ruyi/utils/ci.py +63 -0
- ruyi/utils/frontmatter.py +38 -0
- ruyi/utils/git.py +169 -0
- ruyi/utils/global_mode.py +204 -0
- ruyi/utils/l10n.py +83 -0
- ruyi/utils/markdown.py +73 -0
- ruyi/utils/nuitka.py +33 -0
- ruyi/utils/porcelain.py +51 -0
- ruyi/utils/prereqs.py +77 -0
- ruyi/utils/ssl_patch.py +170 -0
- ruyi/utils/templating.py +34 -0
- ruyi/utils/toml.py +115 -0
- ruyi/utils/url.py +7 -0
- ruyi/utils/xdg_basedir.py +80 -0
- ruyi/version.py +67 -0
- ruyi-0.39.0.dist-info/LICENSE-Apache.txt +201 -0
- ruyi-0.39.0.dist-info/METADATA +403 -0
- ruyi-0.39.0.dist-info/RECORD +101 -0
- ruyi-0.39.0.dist-info/WHEEL +4 -0
- ruyi-0.39.0.dist-info/entry_points.txt +3 -0
ruyi/cli/self_cli.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import shutil
|
|
5
|
+
from typing import Final, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .cmd import RootCommand
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .completion import ArgumentParser
|
|
11
|
+
from .. import config
|
|
12
|
+
|
|
13
|
+
UNINSTALL_NOTICE: Final = """
|
|
14
|
+
[bold]Thanks for hacking with [yellow]Ruyi[/]![/]
|
|
15
|
+
|
|
16
|
+
This will uninstall [yellow]Ruyi[/] from your system, and optionally remove
|
|
17
|
+
all installed packages and [yellow]Ruyi[/]-managed repository data if the
|
|
18
|
+
[green]--purge[/] switch is given on the command line.
|
|
19
|
+
|
|
20
|
+
Note that your [yellow]Ruyi[/] virtual environments [bold]will become unusable[/] after
|
|
21
|
+
[yellow]Ruyi[/] is uninstalled. You should take care of migrating or cleaning
|
|
22
|
+
them yourselves afterwards.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Self-management commands
|
|
27
|
+
class SelfCommand(
|
|
28
|
+
RootCommand,
|
|
29
|
+
cmd="self",
|
|
30
|
+
has_subcommands=True,
|
|
31
|
+
help="Manage this Ruyi installation",
|
|
32
|
+
):
|
|
33
|
+
@classmethod
|
|
34
|
+
def configure_args(
|
|
35
|
+
cls,
|
|
36
|
+
gc: "config.GlobalConfig",
|
|
37
|
+
p: "ArgumentParser",
|
|
38
|
+
) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SelfCleanCommand(
|
|
43
|
+
SelfCommand,
|
|
44
|
+
cmd="clean",
|
|
45
|
+
help="Remove various Ruyi-managed data to reclaim storage",
|
|
46
|
+
):
|
|
47
|
+
@classmethod
|
|
48
|
+
def configure_args(
|
|
49
|
+
cls,
|
|
50
|
+
gc: "config.GlobalConfig",
|
|
51
|
+
p: "ArgumentParser",
|
|
52
|
+
) -> None:
|
|
53
|
+
p.add_argument(
|
|
54
|
+
"--quiet",
|
|
55
|
+
"-q",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Do not print out the actions being performed",
|
|
58
|
+
)
|
|
59
|
+
p.add_argument(
|
|
60
|
+
"--all",
|
|
61
|
+
action="store_true",
|
|
62
|
+
help="Remove all covered data",
|
|
63
|
+
)
|
|
64
|
+
p.add_argument(
|
|
65
|
+
"--distfiles",
|
|
66
|
+
action="store_true",
|
|
67
|
+
help="Remove all downloaded distfiles if any",
|
|
68
|
+
)
|
|
69
|
+
p.add_argument(
|
|
70
|
+
"--installed-pkgs",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help="Remove all installed packages if any",
|
|
73
|
+
)
|
|
74
|
+
p.add_argument(
|
|
75
|
+
"--news-read-status",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help="Mark all news items as unread",
|
|
78
|
+
)
|
|
79
|
+
p.add_argument(
|
|
80
|
+
"--progcache",
|
|
81
|
+
action="store_true",
|
|
82
|
+
help="Clear the Ruyi program cache",
|
|
83
|
+
)
|
|
84
|
+
p.add_argument(
|
|
85
|
+
"--repo",
|
|
86
|
+
action="store_true",
|
|
87
|
+
help="Remove the Ruyi repo if located in Ruyi-managed cache directory",
|
|
88
|
+
)
|
|
89
|
+
p.add_argument(
|
|
90
|
+
"--telemetry",
|
|
91
|
+
action="store_true",
|
|
92
|
+
help="Remove all telemetry data recorded if any",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def main(cls, cfg: "config.GlobalConfig", args: argparse.Namespace) -> int:
|
|
97
|
+
logger = cfg.logger
|
|
98
|
+
quiet: bool = args.quiet
|
|
99
|
+
all: bool = args.all
|
|
100
|
+
distfiles: bool = args.distfiles
|
|
101
|
+
installed_pkgs: bool = args.installed_pkgs
|
|
102
|
+
news_read_status: bool = args.news_read_status
|
|
103
|
+
progcache: bool = args.progcache
|
|
104
|
+
repo: bool = args.repo
|
|
105
|
+
telemetry: bool = args.telemetry
|
|
106
|
+
|
|
107
|
+
if all:
|
|
108
|
+
distfiles = True
|
|
109
|
+
installed_pkgs = True
|
|
110
|
+
news_read_status = True
|
|
111
|
+
progcache = True
|
|
112
|
+
repo = True
|
|
113
|
+
telemetry = True
|
|
114
|
+
|
|
115
|
+
if not any(
|
|
116
|
+
[
|
|
117
|
+
distfiles,
|
|
118
|
+
installed_pkgs,
|
|
119
|
+
news_read_status,
|
|
120
|
+
progcache,
|
|
121
|
+
repo,
|
|
122
|
+
telemetry,
|
|
123
|
+
]
|
|
124
|
+
):
|
|
125
|
+
logger.F("no data specified for cleaning")
|
|
126
|
+
logger.I(
|
|
127
|
+
"please check [yellow]ruyi self clean --help[/] for a list of cleanable data"
|
|
128
|
+
)
|
|
129
|
+
return 1
|
|
130
|
+
|
|
131
|
+
_do_reset(
|
|
132
|
+
cfg,
|
|
133
|
+
quiet=quiet,
|
|
134
|
+
distfiles=distfiles,
|
|
135
|
+
installed_pkgs=installed_pkgs,
|
|
136
|
+
news_read_status=news_read_status,
|
|
137
|
+
progcache=progcache,
|
|
138
|
+
repo=repo,
|
|
139
|
+
telemetry=telemetry,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class SelfUninstallCommand(
|
|
146
|
+
SelfCommand,
|
|
147
|
+
cmd="uninstall",
|
|
148
|
+
help="Uninstall Ruyi",
|
|
149
|
+
):
|
|
150
|
+
@classmethod
|
|
151
|
+
def configure_args(
|
|
152
|
+
cls,
|
|
153
|
+
gc: "config.GlobalConfig",
|
|
154
|
+
p: "ArgumentParser",
|
|
155
|
+
) -> None:
|
|
156
|
+
p.add_argument(
|
|
157
|
+
"--purge",
|
|
158
|
+
action="store_true",
|
|
159
|
+
help="Remove all installed packages and Ruyi-managed remote repo data",
|
|
160
|
+
)
|
|
161
|
+
p.add_argument(
|
|
162
|
+
"-y",
|
|
163
|
+
action="store_true",
|
|
164
|
+
dest="consent",
|
|
165
|
+
help="Give consent for uninstallation on CLI; do not ask for confirmation",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def main(cls, cfg: "config.GlobalConfig", args: argparse.Namespace) -> int:
|
|
170
|
+
from . import user_input
|
|
171
|
+
|
|
172
|
+
logger = cfg.logger
|
|
173
|
+
purge: bool = args.purge
|
|
174
|
+
consent: bool = args.consent
|
|
175
|
+
logger.D(f"ruyi self uninstall: purge={purge}, consent={consent}")
|
|
176
|
+
|
|
177
|
+
if cfg.is_installation_externally_managed:
|
|
178
|
+
logger.F(
|
|
179
|
+
"this [yellow]ruyi[/] is externally managed, for example, by the system package manager, and cannot be uninstalled this way"
|
|
180
|
+
)
|
|
181
|
+
logger.I("please uninstall via the external manager instead")
|
|
182
|
+
return 1
|
|
183
|
+
|
|
184
|
+
if not cfg.is_packaged:
|
|
185
|
+
logger.F(
|
|
186
|
+
"this [yellow]ruyi[/] is not in standalone form, and cannot be uninstalled this way"
|
|
187
|
+
)
|
|
188
|
+
return 1
|
|
189
|
+
|
|
190
|
+
if not consent:
|
|
191
|
+
logger.stdout(UNINSTALL_NOTICE)
|
|
192
|
+
if not user_input.ask_for_yesno_confirmation(logger, "Continue?"):
|
|
193
|
+
logger.I("aborting uninstallation")
|
|
194
|
+
return 0
|
|
195
|
+
else:
|
|
196
|
+
logger.I("uninstallation consent given over CLI, proceeding")
|
|
197
|
+
|
|
198
|
+
_do_reset(
|
|
199
|
+
cfg,
|
|
200
|
+
quiet=False,
|
|
201
|
+
installed_pkgs=purge,
|
|
202
|
+
all_state=purge,
|
|
203
|
+
all_cache=purge,
|
|
204
|
+
self_binary=True,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
logger.I("[yellow]ruyi[/] is uninstalled")
|
|
208
|
+
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _do_reset(
|
|
213
|
+
cfg: "config.GlobalConfig",
|
|
214
|
+
quiet: bool = False,
|
|
215
|
+
*,
|
|
216
|
+
installed_pkgs: bool = False,
|
|
217
|
+
all_state: bool = False,
|
|
218
|
+
news_read_status: bool = False, # ignored if all_state=True
|
|
219
|
+
telemetry: bool = False, # ignored if all_state=True
|
|
220
|
+
all_cache: bool = False,
|
|
221
|
+
distfiles: bool = False, # ignored if all_cache=True
|
|
222
|
+
progcache: bool = False, # ignored if all_cache=True
|
|
223
|
+
repo: bool = False, # ignored if all_cache=True
|
|
224
|
+
self_binary: bool = False,
|
|
225
|
+
) -> None:
|
|
226
|
+
logger = cfg.logger
|
|
227
|
+
|
|
228
|
+
def status(s: str) -> None:
|
|
229
|
+
if quiet:
|
|
230
|
+
return
|
|
231
|
+
logger.I(s)
|
|
232
|
+
|
|
233
|
+
if installed_pkgs:
|
|
234
|
+
status("removing installed packages")
|
|
235
|
+
shutil.rmtree(cfg.data_root, True)
|
|
236
|
+
|
|
237
|
+
# do not record any telemetry data if we're purging it
|
|
238
|
+
if all_state or telemetry:
|
|
239
|
+
if tm := cfg.telemetry:
|
|
240
|
+
tm.discard_events(True)
|
|
241
|
+
|
|
242
|
+
if all_state:
|
|
243
|
+
status("removing state data")
|
|
244
|
+
shutil.rmtree(cfg.state_root, True)
|
|
245
|
+
else:
|
|
246
|
+
if news_read_status:
|
|
247
|
+
status("removing read status of news items")
|
|
248
|
+
cfg.news_read_status.remove()
|
|
249
|
+
|
|
250
|
+
if telemetry:
|
|
251
|
+
status("removing all telemetry data")
|
|
252
|
+
shutil.rmtree(cfg.telemetry_root, True)
|
|
253
|
+
|
|
254
|
+
if all_cache:
|
|
255
|
+
status("removing cached data")
|
|
256
|
+
shutil.rmtree(cfg.cache_root, True)
|
|
257
|
+
else:
|
|
258
|
+
if distfiles:
|
|
259
|
+
status("removing downloaded distfiles")
|
|
260
|
+
# TODO: deduplicate the path derivation
|
|
261
|
+
shutil.rmtree(os.path.join(cfg.cache_root, "distfiles"), True)
|
|
262
|
+
|
|
263
|
+
if progcache:
|
|
264
|
+
status("clearing the Ruyi program cache")
|
|
265
|
+
# TODO: deduplicate the path derivation
|
|
266
|
+
shutil.rmtree(os.path.join(cfg.cache_root, "progcache"), True)
|
|
267
|
+
|
|
268
|
+
if repo:
|
|
269
|
+
# for safety, don't remove the repo if it's outside of Ruyi's XDG
|
|
270
|
+
# cache root
|
|
271
|
+
repo_dir = pathlib.Path(cfg.get_repo_dir()).resolve()
|
|
272
|
+
cache_root = pathlib.Path(cfg.cache_root).resolve()
|
|
273
|
+
|
|
274
|
+
repo_is_below_cache_root = False
|
|
275
|
+
for p in repo_dir.parents:
|
|
276
|
+
if p == cache_root:
|
|
277
|
+
repo_is_below_cache_root = True
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
if not repo_is_below_cache_root:
|
|
281
|
+
logger.W(
|
|
282
|
+
"not removing the Ruyi repo: it is outside of the Ruyi cache directory"
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
status("removing the Ruyi repo")
|
|
286
|
+
shutil.rmtree(repo_dir, True)
|
|
287
|
+
|
|
288
|
+
if self_binary:
|
|
289
|
+
status("removing the ruyi binary")
|
|
290
|
+
try:
|
|
291
|
+
os.unlink(cfg.self_exe)
|
|
292
|
+
except FileNotFoundError:
|
|
293
|
+
# we might have already removed ourselves during the purge; nothing to
|
|
294
|
+
# do now.
|
|
295
|
+
pass
|
ruyi/cli/user_input.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import os.path
|
|
2
|
+
|
|
3
|
+
from ..log import RuyiLogger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def pause_before_continuing(
|
|
7
|
+
logger: RuyiLogger,
|
|
8
|
+
) -> None:
|
|
9
|
+
"""Pause and wait for the user to press Enter before continuing.
|
|
10
|
+
|
|
11
|
+
EOFError should be handled by the caller."""
|
|
12
|
+
|
|
13
|
+
logger.stdout("Press [green]<ENTER>[/] to continue: ", end="")
|
|
14
|
+
input()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def ask_for_yesno_confirmation(
|
|
18
|
+
logger: RuyiLogger,
|
|
19
|
+
prompt: str,
|
|
20
|
+
default: bool = False,
|
|
21
|
+
) -> bool:
|
|
22
|
+
choices_help = "(Y/n)" if default else "(y/N)"
|
|
23
|
+
|
|
24
|
+
while True:
|
|
25
|
+
try:
|
|
26
|
+
logger.stdout(f"{prompt} {choices_help} ", end="")
|
|
27
|
+
user_input = input()
|
|
28
|
+
except EOFError:
|
|
29
|
+
yesno = "YES" if default else "NO"
|
|
30
|
+
logger.W(
|
|
31
|
+
f"EOF while reading user input, assuming the default choice {yesno}"
|
|
32
|
+
)
|
|
33
|
+
return default
|
|
34
|
+
|
|
35
|
+
if not user_input:
|
|
36
|
+
return default
|
|
37
|
+
if user_input in {"Y", "y", "yes"}:
|
|
38
|
+
return True
|
|
39
|
+
if user_input in {"N", "n", "no"}:
|
|
40
|
+
return False
|
|
41
|
+
else:
|
|
42
|
+
logger.stdout(f"Unrecognized input [yellow]'{user_input}'[/].")
|
|
43
|
+
logger.stdout("Accepted choices: Y/y/yes for YES, N/n/no for NO.")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def ask_for_kv_choice(
|
|
47
|
+
logger: RuyiLogger,
|
|
48
|
+
prompt: str,
|
|
49
|
+
choices_kv: dict[str, str],
|
|
50
|
+
default_key: str | None = None,
|
|
51
|
+
) -> str:
|
|
52
|
+
choices_kv_list = list(choices_kv.items())
|
|
53
|
+
choices_prompts = [i[1] for i in choices_kv_list]
|
|
54
|
+
|
|
55
|
+
default_idx: int | None = None
|
|
56
|
+
if default_key is not None:
|
|
57
|
+
for i, k in enumerate(choices_kv_list):
|
|
58
|
+
if k[0] == default_key:
|
|
59
|
+
default_idx = i
|
|
60
|
+
break
|
|
61
|
+
if default_idx is None:
|
|
62
|
+
raise ValueError(f"Default choice key '{default_key}' not in choices")
|
|
63
|
+
|
|
64
|
+
choice = ask_for_choice(logger, prompt, choices_prompts, default_idx)
|
|
65
|
+
return choices_kv_list[choice][0]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def ask_for_choice(
|
|
69
|
+
logger: RuyiLogger,
|
|
70
|
+
prompt: str,
|
|
71
|
+
choices_texts: list[str],
|
|
72
|
+
default_idx: int | None = None,
|
|
73
|
+
) -> int:
|
|
74
|
+
logger.stdout(prompt, end="\n\n")
|
|
75
|
+
for i, choice_text in enumerate(choices_texts):
|
|
76
|
+
logger.stdout(f" {i + 1}. {choice_text}")
|
|
77
|
+
|
|
78
|
+
logger.stdout("")
|
|
79
|
+
|
|
80
|
+
nr_choices = len(choices_texts)
|
|
81
|
+
if default_idx is not None:
|
|
82
|
+
if not (0 <= default_idx < nr_choices):
|
|
83
|
+
raise ValueError(f"Default choice index {default_idx} out of range")
|
|
84
|
+
choices_help = f"(1-{nr_choices}, default {default_idx + 1})"
|
|
85
|
+
else:
|
|
86
|
+
choices_help = f"(1-{nr_choices})"
|
|
87
|
+
while True:
|
|
88
|
+
try:
|
|
89
|
+
user_input = input(f"Choice? {choices_help} ")
|
|
90
|
+
except EOFError:
|
|
91
|
+
raise ValueError("EOF while reading user choice")
|
|
92
|
+
|
|
93
|
+
if default_idx is not None and not user_input:
|
|
94
|
+
return default_idx
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
choice_int = int(user_input)
|
|
98
|
+
except ValueError:
|
|
99
|
+
logger.stdout(f"Unrecognized input [yellow]'{user_input}'[/].")
|
|
100
|
+
logger.stdout(
|
|
101
|
+
f"Accepted choices: an integer number from 1 to {nr_choices} inclusive."
|
|
102
|
+
)
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
if 1 <= choice_int <= nr_choices:
|
|
106
|
+
return choice_int - 1
|
|
107
|
+
|
|
108
|
+
logger.stdout(f"Out-of-range input [yellow]'{user_input}'[/].")
|
|
109
|
+
logger.stdout(
|
|
110
|
+
f"Accepted choices: an integer number from 1 to {nr_choices} inclusive."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def ask_for_file(
|
|
115
|
+
logger: RuyiLogger,
|
|
116
|
+
prompt: str,
|
|
117
|
+
) -> str:
|
|
118
|
+
while True:
|
|
119
|
+
try:
|
|
120
|
+
user_input = input(f"{prompt} ")
|
|
121
|
+
except EOFError:
|
|
122
|
+
raise ValueError("EOF while reading user input")
|
|
123
|
+
|
|
124
|
+
if os.path.exists(user_input):
|
|
125
|
+
return user_input
|
|
126
|
+
|
|
127
|
+
logger.stdout(f"[yellow]'{user_input}'[/] is not a path to an existing file.")
|
ruyi/cli/version_cli.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from .cmd import RootCommand
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .completion import ArgumentParser
|
|
8
|
+
from ..config import GlobalConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VersionCommand(
|
|
12
|
+
RootCommand,
|
|
13
|
+
cmd="version",
|
|
14
|
+
help="Print version information",
|
|
15
|
+
):
|
|
16
|
+
@classmethod
|
|
17
|
+
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def main(cls, cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
22
|
+
return cli_version(cfg, args)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def cli_version(cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
26
|
+
import ruyi
|
|
27
|
+
from ..ruyipkg.host import get_native_host
|
|
28
|
+
from ..version import COPYRIGHT_NOTICE, MPL_REDIST_NOTICE, RUYI_SEMVER
|
|
29
|
+
|
|
30
|
+
print(f"Ruyi {RUYI_SEMVER}\n\nRunning on {get_native_host()}.")
|
|
31
|
+
|
|
32
|
+
if cfg.is_installation_externally_managed:
|
|
33
|
+
print("This Ruyi installation is externally managed.")
|
|
34
|
+
|
|
35
|
+
print()
|
|
36
|
+
|
|
37
|
+
cfg.logger.stdout(COPYRIGHT_NOTICE)
|
|
38
|
+
|
|
39
|
+
# Output the MPL notice only when we actually bundle and depend on the
|
|
40
|
+
# MPL component(s), which right now is only certifi. Keep the condition
|
|
41
|
+
# synced with __main__.py.
|
|
42
|
+
if hasattr(ruyi, "__compiled__") and ruyi.__compiled__.standalone:
|
|
43
|
+
cfg.logger.stdout(MPL_REDIST_NOTICE)
|
|
44
|
+
|
|
45
|
+
return 0
|