ruyi 0.44.0a20251118__py3-none-any.whl → 0.45.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/__main__.py +16 -4
- ruyi/cli/cmd.py +6 -5
- ruyi/cli/config_cli.py +14 -11
- ruyi/cli/main.py +34 -17
- ruyi/cli/oobe.py +10 -10
- ruyi/cli/self_cli.py +49 -36
- ruyi/cli/user_input.py +42 -12
- ruyi/cli/version_cli.py +11 -5
- ruyi/config/__init__.py +30 -10
- ruyi/config/errors.py +19 -7
- ruyi/device/provision.py +116 -55
- ruyi/device/provision_cli.py +6 -3
- ruyi/i18n/__init__.py +129 -0
- ruyi/log/__init__.py +6 -5
- ruyi/mux/runtime.py +19 -6
- ruyi/mux/venv/maker.py +93 -35
- ruyi/mux/venv/venv_cli.py +13 -10
- ruyi/pluginhost/plugin_cli.py +4 -3
- ruyi/resource_bundle/__init__.py +22 -8
- ruyi/resource_bundle/__main__.py +6 -5
- ruyi/resource_bundle/data.py +13 -9
- ruyi/ruyipkg/admin_checksum.py +4 -1
- ruyi/ruyipkg/admin_cli.py +9 -6
- ruyi/ruyipkg/augmented_pkg.py +15 -14
- ruyi/ruyipkg/checksum.py +8 -2
- ruyi/ruyipkg/distfile.py +33 -9
- ruyi/ruyipkg/entity.py +12 -2
- ruyi/ruyipkg/entity_cli.py +20 -12
- ruyi/ruyipkg/entity_provider.py +11 -2
- ruyi/ruyipkg/fetcher.py +38 -9
- ruyi/ruyipkg/install.py +163 -64
- ruyi/ruyipkg/install_cli.py +18 -15
- ruyi/ruyipkg/list.py +27 -20
- ruyi/ruyipkg/list_cli.py +12 -7
- ruyi/ruyipkg/news.py +23 -11
- ruyi/ruyipkg/news_cli.py +10 -7
- ruyi/ruyipkg/profile_cli.py +8 -2
- ruyi/ruyipkg/repo.py +22 -8
- ruyi/ruyipkg/unpack.py +42 -8
- ruyi/ruyipkg/unpack_method.py +5 -1
- ruyi/ruyipkg/update_cli.py +8 -3
- ruyi/telemetry/aggregate.py +5 -0
- ruyi/telemetry/provider.py +292 -105
- ruyi/telemetry/store.py +68 -15
- ruyi/telemetry/telemetry_cli.py +32 -13
- ruyi/utils/git.py +18 -11
- ruyi/utils/prereqs.py +10 -5
- ruyi/utils/ssl_patch.py +2 -1
- ruyi/version.py +9 -3
- {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/METADATA +4 -2
- ruyi-0.45.0.dist-info/RECORD +103 -0
- {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/WHEEL +1 -1
- ruyi-0.44.0a20251118.dist-info/RECORD +0 -102
- {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/entry_points.txt +0 -0
- {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/licenses/LICENSE-Apache.txt +0 -0
ruyi/cli/user_input.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os.path
|
|
2
2
|
|
|
3
|
+
from ..i18n import _
|
|
3
4
|
from ..log import RuyiLogger
|
|
4
5
|
|
|
5
6
|
|
|
@@ -10,7 +11,7 @@ def pause_before_continuing(
|
|
|
10
11
|
|
|
11
12
|
EOFError should be handled by the caller."""
|
|
12
13
|
|
|
13
|
-
logger.stdout("Press [green]<ENTER>[/] to continue: ", end="")
|
|
14
|
+
logger.stdout(_("Press [green]<ENTER>[/] to continue: "), end="")
|
|
14
15
|
input()
|
|
15
16
|
|
|
16
17
|
|
|
@@ -26,9 +27,11 @@ def ask_for_yesno_confirmation(
|
|
|
26
27
|
logger.stdout(f"{prompt} {choices_help} ", end="")
|
|
27
28
|
user_input = input()
|
|
28
29
|
except EOFError:
|
|
29
|
-
yesno = "YES" if default else "NO"
|
|
30
|
+
yesno = _("YES") if default else _("NO")
|
|
30
31
|
logger.W(
|
|
31
|
-
|
|
32
|
+
_(
|
|
33
|
+
"EOF while reading user input, assuming the default choice {yesno}"
|
|
34
|
+
).format(yesno=yesno)
|
|
32
35
|
)
|
|
33
36
|
return default
|
|
34
37
|
|
|
@@ -39,8 +42,12 @@ def ask_for_yesno_confirmation(
|
|
|
39
42
|
if user_input in {"N", "n", "no"}:
|
|
40
43
|
return False
|
|
41
44
|
else:
|
|
42
|
-
logger.stdout(
|
|
43
|
-
|
|
45
|
+
logger.stdout(
|
|
46
|
+
_("Unrecognized input [yellow]'{user_input}'[/].").format(
|
|
47
|
+
user_input=user_input
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
logger.stdout(_("Accepted choices: Y/y/yes for YES, N/n/no for NO."))
|
|
44
51
|
|
|
45
52
|
|
|
46
53
|
def ask_for_kv_choice(
|
|
@@ -81,12 +88,19 @@ def ask_for_choice(
|
|
|
81
88
|
if default_idx is not None:
|
|
82
89
|
if not (0 <= default_idx < nr_choices):
|
|
83
90
|
raise ValueError(f"Default choice index {default_idx} out of range")
|
|
84
|
-
choices_help =
|
|
91
|
+
choices_help = _("(1-{nr_choices}, default {default})").format(
|
|
92
|
+
nr_choices=nr_choices,
|
|
93
|
+
default=default_idx + 1,
|
|
94
|
+
)
|
|
85
95
|
else:
|
|
86
|
-
choices_help =
|
|
96
|
+
choices_help = _("(1-{nr_choices})").format(nr_choices=nr_choices)
|
|
87
97
|
while True:
|
|
88
98
|
try:
|
|
89
|
-
user_input = input(
|
|
99
|
+
user_input = input(
|
|
100
|
+
_("Choice? {choices_help} ").format(
|
|
101
|
+
choices_help=choices_help,
|
|
102
|
+
)
|
|
103
|
+
)
|
|
90
104
|
except EOFError:
|
|
91
105
|
raise ValueError("EOF while reading user choice")
|
|
92
106
|
|
|
@@ -96,18 +110,34 @@ def ask_for_choice(
|
|
|
96
110
|
try:
|
|
97
111
|
choice_int = int(user_input)
|
|
98
112
|
except ValueError:
|
|
99
|
-
logger.stdout(f"Unrecognized input [yellow]'{user_input}'[/].")
|
|
100
113
|
logger.stdout(
|
|
101
|
-
|
|
114
|
+
_("Unrecognized input [yellow]'{user_input}'[/].").format(
|
|
115
|
+
user_input=user_input,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
logger.stdout(
|
|
119
|
+
_(
|
|
120
|
+
"Accepted choices: an integer number from 1 to {nr_choices} inclusive."
|
|
121
|
+
).format(
|
|
122
|
+
nr_choices=nr_choices,
|
|
123
|
+
)
|
|
102
124
|
)
|
|
103
125
|
continue
|
|
104
126
|
|
|
105
127
|
if 1 <= choice_int <= nr_choices:
|
|
106
128
|
return choice_int - 1
|
|
107
129
|
|
|
108
|
-
logger.stdout(f"Out-of-range input [yellow]'{user_input}'[/].")
|
|
109
130
|
logger.stdout(
|
|
110
|
-
|
|
131
|
+
_("Out-of-range input [yellow]'{user_input}'[/].").format(
|
|
132
|
+
user_input=user_input,
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
logger.stdout(
|
|
136
|
+
_(
|
|
137
|
+
"Accepted choices: an integer number from 1 to {nr_choices} inclusive."
|
|
138
|
+
).format(
|
|
139
|
+
nr_choices=nr_choices,
|
|
140
|
+
)
|
|
111
141
|
)
|
|
112
142
|
|
|
113
143
|
|
ruyi/cli/version_cli.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
|
+
from ..i18n import _
|
|
4
5
|
from .cmd import RootCommand
|
|
5
6
|
|
|
6
7
|
if TYPE_CHECKING:
|
|
@@ -11,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
11
12
|
class VersionCommand(
|
|
12
13
|
RootCommand,
|
|
13
14
|
cmd="version",
|
|
14
|
-
help="Print version information",
|
|
15
|
+
help=_("Print version information"),
|
|
15
16
|
):
|
|
16
17
|
@classmethod
|
|
17
18
|
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
@@ -27,19 +28,24 @@ def cli_version(cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
|
27
28
|
from ..ruyipkg.host import get_native_host
|
|
28
29
|
from ..version import COPYRIGHT_NOTICE, MPL_REDIST_NOTICE, RUYI_SEMVER
|
|
29
30
|
|
|
30
|
-
print(
|
|
31
|
+
print(
|
|
32
|
+
_("Ruyi {version}\n\nRunning on {host}.").format(
|
|
33
|
+
version=RUYI_SEMVER,
|
|
34
|
+
host=get_native_host(),
|
|
35
|
+
)
|
|
36
|
+
)
|
|
31
37
|
|
|
32
38
|
if cfg.is_installation_externally_managed:
|
|
33
|
-
print("This Ruyi installation is externally managed.")
|
|
39
|
+
print(_("This Ruyi installation is externally managed."))
|
|
34
40
|
|
|
35
41
|
print()
|
|
36
42
|
|
|
37
|
-
cfg.logger.stdout(COPYRIGHT_NOTICE)
|
|
43
|
+
cfg.logger.stdout(_(COPYRIGHT_NOTICE))
|
|
38
44
|
|
|
39
45
|
# Output the MPL notice only when we actually bundle and depend on the
|
|
40
46
|
# MPL component(s), which right now is only certifi. Keep the condition
|
|
41
47
|
# synced with __main__.py.
|
|
42
48
|
if hasattr(ruyi, "__compiled__") and ruyi.__compiled__.standalone:
|
|
43
|
-
cfg.logger.stdout(MPL_REDIST_NOTICE)
|
|
49
|
+
cfg.logger.stdout(_(MPL_REDIST_NOTICE))
|
|
44
50
|
|
|
45
51
|
return 0
|
ruyi/config/__init__.py
CHANGED
|
@@ -18,6 +18,13 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from ..utils.xdg_basedir import XDGPathEntry
|
|
19
19
|
from .news import NewsReadStatusStore
|
|
20
20
|
|
|
21
|
+
import babel
|
|
22
|
+
# not sure why Pyright insists on individual imports
|
|
23
|
+
# otherwise, at the use site (`except babel.core.UnknownLocaleError`):
|
|
24
|
+
# error: "core" is not a known attribute of module "babel" (reportAttributeAccessIssue)
|
|
25
|
+
from babel.core import UnknownLocaleError
|
|
26
|
+
|
|
27
|
+
from ..i18n import _
|
|
21
28
|
from . import errors
|
|
22
29
|
from . import schema
|
|
23
30
|
|
|
@@ -115,7 +122,11 @@ class GlobalConfig:
|
|
|
115
122
|
if iem is not None and not is_global_scope:
|
|
116
123
|
iem_cfg_key = f"{schema.SECTION_INSTALLATION}.{schema.KEY_INSTALLATION_EXTERNALLY_MANAGED}"
|
|
117
124
|
self.logger.W(
|
|
118
|
-
|
|
125
|
+
_(
|
|
126
|
+
"the config key [yellow]{key}[/] cannot be set from user config; ignoring"
|
|
127
|
+
).format(
|
|
128
|
+
key=iem_cfg_key,
|
|
129
|
+
),
|
|
119
130
|
)
|
|
120
131
|
else:
|
|
121
132
|
self.is_installation_externally_managed = bool(iem)
|
|
@@ -133,7 +144,11 @@ class GlobalConfig:
|
|
|
133
144
|
if self.override_repo_dir:
|
|
134
145
|
if not pathlib.Path(self.override_repo_dir).is_absolute():
|
|
135
146
|
self.logger.W(
|
|
136
|
-
|
|
147
|
+
_(
|
|
148
|
+
"the local repo path '{path}' is not absolute; ignoring"
|
|
149
|
+
).format(
|
|
150
|
+
path=self.override_repo_dir,
|
|
151
|
+
)
|
|
137
152
|
)
|
|
138
153
|
self.override_repo_dir = None
|
|
139
154
|
|
|
@@ -274,6 +289,15 @@ class GlobalConfig:
|
|
|
274
289
|
def lang_code(self) -> str:
|
|
275
290
|
return self._lang_code
|
|
276
291
|
|
|
292
|
+
@cached_property
|
|
293
|
+
def babel_locale(self) -> babel.Locale:
|
|
294
|
+
try:
|
|
295
|
+
return babel.Locale.parse(self.lang_code)
|
|
296
|
+
except UnknownLocaleError:
|
|
297
|
+
# this can happen in case of unrecognized locale names, which
|
|
298
|
+
# apparently falls back to "C"
|
|
299
|
+
return babel.Locale.parse("en_US")
|
|
300
|
+
|
|
277
301
|
@property
|
|
278
302
|
def cache_root(self) -> os.PathLike[Any]:
|
|
279
303
|
return self._dirs.app_cache
|
|
@@ -297,17 +321,13 @@ class GlobalConfig:
|
|
|
297
321
|
def telemetry_root(self) -> os.PathLike[Any]:
|
|
298
322
|
return pathlib.Path(self.ensure_state_dir()) / "telemetry"
|
|
299
323
|
|
|
300
|
-
@property
|
|
301
|
-
def telemetry(self) -> "TelemetryProvider | None":
|
|
302
|
-
return None if self.telemetry_mode == "off" else self._telemetry_provider
|
|
303
|
-
|
|
304
324
|
@cached_property
|
|
305
|
-
def
|
|
306
|
-
"""Do not access directly; use the ``telemetry`` property instead."""
|
|
307
|
-
|
|
325
|
+
def telemetry(self) -> "TelemetryProvider":
|
|
308
326
|
from ..telemetry.provider import TelemetryProvider
|
|
309
327
|
|
|
310
|
-
|
|
328
|
+
# for allowing minimal uploads when telemetry is off
|
|
329
|
+
minimal_mode = self.telemetry_mode == "off"
|
|
330
|
+
return TelemetryProvider(self, minimal_mode)
|
|
311
331
|
|
|
312
332
|
@property
|
|
313
333
|
def telemetry_mode(self) -> str:
|
ruyi/config/errors.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from os import PathLike
|
|
2
2
|
from typing import Any, Sequence
|
|
3
3
|
|
|
4
|
+
from ..i18n import _
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class InvalidConfigSectionError(Exception):
|
|
6
8
|
def __init__(self, section: str) -> None:
|
|
@@ -8,7 +10,7 @@ class InvalidConfigSectionError(Exception):
|
|
|
8
10
|
self._section = section
|
|
9
11
|
|
|
10
12
|
def __str__(self) -> str:
|
|
11
|
-
return
|
|
13
|
+
return _("invalid config section: {section}").format(section=self._section)
|
|
12
14
|
|
|
13
15
|
def __repr__(self) -> str:
|
|
14
16
|
return f"InvalidConfigSectionError({self._section!r})"
|
|
@@ -20,7 +22,7 @@ class InvalidConfigKeyError(Exception):
|
|
|
20
22
|
self._key = key
|
|
21
23
|
|
|
22
24
|
def __str__(self) -> str:
|
|
23
|
-
return
|
|
25
|
+
return _("invalid config key: {key}").format(key=self._key)
|
|
24
26
|
|
|
25
27
|
def __repr__(self) -> str:
|
|
26
28
|
return f"InvalidConfigKeyError({self._key:!r})"
|
|
@@ -39,7 +41,13 @@ class InvalidConfigValueTypeError(TypeError):
|
|
|
39
41
|
self._expected = expected
|
|
40
42
|
|
|
41
43
|
def __str__(self) -> str:
|
|
42
|
-
return
|
|
44
|
+
return _(
|
|
45
|
+
"invalid value type for config key {key}: {actual_type}, expected {expected_type}"
|
|
46
|
+
).format(
|
|
47
|
+
key=self._key,
|
|
48
|
+
actual_type=type(self._val),
|
|
49
|
+
expected_type=self._expected,
|
|
50
|
+
)
|
|
43
51
|
|
|
44
52
|
def __repr__(self) -> str:
|
|
45
53
|
return f"InvalidConfigValueTypeError({self._key!r}, {self._val!r}, {self._expected:!r})"
|
|
@@ -58,8 +66,10 @@ class InvalidConfigValueError(ValueError):
|
|
|
58
66
|
self._typ = typ
|
|
59
67
|
|
|
60
68
|
def __str__(self) -> str:
|
|
61
|
-
return (
|
|
62
|
-
|
|
69
|
+
return _("invalid config value for key {key} (type {typ}): {val}").format(
|
|
70
|
+
key=self._key,
|
|
71
|
+
typ=self._typ,
|
|
72
|
+
val=self._val,
|
|
63
73
|
)
|
|
64
74
|
|
|
65
75
|
def __repr__(self) -> str:
|
|
@@ -74,7 +84,7 @@ class MalformedConfigFileError(Exception):
|
|
|
74
84
|
self._path = path
|
|
75
85
|
|
|
76
86
|
def __str__(self) -> str:
|
|
77
|
-
return
|
|
87
|
+
return _("malformed config file: {path}").format(path=self._path)
|
|
78
88
|
|
|
79
89
|
def __repr__(self) -> str:
|
|
80
90
|
return f"MalformedConfigFileError({self._path:!r})"
|
|
@@ -86,7 +96,9 @@ class ProtectedGlobalConfigError(Exception):
|
|
|
86
96
|
self._key = key
|
|
87
97
|
|
|
88
98
|
def __str__(self) -> str:
|
|
89
|
-
return
|
|
99
|
+
return _("attempt to modify protected global config key: {key}").format(
|
|
100
|
+
key=self._key,
|
|
101
|
+
)
|
|
90
102
|
|
|
91
103
|
def __repr__(self) -> str:
|
|
92
104
|
return f"ProtectedGlobalConfigError({self._key!r})"
|