ruyi 0.41.0a20250911__tar.gz → 0.41.0b20250924__tar.gz
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-0.41.0a20250911 → ruyi-0.41.0b20250924}/PKG-INFO +5 -4
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/README.md +2 -2
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/pyproject.toml +1 -1
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/cmd.py +3 -1
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/completion.py +3 -1
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/main.py +6 -1
- ruyi-0.41.0b20250924/ruyi/cli/oobe.py +79 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/device/provision.py +26 -5
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/news.py +19 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/news_cli.py +15 -1
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/repo.py +7 -2
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/update_cli.py +1 -6
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/provider.py +4 -14
- ruyi-0.41.0b20250924/ruyi/utils/mounts.py +45 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/LICENSE-Apache.txt +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/__main__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/builtin_commands.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/completer.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/config_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/self_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/user_input.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/cli/version_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/config/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/config/editor.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/config/errors.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/config/news.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/config/schema.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/device/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/device/provision_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/log/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/.gitignore +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/runtime.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/venv/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/venv/emulator_cfg.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/venv/maker.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/venv/venv_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/mux/venv_cfg.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/api.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/ctx.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/paths.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/plugin_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/pluginhost/unsandboxed.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/py.typed +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/resource_bundle/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/resource_bundle/__main__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/resource_bundle/data.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/admin_checksum.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/admin_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/atom.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/augmented_pkg.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/canonical_dump.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/checksum.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/cli_completion.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/distfile.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/entity.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/entity_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/entity_provider.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/fetch.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/host.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/install.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/install_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/list.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/list_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/list_filter.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/msg.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/news_store.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/pkg_manifest.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/profile.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/profile_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/protocols.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/state.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/unpack.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/ruyipkg/unpack_method.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/aggregate.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/event.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/node_info.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/scope.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/store.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/telemetry/telemetry_cli.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/__init__.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/ar.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/ci.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/frontmatter.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/git.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/global_mode.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/l10n.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/markdown.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/nuitka.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/porcelain.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/prereqs.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/ssl_patch.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/templating.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/toml.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/url.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/utils/xdg_basedir.py +0 -0
- {ruyi-0.41.0a20250911 → ruyi-0.41.0b20250924}/ruyi/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ruyi
|
|
3
|
-
Version: 0.41.
|
|
3
|
+
Version: 0.41.0b20250924
|
|
4
4
|
Summary: Package manager for RuyiSDK
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -203,6 +203,7 @@ License: Apache License
|
|
|
203
203
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
204
204
|
See the License for the specific language governing permissions and
|
|
205
205
|
limitations under the License.
|
|
206
|
+
License-File: LICENSE-Apache.txt
|
|
206
207
|
Keywords: ruyi,ruyisdk
|
|
207
208
|
Author: WANG Xuerui
|
|
208
209
|
Author-email: wangxuerui@iscas.ac.cn
|
|
@@ -271,6 +272,8 @@ PyPI installation, but the one-file distribution is a bit easier to set up
|
|
|
271
272
|
because one doesn't have to first configure a Python environment. Either way,
|
|
272
273
|
the feature set should be the same.
|
|
273
274
|
|
|
275
|
+
Detailed installation instructions are also available [at our documentation site](https://ruyisdk.org/en/docs/Package-Manager/installation).
|
|
276
|
+
|
|
274
277
|
### ✅ Recommended: Install from PyPI
|
|
275
278
|
|
|
276
279
|
This is the recommended way to install `ruyi` on your machine. In any Python
|
|
@@ -431,8 +434,6 @@ We collect the following information with `ruyi`:
|
|
|
431
434
|
* without exposing any parameters
|
|
432
435
|
* invocation time is recorded with a granularity of 1 minute
|
|
433
436
|
|
|
434
|
-
You can see our Privacy Policy on the RuyiSDK website.
|
|
435
|
-
|
|
436
437
|
You can see [our Privacy Policy][privacy-policy-en] ([中文][privacy-policy-zh])
|
|
437
438
|
on the RuyiSDK website.
|
|
438
439
|
|
|
@@ -28,6 +28,8 @@ PyPI installation, but the one-file distribution is a bit easier to set up
|
|
|
28
28
|
because one doesn't have to first configure a Python environment. Either way,
|
|
29
29
|
the feature set should be the same.
|
|
30
30
|
|
|
31
|
+
Detailed installation instructions are also available [at our documentation site](https://ruyisdk.org/en/docs/Package-Manager/installation).
|
|
32
|
+
|
|
31
33
|
### ✅ Recommended: Install from PyPI
|
|
32
34
|
|
|
33
35
|
This is the recommended way to install `ruyi` on your machine. In any Python
|
|
@@ -188,8 +190,6 @@ We collect the following information with `ruyi`:
|
|
|
188
190
|
* without exposing any parameters
|
|
189
191
|
* invocation time is recorded with a granularity of 1 minute
|
|
190
192
|
|
|
191
|
-
You can see our Privacy Policy on the RuyiSDK website.
|
|
192
|
-
|
|
193
193
|
You can see [our Privacy Policy][privacy-policy-en] ([中文][privacy-policy-zh])
|
|
194
194
|
on the RuyiSDK website.
|
|
195
195
|
|
|
@@ -198,7 +198,9 @@ class RootCommand(
|
|
|
198
198
|
return 0
|
|
199
199
|
# the rest are implementation of "--output-completion-script"
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
from .completion import SUPPORTED_SHELLS
|
|
202
|
+
|
|
203
|
+
if sh not in SUPPORTED_SHELLS:
|
|
202
204
|
raise ValueError(f"Unsupported shell: {sh}")
|
|
203
205
|
|
|
204
206
|
import sys
|
|
@@ -5,7 +5,9 @@ see https://github.com/kislyuk/argcomplete/issues/443 for why this is needed
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
|
-
from typing import Any, Callable, Optional, Sequence, cast
|
|
8
|
+
from typing import Any, Callable, Final, Optional, Sequence, cast
|
|
9
|
+
|
|
10
|
+
SUPPORTED_SHELLS: Final[set[str]] = {"bash", "zsh"}
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
class ArgcompleteAction(argparse.Action):
|
|
@@ -7,6 +7,7 @@ from ..config import GlobalConfig
|
|
|
7
7
|
from ..telemetry.scope import TelemetryScope
|
|
8
8
|
from ..utils.global_mode import GlobalModeProvider
|
|
9
9
|
from . import RUYI_ENTRYPOINT_NAME
|
|
10
|
+
from .oobe import OOBE
|
|
10
11
|
|
|
11
12
|
ALLOWED_RUYI_ENTRYPOINT_NAMES: Final = (
|
|
12
13
|
RUYI_ENTRYPOINT_NAME,
|
|
@@ -21,11 +22,15 @@ def is_called_as_ruyi(argv0: str) -> bool:
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def main(gm: GlobalModeProvider, gc: GlobalConfig, argv: list[str]) -> int:
|
|
25
|
+
oobe = OOBE(gc)
|
|
26
|
+
|
|
24
27
|
if tm := gc.telemetry:
|
|
25
28
|
tm.check_first_run_status()
|
|
26
29
|
tm.init_installation(False)
|
|
27
30
|
atexit.register(tm.flush)
|
|
28
|
-
tm.
|
|
31
|
+
oobe.handlers.append(tm.oobe_prompt)
|
|
32
|
+
|
|
33
|
+
oobe.maybe_prompt()
|
|
29
34
|
|
|
30
35
|
if not is_called_as_ruyi(gm.argv0):
|
|
31
36
|
from ..mux.runtime import mux_main
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""First-run (Out-of-the-box) experience for ``ruyi``."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Callable, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from ..config import GlobalConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SHELL_AUTO_COMPLETION_TIP = """
|
|
12
|
+
[bold green]tip[/]: you can enable shell auto-completion for [yellow]ruyi[/] by adding the
|
|
13
|
+
following line to your [green]{shrc}[/], if you have not done so already:
|
|
14
|
+
|
|
15
|
+
[green]eval "$(ruyi --output-completion-script={shell})"[/]
|
|
16
|
+
|
|
17
|
+
You can do so by running the following command later:
|
|
18
|
+
|
|
19
|
+
[green]echo 'eval "$(ruyi --output-completion-script={shell})"' >> {shrc}[/]
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OOBE:
|
|
24
|
+
"""Out-of-the-box experience (OOBE) handler for RuyiSDK CLI."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, gc: "GlobalConfig") -> None:
|
|
27
|
+
self._gc = gc
|
|
28
|
+
self.handlers: list[Callable[[], None]] = [
|
|
29
|
+
self._builtin_shell_completion_tip,
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def is_first_run(self) -> bool:
|
|
33
|
+
if tm := self._gc.telemetry:
|
|
34
|
+
return tm.is_first_run
|
|
35
|
+
# cannot reliably determine first run status without telemetry
|
|
36
|
+
# we may revisit this later if it turns out users want OOBE tips even
|
|
37
|
+
# if they know how to disable telemetry (hence more likely to be power
|
|
38
|
+
# users)
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
def should_prompt(self) -> bool:
|
|
42
|
+
from ..utils.global_mode import is_env_var_truthy
|
|
43
|
+
|
|
44
|
+
if is_env_var_truthy(os.environ, "RUYI_DEBUG_FORCE_FIRST_RUN"):
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
return self.is_first_run() and sys.stdin.isatty()
|
|
48
|
+
|
|
49
|
+
def maybe_prompt(self) -> None:
|
|
50
|
+
if not self.should_prompt():
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
logger = self._gc.logger
|
|
54
|
+
logger.I(
|
|
55
|
+
"Welcome to RuyiSDK! This appears to be your first run of [yellow]ruyi[/].",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for handler in self.handlers:
|
|
59
|
+
handler()
|
|
60
|
+
|
|
61
|
+
def _builtin_shell_completion_tip(self) -> None:
|
|
62
|
+
from ..telemetry.node_info import probe_for_shell
|
|
63
|
+
from .completion import SUPPORTED_SHELLS
|
|
64
|
+
|
|
65
|
+
# Only show the tip if we're not externally managed by a package manager,
|
|
66
|
+
# because we expect proper shell integration to be done by distro packagers
|
|
67
|
+
if self._gc.is_installation_externally_managed:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
shell = probe_for_shell(os.environ)
|
|
71
|
+
if shell not in SUPPORTED_SHELLS:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
self._gc.logger.stdout(
|
|
75
|
+
SHELL_AUTO_COMPLETION_TIP.format(
|
|
76
|
+
shell=shell,
|
|
77
|
+
shrc=f"~/.{shell}rc",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
@@ -15,7 +15,7 @@ from ..ruyipkg.pkg_manifest import (
|
|
|
15
15
|
PartitionMapDecl,
|
|
16
16
|
)
|
|
17
17
|
from ..ruyipkg.repo import MetadataRepo
|
|
18
|
-
from ..utils import prereqs
|
|
18
|
+
from ..utils import mounts, prereqs
|
|
19
19
|
|
|
20
20
|
if TYPE_CHECKING:
|
|
21
21
|
from ..ruyipkg.pkg_manifest import BoundPackageManifest
|
|
@@ -209,10 +209,31 @@ information you will need later.
|
|
|
209
209
|
)
|
|
210
210
|
for part in requested_host_blkdevs:
|
|
211
211
|
part_desc = get_part_desc(part)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
|
|
213
|
+
while True:
|
|
214
|
+
path = user_input.ask_for_file(
|
|
215
|
+
logger,
|
|
216
|
+
f"Please give the path for the {part_desc}:",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Retrieve the latest mount info in case the user un-mounts
|
|
220
|
+
# on seeing the prompt
|
|
221
|
+
all_mounts = mounts.parse_mounts()
|
|
222
|
+
blkdev_mounts = [m for m in all_mounts if m.source_is_blkdev]
|
|
223
|
+
path_valid = True
|
|
224
|
+
for m in blkdev_mounts:
|
|
225
|
+
if m.source_path.samefile(path):
|
|
226
|
+
logger.W(
|
|
227
|
+
f"path [cyan]'{path}'[/] is currently mounted at [yellow]'{m.target}'[/]"
|
|
228
|
+
)
|
|
229
|
+
logger.I(
|
|
230
|
+
"rejecting the path for safety; please double-check and retry"
|
|
231
|
+
)
|
|
232
|
+
path_valid = False
|
|
233
|
+
break
|
|
234
|
+
if path_valid:
|
|
235
|
+
break
|
|
236
|
+
|
|
216
237
|
host_blkdev_map[part] = path
|
|
217
238
|
|
|
218
239
|
# final confirmation
|
|
@@ -32,6 +32,25 @@ def print_news_item_titles(
|
|
|
32
32
|
logger.stdout(tbl)
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def maybe_notify_unread_news(
|
|
36
|
+
gc: GlobalConfig,
|
|
37
|
+
prompt_no_unread: bool = True,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Check if there are new newsitems, notify the user if so."""
|
|
40
|
+
|
|
41
|
+
unread_newsitems = gc.repo.news_store().list(True)
|
|
42
|
+
if unread_newsitems:
|
|
43
|
+
gc.logger.stdout(f"\nThere are {len(unread_newsitems)} new news item(s):\n")
|
|
44
|
+
print_news_item_titles(gc.logger, unread_newsitems, gc.lang_code)
|
|
45
|
+
gc.logger.stdout("\nYou can read them with [yellow]ruyi news read[/].")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if prompt_no_unread:
|
|
49
|
+
gc.logger.stdout(
|
|
50
|
+
"\nAll news items have been read. To see a list of them, run [yellow]ruyi news list[/].\n"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
35
54
|
def do_news_list(
|
|
36
55
|
cfg: GlobalConfig,
|
|
37
56
|
only_unread: bool,
|
|
@@ -12,11 +12,25 @@ class NewsCommand(
|
|
|
12
12
|
RootCommand,
|
|
13
13
|
cmd="news",
|
|
14
14
|
has_subcommands=True,
|
|
15
|
+
is_subcommand_required=False,
|
|
16
|
+
has_main=True,
|
|
15
17
|
help="List and read news items from configured repository",
|
|
16
18
|
):
|
|
19
|
+
_my_parser: "ArgumentParser | None" = None
|
|
20
|
+
|
|
17
21
|
@classmethod
|
|
18
22
|
def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
|
|
19
|
-
|
|
23
|
+
cls._my_parser = p
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def main(cls, cfg: "GlobalConfig", args: argparse.Namespace) -> int:
|
|
27
|
+
from .news import maybe_notify_unread_news
|
|
28
|
+
|
|
29
|
+
assert cls._my_parser is not None
|
|
30
|
+
cls._my_parser.print_help()
|
|
31
|
+
maybe_notify_unread_news(cfg, True)
|
|
32
|
+
|
|
33
|
+
return 0
|
|
20
34
|
|
|
21
35
|
|
|
22
36
|
class NewsListCommand(
|
|
@@ -284,7 +284,8 @@ class MetadataRepo(ProvidesPackageManifests):
|
|
|
284
284
|
self.repo = Repository(self.root)
|
|
285
285
|
return self.repo
|
|
286
286
|
|
|
287
|
-
self.logger.
|
|
287
|
+
self.logger.I(f"the package repository does not exist at [yellow]{self.root}[/]")
|
|
288
|
+
self.logger.I(f"cloning from [cyan link={self.remote}]{self.remote}[/]")
|
|
288
289
|
|
|
289
290
|
with RemoteGitProgressIndicator() as pr:
|
|
290
291
|
repo = clone_repository(
|
|
@@ -306,13 +307,15 @@ class MetadataRepo(ProvidesPackageManifests):
|
|
|
306
307
|
return self.repo
|
|
307
308
|
|
|
308
309
|
def sync(self) -> None:
|
|
310
|
+
self._gc.logger.I("updating the package repository")
|
|
311
|
+
|
|
309
312
|
repo = self.ensure_git_repo()
|
|
310
313
|
|
|
311
314
|
# only manage the repo settings on the user's behalf if the user
|
|
312
315
|
# has not overridden the repo directory themselves
|
|
313
316
|
allow_auto_management = self._gc.override_repo_dir is None
|
|
314
317
|
|
|
315
|
-
|
|
318
|
+
pull_ff_or_die(
|
|
316
319
|
self.logger,
|
|
317
320
|
repo,
|
|
318
321
|
"origin",
|
|
@@ -321,6 +324,8 @@ class MetadataRepo(ProvidesPackageManifests):
|
|
|
321
324
|
allow_auto_management=allow_auto_management,
|
|
322
325
|
)
|
|
323
326
|
|
|
327
|
+
self._gc.logger.I("package repository is updated")
|
|
328
|
+
|
|
324
329
|
@property
|
|
325
330
|
def global_config(self) -> "GlobalConfig":
|
|
326
331
|
return self._gc
|
|
@@ -44,11 +44,6 @@ Re-run [yellow]ruyi install[/] to upgrade, and don't forget to re-create any aff
|
|
|
44
44
|
virtual environments."""
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
unread_newsitems = mr.news_store().list(True)
|
|
49
|
-
if unread_newsitems:
|
|
50
|
-
logger.stdout(f"\nThere are {len(unread_newsitems)} new news item(s):\n")
|
|
51
|
-
news.print_news_item_titles(logger, unread_newsitems, cfg.lang_code)
|
|
52
|
-
logger.stdout("\nYou can read them with [yellow]ruyi news read[/].")
|
|
47
|
+
news.maybe_notify_unread_news(cfg, False)
|
|
53
48
|
|
|
54
49
|
return 0
|
|
@@ -3,7 +3,6 @@ import datetime
|
|
|
3
3
|
import functools
|
|
4
4
|
import json
|
|
5
5
|
import pathlib
|
|
6
|
-
import sys
|
|
7
6
|
import time
|
|
8
7
|
from typing import Callable, TYPE_CHECKING, cast
|
|
9
8
|
import uuid
|
|
@@ -19,9 +18,7 @@ if TYPE_CHECKING:
|
|
|
19
18
|
|
|
20
19
|
FALLBACK_PM_TELEMETRY_ENDPOINT = "https://api.ruyisdk.cn/telemetry/pm/"
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
Welcome to RuyiSDK! This appears to be your first run of [yellow]ruyi[/].
|
|
24
|
-
|
|
21
|
+
TELEMETRY_INITIAL_UPLOAD_PROMPT = """
|
|
25
22
|
By default, the RuyiSDK team collects anonymous usage data to help us improve
|
|
26
23
|
the product. No personal information or detail about your project is ever
|
|
27
24
|
collected. The data will be uploaded to RuyiSDK team-managed servers located
|
|
@@ -388,19 +385,12 @@ class TelemetryProvider:
|
|
|
388
385
|
|
|
389
386
|
return store.upload_staged_payloads()
|
|
390
387
|
|
|
391
|
-
def
|
|
392
|
-
"""
|
|
393
|
-
Ask whether the user consents to a first-run telemetry upload when
|
|
394
|
-
running for the first time (OOBE) and with an interactive stdin.
|
|
395
|
-
"""
|
|
396
|
-
|
|
397
|
-
# Only prompt if this is first run and stdin is a TTY
|
|
398
|
-
if not (self.is_first_run and sys.stdin.isatty()):
|
|
399
|
-
return
|
|
388
|
+
def oobe_prompt(self) -> None:
|
|
389
|
+
"""Ask whether the user consents to a first-run telemetry upload."""
|
|
400
390
|
|
|
401
391
|
from ..cli import user_input
|
|
402
392
|
|
|
403
|
-
self.logger.
|
|
393
|
+
self.logger.stdout(TELEMETRY_INITIAL_UPLOAD_PROMPT)
|
|
404
394
|
if not user_input.ask_for_yesno_confirmation(
|
|
405
395
|
self.logger,
|
|
406
396
|
"Do you agree?",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Utilities for parsing mount information from /proc/self/mounts."""
|
|
2
|
+
|
|
3
|
+
import pathlib
|
|
4
|
+
import re
|
|
5
|
+
from typing import NamedTuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MountInfo(NamedTuple):
|
|
9
|
+
source: str
|
|
10
|
+
target: str
|
|
11
|
+
fstype: str
|
|
12
|
+
options: list[str]
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def source_path(self) -> pathlib.Path:
|
|
16
|
+
return pathlib.Path(self.source)
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def source_is_blkdev(self) -> bool:
|
|
20
|
+
return self.source_path.is_block_device()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_mounts(contents: str | None = None) -> list[MountInfo]:
|
|
24
|
+
if contents is None:
|
|
25
|
+
try:
|
|
26
|
+
with open("/proc/self/mounts", "r", encoding="utf-8") as f:
|
|
27
|
+
contents = f.read()
|
|
28
|
+
except OSError:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
mounts: list[MountInfo] = []
|
|
32
|
+
for line in contents.splitlines():
|
|
33
|
+
parts = line.split()
|
|
34
|
+
if len(parts) < 4:
|
|
35
|
+
continue
|
|
36
|
+
source, target, fstype, opts = parts[:4]
|
|
37
|
+
options = opts.split(",")
|
|
38
|
+
source = _unescape_octals(source)
|
|
39
|
+
target = _unescape_octals(target)
|
|
40
|
+
mounts.append(MountInfo(source, target, fstype, options))
|
|
41
|
+
return mounts
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _unescape_octals(s: str) -> str:
|
|
45
|
+
return re.sub(r"\\([0-3][0-7]{2})", lambda m: chr(int(m.group(1), 8)), s)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|