ruyi 0.44.0b20251219__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.
Files changed (53) hide show
  1. ruyi/__main__.py +16 -4
  2. ruyi/cli/cmd.py +6 -5
  3. ruyi/cli/config_cli.py +14 -11
  4. ruyi/cli/main.py +14 -4
  5. ruyi/cli/oobe.py +7 -3
  6. ruyi/cli/self_cli.py +48 -34
  7. ruyi/cli/user_input.py +42 -12
  8. ruyi/cli/version_cli.py +11 -5
  9. ruyi/config/__init__.py +26 -2
  10. ruyi/config/errors.py +19 -7
  11. ruyi/device/provision.py +116 -55
  12. ruyi/device/provision_cli.py +6 -3
  13. ruyi/i18n/__init__.py +129 -0
  14. ruyi/log/__init__.py +6 -5
  15. ruyi/mux/runtime.py +19 -6
  16. ruyi/mux/venv/maker.py +93 -35
  17. ruyi/mux/venv/venv_cli.py +13 -10
  18. ruyi/pluginhost/plugin_cli.py +4 -3
  19. ruyi/resource_bundle/__init__.py +22 -8
  20. ruyi/resource_bundle/__main__.py +6 -5
  21. ruyi/resource_bundle/data.py +13 -9
  22. ruyi/ruyipkg/admin_checksum.py +4 -1
  23. ruyi/ruyipkg/admin_cli.py +9 -6
  24. ruyi/ruyipkg/augmented_pkg.py +15 -14
  25. ruyi/ruyipkg/checksum.py +8 -2
  26. ruyi/ruyipkg/distfile.py +33 -9
  27. ruyi/ruyipkg/entity.py +12 -2
  28. ruyi/ruyipkg/entity_cli.py +20 -12
  29. ruyi/ruyipkg/entity_provider.py +11 -2
  30. ruyi/ruyipkg/fetcher.py +38 -9
  31. ruyi/ruyipkg/install.py +143 -42
  32. ruyi/ruyipkg/install_cli.py +18 -15
  33. ruyi/ruyipkg/list.py +27 -20
  34. ruyi/ruyipkg/list_cli.py +12 -7
  35. ruyi/ruyipkg/news.py +23 -11
  36. ruyi/ruyipkg/news_cli.py +10 -7
  37. ruyi/ruyipkg/profile_cli.py +8 -2
  38. ruyi/ruyipkg/repo.py +22 -8
  39. ruyi/ruyipkg/unpack.py +42 -8
  40. ruyi/ruyipkg/unpack_method.py +5 -1
  41. ruyi/ruyipkg/update_cli.py +8 -3
  42. ruyi/telemetry/provider.py +74 -29
  43. ruyi/telemetry/telemetry_cli.py +9 -8
  44. ruyi/utils/git.py +18 -11
  45. ruyi/utils/prereqs.py +10 -5
  46. ruyi/utils/ssl_patch.py +2 -1
  47. ruyi/version.py +9 -3
  48. {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/METADATA +2 -1
  49. ruyi-0.45.0.dist-info/RECORD +103 -0
  50. {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/WHEEL +1 -1
  51. ruyi-0.44.0b20251219.dist-info/RECORD +0 -102
  52. {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/entry_points.txt +0 -0
  53. {ruyi-0.44.0b20251219.dist-info → ruyi-0.45.0.dist-info}/licenses/LICENSE-Apache.txt +0 -0
@@ -1,20 +1,34 @@
1
1
  import base64
2
2
  import zlib
3
3
 
4
- from .data import RESOURCES, TEMPLATES
4
+ from .data import RESOURCES, TEMPLATE_NAME_MAP
5
5
 
6
6
 
7
- def _unpack_payload(x: bytes) -> str:
8
- return zlib.decompress(base64.b64decode(x)).decode("utf-8")
7
+ def _unpack_payload(x: bytes) -> bytes:
8
+ return zlib.decompress(base64.b64decode(x))
9
9
 
10
10
 
11
- def get_resource_str(template_name: str) -> str | None:
12
- if t := RESOURCES.get(template_name):
13
- return _unpack_payload(t)
11
+ _CACHE: dict[str, bytes] = {}
12
+
13
+
14
+ def get_resource_blob(name: str) -> bytes | None:
15
+ if t := RESOURCES.get(name):
16
+ if name not in _CACHE:
17
+ # In our use cases, the program is short-lived and involved resources
18
+ # are small in size, so it is fine to just store the decompressed
19
+ # blobs without eviction.
20
+ _CACHE[name] = _unpack_payload(t)
21
+ return _CACHE[name]
22
+ return None
23
+
24
+
25
+ def get_resource_str(name: str) -> str | None:
26
+ if blob := get_resource_blob(name):
27
+ return blob.decode("utf-8")
14
28
  return None
15
29
 
16
30
 
17
31
  def get_template_str(template_name: str) -> str | None:
18
- if t := TEMPLATES.get(template_name):
19
- return _unpack_payload(t)
32
+ if t := TEMPLATE_NAME_MAP.get(template_name):
33
+ return get_resource_str(t)
20
34
  return None
@@ -20,15 +20,16 @@ def main() -> None:
20
20
 
21
21
  resources: dict[str, str] = {}
22
22
  template_names: dict[str, str] = {}
23
- for f in bundled_resource_root.iterdir():
23
+ for f in bundled_resource_root.glob("**/*"):
24
24
  if not f.is_file():
25
25
  continue
26
26
 
27
- resources[f.name] = make_payload_from_file(f)
27
+ rel_path = f.relative_to(bundled_resource_root)
28
+ resources[str(rel_path)] = make_payload_from_file(f)
28
29
 
29
30
  if f.suffix.lower() == ".jinja":
30
31
  # strip the .jinja suffix for the template name
31
- template_names[f.stem] = f.name
32
+ template_names[str(rel_path.with_suffix(""))] = str(rel_path)
32
33
 
33
34
  with open(self_path / "data.py", "w", encoding="utf-8") as fp:
34
35
 
@@ -45,9 +46,9 @@ def main() -> None:
45
46
  p(f' "{filename}": b"{payload}", # fmt: skip')
46
47
  p("}\n")
47
48
 
48
- p("TEMPLATES: Final = {")
49
+ p("TEMPLATE_NAME_MAP: Final = {")
49
50
  for stem, full_filename in sorted(template_names.items()):
50
- p(f' "{stem}": RESOURCES["{full_filename}"],')
51
+ p(f' "{stem}": "{full_filename}",')
51
52
  p("}")
52
53
 
53
54
 
@@ -7,20 +7,24 @@ from typing import Final
7
7
  RESOURCES: Final = {
8
8
  "_ruyi_completion": b"eNqlVntP40YQ/z+fYmosCC0h5KpKd7Tmjoc5ogYSxUlPd4FaTryJV9i7kXdNLofoZ++M7TgmmOiutVDYncdvZuexOzsTGc19NoU4WfJabQc6/J6FS/BAxzyKmA++XAh4YLHiUoCcQqD1XB03mzOug2R8iPrNe67CZXLf9OIZwYVMs+Y4lONm5HHxnOqpwM13iHfoN935UgdSNEpS6MUVC+doEgLvgYHPleZiomEesyn/CtNYRqADBjLmMy68ENQk5nMNWoL3ILkPwosYTGQYcvJa0bn6iQAlI6YDLmYHECUIOQOZ6HmiEQhi5vOYTVIqT6HIgs/GyQyUjpkXIYjP5kz4qaJI+Q9emDCKinvaP3cv7LPhx0OUa0/htP/xvHvd69gD2x06tjuwr3uX7Y7tAFegmD6ARDHQLJpPecgUTNGHdu/8sObmEXFLEXEpO/gj6vvwWAP8+BRGI2h8A8N8fNVU48mAu7vfyVORatG3Fd/lQrAYQT8YhULMdBJn+lOe/gvlBIOuM9ctw6xH93SQ/UzHLfvjDC66w4FL7tycXtsonKsZP+ZIZnIifWaZ71PKxNOwRktJcfSCkvoOJinWnmq177BZGeJ1eitjSm7C25PdFrw72X0DrZOmzx6aIglDeEPUP4p9qsJCxV7X3X1XqYSxpwO84n/hdRao9qVjmXu3R61f90rUrE0swyifT9D5vjhX7l9232l3byoPuAr/qnFVwSnRrLpZJ8OGib8G3BYy9FFFuJ32DZXA2fDy0u5XSvS67ZsBipwP+073hUi5tKzWFqbrXNmdjmV8U4GxVWzY6/Vtx3Gd3ul5BeS2cgHzMQvoccN8XMjYV6PW3dPT/v5G1IRUMtZWvYI+9yaszMCUcNXwdCNkntLw2+HbjTzQt8JryHy51s/7c51dTG0pQZRa6x+oj/6+vb3bP4bdXRKIPD0JyPWUObKax3cvCyA3nDnccGBvr9Kq6zOKyZgRcBETo1wlxMn8Hn0gVrYl4HT/skGyaG1k6qh8UEKXeP//Ilc+YgdB0T4VR9nMe9VZqEj6dq/z+bvrulhvL+1iUyk2+NwrsGi9tX5TqU/d/sVZ3z7901nprSn/u4Poxf6RFjKf7/9rQ7XKfZTVsvkefrLgqLo2E4Fv6jplBY+FufKGW5YFLUSiFsjapFBtlPrArDa2qrdGUW+b9ZNe1uUHZNsFuwpECY+WOJV5SZhaoSQU20t4LYjZEFc0zw58QhP4UIYhznHjZTqxfFFBqRtBLRU+3QfIwrFkwfHBwgEHFzrIIYxQer6XaDlNxMSABSFywTVHVBwTkUECiO+hnqGCTCz0NL7eUhyARKPxgit2kAOSE/m8tvDwNmA4QxkNBFAyHYmMfCI1aAaL2QyHP4TCkYycIu0c58UhUvrKofI9WnrtTHwQXLKIQ6jQ7KumQviZfK4YlF6LcjGUPLumVoP09uTkxYF//wJg/FlP", # fmt: skip
9
9
  "binfmt.conf.jinja": b"eNpNUrlu20AQ7f0VAwgCbECk+wQp06Zwl0oakUNyoD2UvQRC1r/7LUkhrkjuzjuHOzqrG2w6Wo0ddd4NOlLMmvhshAYfKE0a6SPPSkVDymxIHN68s+LS4WVH6ijOMYntm5XrSQO05fSTogidLLtNqu1PC3EvidXEFhR/faY4+Wx6CjIqyAJ5J+QHyFcbxvibuhGYznDgpN69xrdD1Wa68QwOvl6DvwbltPqefYYGuIKecwXQO5yEop0QzPAIjXeS1B0I0+qKvwhoqp7YbDjhFIY5zAtVFDOgAV4GTgF9NP8wd6JbgLKE9uW+X3ThYqy+gkRvivTHehBp/wD57yfx/b7MtfB3NTwfHVuhxwMcDemw3qHmBQXcH49QaeJUrRCb6GnigiNfR4v29VaifF8NFUYZ2GIk4EJ2Sa1s+/KhR3qguZtUQNT5EKRLW/JaVhSUlLSLP+Bgi3Y5UKnwp71WsfX4+gabO2pqqAtC/MKz0CcWuiaCqb6i92s8fCFhU7+2Er79gUes69nDf9gX9HfteA==", # fmt: skip
10
+ "locale/zh_CN/LC_MESSAGES/argparse.mo": b"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", # fmt: skip
11
+ "locale/zh_CN/LC_MESSAGES/ruyi.mo": b"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", # fmt: skip
10
12
  "meson-cross.ini.jinja": b"eNptkMFqwzAQRO/6ioUQ0tJazaU9FPoLvfUUglDkNV4iS0IrB4eQf69k2ZRCbmI083Z3NvDDCJbO+Ck2YgMDsnfAmMYATWOiZ246sgi3G1zQXVT0PsH9/jYbq0GSI5BSzoBvxJYXzF6+f8j9ixTicCKnIyEfhYEv2GWaMRmzEyaEVZimWbltG+h8hDNeXyHo1AO5ClS6bSmRd9qqFSgp4cBPz7C9iwzJoQxZiHN4RaJrC7XYtkAd8JXnW7JwOI1kU5PH+FDwecsQsaNp4azWiiqknC+5EH3AmOpdgz6jSt5b02tyam5tuezR17rX/1Xys1b8aHLzN1oces9JDdr05PBYcrmHkrLkxqn0OqpOD2SvaxnRG2TOFdTax4d6GaBd5aRkcSd+ATx1t64=", # fmt: skip
11
- "prompt.venv-created.txt.jinja": b"eNp1kTFvwkAMhff7FV7YStjbqWLqwMZSISSOO4dYJOfIvoRGiP9eXwR0KTedbOv5e8/bBmEkyYNvAZP9OHWYMpBC4gsEQZ8xVs598wCdn8CHTKPVgDIcJ1AeJFA6QTYh3/fCvVBp3+eIE2gQ6k0ylSG3Owli2h8p7VZ7iCQYMsv0Bj5FiPjUN3FKI5+L+EGGiZZ/zUPlroslUA06qTBnWNyc+4RaUJtHbdUL1vRTrPhWGYxtJDUgjHeW/5xX7mt270dPrT+25iTPszW3LV8KTe9z8+4c2LubuV6fILeb2ZrpMEUDLGDbFyHPWIE7VLhQbsDCjtOy8xFhvfFnhMzchsYbbU0tupLQBtUiDcKqc7GCdYPh/MoOzFA1iw2w4oezdaWujwiKhhpEymXNoP5kp02aZQjleFq5Xz21xdY=", # fmt: skip
13
+ "prompt.venv-created.en.txt.jinja": b"eNp1kTFvwkAMhff7FV7YStjbqWLqwMZSISSOO4dYJOfIvoRGiP9eXwR0KTedbOv5e8/bBmEkyYNvAZP9OHWYMpBC4gsEQZ8xVs598wCdn8CHTKPVgDIcJ1AeJFA6QTYh3/fCvVBp3+eIE2gQ6k0ylSG3Owli2h8p7VZ7iCQYMsv0Bj5FiPjUN3FKI5+L+EGGiZZ/zUPlroslUA06qTBnWNyc+4RaUJtHbdUL1vRTrPhWGYxtJDUgjHeW/5xX7mt270dPrT+25iTPszW3LV8KTe9z8+4c2LubuV6fILeb2ZrpMEUDLGDbFyHPWIE7VLhQbsDCjtOy8xFhvfFnhMzchsYbbU0tupLQBtUiDcKqc7GCdYPh/MoOzFA1iw2w4oezdaWujwiKhhpEymXNoP5kp02aZQjleFq5Xz21xdY=", # fmt: skip
14
+ "prompt.venv-created.zh_CN.txt.jinja": b"eNpdkc1O20AUhfd+itmwRDwEa54gyiINk2IR2ZUdoFEUycofTgkmqG0KSapWKEFeFDtARFzbSd6Fzp2xV36FXmNAiFnO3DnnO/cIawaLOzBHEPjg9LjZjy6H/OSXsFy4av4zGpLEGzacuY/GWFcPtCJ9NH6S3EeNUiX/QVZyW3kiRg4svzPvBPpmbDTEsMXXBp8HUWvIx3+SsMeCaXbDbyZv5ZNwFBk96FuoH82a4ptNclVaLqtHee2gKm/u0kKxIh8WKhRtJFQR1wGEBjhPYLWNTSKXiF7VNVWtkI26JMHYfmfBvJsUwL9mno9B4qYtlnfM7zDPgOkPPpgh7YvE1ieNluTPBFXQC/NECxdWrSQcShLB85y6Vnv1rNdTsBSEKrvIkjK8A4jWF9Bpi6WDpsgunFs4vcekqe/2TmGfElhMob2Iv67i9inO8cExCx6S0MxeK6paLu4VZIWU5DJNwi7zLLJDdVWRmD+Bs64IB5F7/vrr6YkUNVXXX37gsrIOMdhbtLSo33+z9nh3BeYMN8sCbGyUyaUD93Z8/AW3yMddtlwjeOTO+YWVFvAf3zNT3Q==", # fmt: skip
12
15
  "ruyi-activate.bash.jinja": b"eNp9lN1u2kAQhe/3KSYGqiSVRUnuqIhEGqRESgLCBClKI2uxx/JKZh2t126o6yfqI/QuT9bxH2CHhBvEzpn1d2YO7sDCFxF4IkBYx5GGFUIcoQu/hPbBiMJYOQgrIfsq3giTO1okXKMBp54K17DikX/KOrAJY3C4lKEGFUsQGlyh0NHBhjEmPHgCo5tejq1r25o+zH9MzMyAEZ19M+D5O2gfJQP6oOOHYDzSZQVL9XSdE0aOEi96CD+79XHefPHlrGx8pUeenzNPMFaAulijwvEJpIWoAwoj1BAGLqBMhArlGqWGhCvBVwFGleqIcM3f0E2X4/lXO4Nn6tOxkhFoFSOQHSoAMbnoBFzRsLgGHgRFO1WrfnJszx8eb+zp7ZU9Gy+uh3QZ+YU9w/knL42MblNrbMv4+hIqXai2Z7HMfTQ7imLuvzQhZMID4eYDoKcV7bQhx8ei7tPewFRwdtF3MenLmOg/xbcGH8Bbgya7NXiPbg0+Jq9qNXhZL8rLyf3y0Jk9m0/vZosa94mAiXVQRUqG0sWIFpWvH1vEHbAw8KAWHLWwTA9a4anZMkYhL0VCKQww4Y3gtDPXpGBsCz9K050T+AO0hixj1aB2tllztzTiMhTVj62wT//MYVXbDwr7LAAHl5/28mHuhnw/vpuA2ctYe+4NB6WstpH2TMAgwsN9RveYXhco+Rphz4JxYpSN0iWA3vth1Psu3yNlMnfFqxtrfHk7qURmO6ONpBFCSl8kYtvsvv2d0+5g/8ryquztHzTke2mmRPwHax2J0Q==", # fmt: skip
13
16
  "ruyi-cache.toml.jinja": b"eNptUs1uhCAYvPsUX2nMtknjoccmve0e9tK9eDOGsIJK1r8CmhjjuxcUF6uexG+GmWHgFX5u4eULwpxLSHnBQH9LUpGMUbj3INqeB3C+GRpcztfwxfOihCQ5o7j7jL3BB57CLytbfOcV+ONz+Q1oGBwyjkhzWUU13R/tvkbUxhMbEqs6AzjxYINOZmkt4PEBHWjJDR5wxUr59q5VtPFDO9oMnTH3ZnezX+P/k6xFFREZU3Iltkq0gFY/9lRdF0lOeGXOSLlYHKPTFjnFUwo3TguSySP+BFj6XFMXIQfLXoq6VijW0XbTIz0LzYr7O9DiWZJgXklFigLrqJP0ZuaEN8Ch7LPobbtJSXHJFKFEEVyS5rjmHQvZwlHsUSZXpzR/S7PT3WDVNvoRuxpWU9epy/cHatoHPQ==", # fmt: skip
14
17
  "ruyi-venv.toml.jinja": b"eNqtkOFqwyAQx7/7FBIIbWEN7AH2JEHCYUwqbTxRN1ZC3n0atQlpSxnbN/V+3v3uX3NUnewZ0QY7eRH0gxbjSPNtmgoyllR21F6tQXT0WE4knyObb5E9UqFaz3uM1INw0IIDRr6EsRKV//JOQscBuEGqz32Tmb0+zM2N0NjINovUu/SwY/MEDk70aK5LPb8kQMEglmK4pcKikGrpIZWTehQL9nHvrFcXac/GSxcsELf1Kg38DL2wVWIYCRPWyz1pc/Cjw6Als+1UMXxewKEJvI1zPdKhoQ5ML1wDhp/eqKZSPf9VSScGu48BP9DOeBWSWfUNwdzvolfWQaR8tcK3M3Dvv1VeU7PnI9EAsb8rOcQLP4FUv4t1++1Vrjf+X4L9AYyLQa0=", # fmt: skip
15
18
  "toolchain.cmake.jinja": b"eNqFkVFrwjAUhd/7Ky6IMGF274M9dG2cZWlT0joUhJDVaIuajKQTR+l/X6zOWccwb+F+5+Scmx5MjIBNuRaPTs/pQb7lawGu68Lc3g5nGPiR94pYRgj2x14Ys1GI0VNdw07IHdNKVdA0D5VSm7zgpXSPFnOn7g+hXIL5Mi3Tb64MwzjNPIxZQtEonB4Mf9CmOcmFXFiHs9LGchwjqrujQTpLMxSx2IsQ4FJ+7gd/hwklPkpTQsH6f2iVC2OUti9csj7zSZTYVi2V59fj6bQL7PctUfcv+w1tzF/NKIwDRgnJWOJlY+iUa5UX1YzgOi9gaXMVgi+ENsDlwn7Ku+a6FAZKCVUhoOJ6JSqr3JVaya2Q1X1XbfutNN+eBYUyHfzffCwiATrs6oV6EcToDdHBDRiHz9SjMyAxnt1iw9jHkwCd2G+4PsQk", # fmt: skip
16
19
  }
17
20
 
18
- TEMPLATES: Final = {
19
- "binfmt.conf": RESOURCES["binfmt.conf.jinja"],
20
- "meson-cross.ini": RESOURCES["meson-cross.ini.jinja"],
21
- "prompt.venv-created.txt": RESOURCES["prompt.venv-created.txt.jinja"],
22
- "ruyi-activate.bash": RESOURCES["ruyi-activate.bash.jinja"],
23
- "ruyi-cache.toml": RESOURCES["ruyi-cache.toml.jinja"],
24
- "ruyi-venv.toml": RESOURCES["ruyi-venv.toml.jinja"],
25
- "toolchain.cmake": RESOURCES["toolchain.cmake.jinja"],
21
+ TEMPLATE_NAME_MAP: Final = {
22
+ "binfmt.conf": "binfmt.conf.jinja",
23
+ "meson-cross.ini": "meson-cross.ini.jinja",
24
+ "prompt.venv-created.en.txt": "prompt.venv-created.en.txt.jinja",
25
+ "prompt.venv-created.zh_CN.txt": "prompt.venv-created.zh_CN.txt.jinja",
26
+ "ruyi-activate.bash": "ruyi-activate.bash.jinja",
27
+ "ruyi-cache.toml": "ruyi-cache.toml.jinja",
28
+ "ruyi-venv.toml": "ruyi-venv.toml.jinja",
29
+ "toolchain.cmake": "toolchain.cmake.jinja",
26
30
  }
@@ -6,6 +6,7 @@ from tomlkit import document, table
6
6
  from tomlkit.items import AoT, Table
7
7
  from tomlkit.toml_document import TOMLDocument
8
8
 
9
+ from ..i18n import _
9
10
  from ..log import RuyiLogger
10
11
  from . import checksum
11
12
  from .pkg_manifest import DistfileDeclType, RestrictKind
@@ -18,7 +19,9 @@ def do_admin_checksum(
18
19
  restrict: list[str],
19
20
  ) -> int:
20
21
  if not validate_restrict_kinds(restrict):
21
- logger.F(f"invalid restrict kinds given: {restrict}")
22
+ logger.F(
23
+ _("invalid restrict kinds given: {restrict}").format(restrict=restrict)
24
+ )
22
25
  return 1
23
26
 
24
27
  entries = [gen_distfile_entry(logger, f, restrict) for f in files]
ruyi/ruyipkg/admin_cli.py CHANGED
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import TYPE_CHECKING, cast
4
4
 
5
5
  from ..cli.cmd import AdminCommand
6
+ from ..i18n import _
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from ..cli.completion import ArgumentParser
@@ -12,7 +13,7 @@ if TYPE_CHECKING:
12
13
  class AdminChecksumCommand(
13
14
  AdminCommand,
14
15
  cmd="checksum",
15
- help="Generate a checksum section for a manifest file for the distfiles given",
16
+ help=_("Generate a checksum section for a manifest file for the distfiles given"),
16
17
  ):
17
18
  @classmethod
18
19
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -22,19 +23,21 @@ class AdminChecksumCommand(
22
23
  type=str,
23
24
  choices=["toml"],
24
25
  default="toml",
25
- help="Format of checksum section to generate in",
26
+ help=_("Format of checksum section to generate in"),
26
27
  )
27
28
  p.add_argument(
28
29
  "--restrict",
29
30
  type=str,
30
31
  default="",
31
- help="the 'restrict' field to use for all mentioned distfiles, separated with comma",
32
+ help=_(
33
+ "the 'restrict' field to use for all mentioned distfiles, separated with comma"
34
+ ),
32
35
  )
33
36
  p.add_argument(
34
37
  "file",
35
38
  type=str,
36
39
  nargs="+",
37
- help="Path to the distfile(s) to checksum",
40
+ help=_("Path to the distfile(s) to checksum"),
38
41
  )
39
42
 
40
43
  @classmethod
@@ -53,7 +56,7 @@ class AdminChecksumCommand(
53
56
  class AdminFormatManifestCommand(
54
57
  AdminCommand,
55
58
  cmd="format-manifest",
56
- help="Format the given package manifests into canonical TOML representation",
59
+ help=_("Format the given package manifests into canonical TOML representation"),
57
60
  ):
58
61
  @classmethod
59
62
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -61,7 +64,7 @@ class AdminFormatManifestCommand(
61
64
  "file",
62
65
  type=str,
63
66
  nargs="+",
64
- help="Path to the distfile(s) to generate manifest for",
67
+ help=_("Path to the distfile(s) to generate manifest for"),
65
68
  )
66
69
 
67
70
  @classmethod
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
6
6
  from typing_extensions import Self
7
7
 
8
8
  from ..config import GlobalConfig
9
+ from ..i18n import _
9
10
  from ..utils.porcelain import PorcelainEntity, PorcelainEntityType
10
11
  from .distfile import Distfile
11
12
  from .host import get_native_host
@@ -28,19 +29,19 @@ if sys.version_info >= (3, 11):
28
29
  def as_rich_markup(self) -> str:
29
30
  match self:
30
31
  case self.Latest:
31
- return "latest"
32
+ return _("latest")
32
33
  case self.LatestPreRelease:
33
- return "latest-prerelease"
34
+ return _("latest-prerelease")
34
35
  case self.NoBinaryForCurrentHost:
35
- return "[red]no binary for current host[/]"
36
+ return _("[red]no binary for current host[/]")
36
37
  case self.PreRelease:
37
- return "prerelease"
38
+ return _("prerelease")
38
39
  case self.HasKnownIssue:
39
- return "[yellow]has known issue[/]"
40
+ return _("[yellow]has known issue[/]")
40
41
  case self.Downloaded:
41
- return "[green]downloaded[/]"
42
+ return _("[green]downloaded[/]")
42
43
  case self.Installed:
43
- return "[green]installed[/]"
44
+ return _("[green]installed[/]")
44
45
  return ""
45
46
 
46
47
  else:
@@ -57,19 +58,19 @@ else:
57
58
  def as_rich_markup(self) -> str:
58
59
  match self:
59
60
  case self.Latest:
60
- return "latest"
61
+ return _("latest")
61
62
  case self.LatestPreRelease:
62
- return "latest-prerelease"
63
+ return _("latest-prerelease")
63
64
  case self.NoBinaryForCurrentHost:
64
- return "[red]no binary for current host[/]"
65
+ return _("[red]no binary for current host[/]")
65
66
  case self.PreRelease:
66
- return "prerelease"
67
+ return _("prerelease")
67
68
  case self.HasKnownIssue:
68
- return "[yellow]has known issue[/]"
69
+ return _("[yellow]has known issue[/]")
69
70
  case self.Downloaded:
70
- return "[green]downloaded[/]"
71
+ return _("[green]downloaded[/]")
71
72
  case self.Installed:
72
- return "[green]installed[/]"
73
+ return _("[green]installed[/]")
73
74
  return ""
74
75
 
75
76
 
ruyi/ruyipkg/checksum.py CHANGED
@@ -1,12 +1,14 @@
1
1
  import hashlib
2
2
  from typing import BinaryIO, Final, Iterable
3
3
 
4
+ from ..i18n import _
5
+
4
6
  SUPPORTED_CHECKSUM_KINDS: Final = {"sha256", "sha512"}
5
7
 
6
8
 
7
9
  def get_hash_instance(kind: str) -> "hashlib._Hash":
8
10
  if kind not in SUPPORTED_CHECKSUM_KINDS:
9
- raise ValueError(f"checksum algorithm {kind} not supported")
11
+ raise ValueError(_("checksum algorithm {kind} not supported").format(kind=kind))
10
12
  return hashlib.new(kind)
11
13
 
12
14
 
@@ -20,7 +22,11 @@ class Checksummer:
20
22
  for kind, expected_csum in self.checksums.items():
21
23
  if computed_csums[kind] != expected_csum:
22
24
  raise ValueError(
23
- f"wrong {kind} checksum: want {expected_csum}, got {computed_csums[kind]}"
25
+ _("wrong {kind} checksum: want {want}, got {got}").format(
26
+ kind=kind,
27
+ want=expected_csum,
28
+ got=computed_csums[kind],
29
+ )
24
30
  )
25
31
 
26
32
  def compute(
ruyi/ruyipkg/distfile.py CHANGED
@@ -2,6 +2,7 @@ from functools import cached_property
2
2
  import os
3
3
  from typing import Any, Final
4
4
 
5
+ from ..i18n import _, d_
5
6
  from ..log import RuyiLogger
6
7
  from .checksum import Checksummer
7
8
  from .fetcher import BaseFetcher
@@ -12,7 +13,8 @@ from .unpack_method import UnpackMethod
12
13
 
13
14
 
14
15
  # https://github.com/ruyisdk/ruyi/issues/46
15
- HELP_ERROR_FETCHING: Final = """
16
+ HELP_ERROR_FETCHING: Final = d_(
17
+ """
16
18
  Downloads can fail for a multitude of reasons, most of which should not and
17
19
  cannot be handled by [yellow]Ruyi[/]. For your convenience though, please check if any
18
20
  of the following common failure modes apply to you, and take actions
@@ -28,6 +30,7 @@ accordingly if one of them turns out to be the case:
28
30
  * Volatile upstream
29
31
  - is the recorded [yellow]link dead[/]? (Please raise a Ruyi issue for a fix!)
30
32
  """
33
+ )
31
34
 
32
35
 
33
36
  class Distfile:
@@ -86,9 +89,13 @@ class Distfile:
86
89
  # to reduce surprises for packagers.
87
90
  if k in fr["params"]:
88
91
  logger.F(
89
- f"malformed package fetch instructions: the param named '{k}' is reserved and cannot be overridden by packages"
92
+ _(
93
+ "malformed package fetch instructions: the param named '{param}' is reserved and cannot be overridden by packages"
94
+ ).format(
95
+ param=k,
96
+ )
90
97
  )
91
- raise RuntimeError("malformed package fetch instructions")
98
+ raise RuntimeError(_("malformed package fetch instructions"))
92
99
 
93
100
  params.update(fr["params"])
94
101
 
@@ -128,7 +135,13 @@ class Distfile:
128
135
  return self.fetch_and_ensure_integrity(logger)
129
136
 
130
137
  logger.W(
131
- f"file {self.dest} is corrupt: size too big ({st.st_size} > {self.size}); deleting"
138
+ _(
139
+ "file {file} is corrupt: size too big ({actual_size} > {expected_size}); deleting"
140
+ ).format(
141
+ file=self.dest,
142
+ actual_size=st.st_size,
143
+ expected_size=self.size,
144
+ )
132
145
  )
133
146
  os.remove(self.dest)
134
147
  return self.fetch_and_ensure_integrity(logger)
@@ -140,7 +153,12 @@ class Distfile:
140
153
  cs.check()
141
154
  return True
142
155
  except ValueError as e:
143
- logger.W(f"file {self.dest} is corrupt: {e}; deleting")
156
+ logger.W(
157
+ _("file {file} is corrupt: {reason}; deleting").format(
158
+ file=self.dest,
159
+ reason=e,
160
+ )
161
+ )
144
162
  os.remove(self.dest)
145
163
  return False
146
164
 
@@ -158,9 +176,13 @@ class Distfile:
158
176
  # TODO: allow rendering instructions for all missing fetch-restricted
159
177
  # files at once
160
178
  logger.F(
161
- f"the file [yellow]'{self.dest}'[/] cannot be automatically fetched"
179
+ _(
180
+ "the file [yellow]'{file}'[/] cannot be automatically fetched"
181
+ ).format(
182
+ file=self.dest,
183
+ )
162
184
  )
163
- logger.I("instructions on fetching this file:")
185
+ logger.I(_("instructions on fetching this file:"))
164
186
  logger.I(
165
187
  self.render_fetch_instructions(logger, self._mr.global_config.lang_code)
166
188
  )
@@ -170,7 +192,7 @@ class Distfile:
170
192
  return self._fetch_and_ensure_integrity(logger, resume=resume)
171
193
  except RuntimeError as e:
172
194
  logger.F(f"{e}")
173
- logger.stdout(HELP_ERROR_FETCHING)
195
+ logger.stdout(_(HELP_ERROR_FETCHING))
174
196
  raise SystemExit(1)
175
197
 
176
198
  def _fetch_and_ensure_integrity(
@@ -184,7 +206,9 @@ class Distfile:
184
206
 
185
207
  if not self.ensure_integrity_or_rm(logger):
186
208
  raise RuntimeError(
187
- f"failed to fetch distfile: {self.dest} failed integrity checks"
209
+ _("failed to fetch distfile: {file} failed integrity checks").format(
210
+ file=self.dest,
211
+ )
188
212
  )
189
213
 
190
214
  def unpack(
ruyi/ruyipkg/entity.py CHANGED
@@ -3,6 +3,7 @@ from typing import Any, Callable, Iterable, Iterator, Mapping
3
3
  import fastjsonschema
4
4
  from fastjsonschema.exceptions import JsonSchemaException
5
5
 
6
+ from ..i18n import _
6
7
  from ..log import RuyiLogger
7
8
  from .entity_provider import BaseEntity, BaseEntityProvider, EntityValidationError
8
9
 
@@ -63,7 +64,11 @@ class EntityStore:
63
64
 
64
65
  schema = self._schemas.get(entity_type)
65
66
  if not schema:
66
- self._logger.W(f"no schema found for entity type: {entity_type}")
67
+ self._logger.W(
68
+ _("no schema found for entity type: {entity_type}").format(
69
+ entity_type=entity_type,
70
+ )
71
+ )
67
72
  # Return a simple validator that accepts anything
68
73
  return lambda x: x
69
74
 
@@ -72,7 +77,12 @@ class EntityStore:
72
77
  self._validators[entity_type] = validator
73
78
  return validator
74
79
  except Exception as e:
75
- self._logger.W(f"failed to compile schema for {entity_type}: {e}")
80
+ self._logger.W(
81
+ _("failed to compile schema for {entity_type}: {reason}").format(
82
+ entity_type=entity_type,
83
+ reason=e,
84
+ )
85
+ )
76
86
  # Return a simple validator that accepts anything
77
87
  return lambda x: x
78
88
 
@@ -2,6 +2,7 @@ import argparse
2
2
  from typing import TYPE_CHECKING
3
3
 
4
4
  from ..cli.cmd import RootCommand
5
+ from ..i18n import _
5
6
 
6
7
  if TYPE_CHECKING:
7
8
  from ..cli.completion import ArgumentParser
@@ -13,7 +14,7 @@ class EntityCommand(
13
14
  cmd="entity",
14
15
  has_subcommands=True,
15
16
  is_experimental=True,
16
- help="Interact with entities defined in the repositories",
17
+ help=_("Interact with entities defined in the repositories"),
17
18
  ):
18
19
  @classmethod
19
20
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -23,13 +24,15 @@ class EntityCommand(
23
24
  class EntityDescribeCommand(
24
25
  EntityCommand,
25
26
  cmd="describe",
26
- help="Describe an entity",
27
+ help=_("Describe an entity"),
27
28
  ):
28
29
  @classmethod
29
30
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
30
31
  p.add_argument(
31
32
  "ref",
32
- help="Reference to the entity to describe in the form of '<type>:<name>'",
33
+ help=_(
34
+ "Reference to the entity to describe in the form of '<type>:<name>'"
35
+ ),
33
36
  )
34
37
 
35
38
  @classmethod
@@ -40,30 +43,33 @@ class EntityDescribeCommand(
40
43
  entity_store = cfg.repo.entity_store
41
44
  entity = entity_store.get_entity_by_ref(ref)
42
45
  if entity is None:
43
- logger.F(f"entity [yellow]{ref}[/] not found")
46
+ logger.F(_("entity [yellow]{ref}[/] not found").format(ref=ref))
44
47
  return 1
45
48
 
46
49
  logger.stdout(
47
- f"Entity [bold]{str(entity)}[/] ([green]{entity.display_name}[/])\n"
50
+ _("Entity [bold]{entity}[/] ([green]{display_name}[/])\n").format(
51
+ entity=str(entity),
52
+ display_name=entity.display_name,
53
+ )
48
54
  )
49
55
 
50
56
  fwd_refs = entity.related_refs
51
57
  if fwd_refs:
52
- logger.stdout(" Direct forward relationships:")
58
+ logger.stdout(_(" Direct forward relationships:"))
53
59
  for ref in sorted(fwd_refs):
54
60
  logger.stdout(f" - [yellow]{ref}[/]")
55
61
  else:
56
- logger.stdout(" Direct forward relationships: [gray]none[/]")
62
+ logger.stdout(_(" Direct forward relationships: [gray]none[/]"))
57
63
 
58
64
  rev_refs = entity.reverse_refs
59
65
  if rev_refs:
60
- logger.stdout(" Direct reverse relationships:")
66
+ logger.stdout(_(" Direct reverse relationships:"))
61
67
  for ref in sorted(rev_refs):
62
68
  logger.stdout(f" - [yellow]{ref}[/]")
63
69
  else:
64
- logger.stdout(" Direct reverse relationships: [gray]none[/]")
70
+ logger.stdout(_(" Direct reverse relationships: [gray]none[/]"))
65
71
 
66
- logger.stdout(" All indirectly related entities:")
72
+ logger.stdout(_(" All indirectly related entities:"))
67
73
  for e in entity_store.traverse_related_entities(
68
74
  entity,
69
75
  transitive=True,
@@ -81,7 +87,7 @@ class EntityDescribeCommand(
81
87
  class EntityListCommand(
82
88
  EntityCommand,
83
89
  cmd="list",
84
- help="List entities",
90
+ help=_("List entities"),
85
91
  ):
86
92
  @classmethod
87
93
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -91,7 +97,9 @@ class EntityListCommand(
91
97
  action="append",
92
98
  nargs=1,
93
99
  dest="entity_type",
94
- help="List entities of this type. Can be passed multiple times to list multiple types.",
100
+ help=_(
101
+ "List entities of this type. Can be passed multiple times to list multiple types."
102
+ ),
95
103
  )
96
104
 
97
105
  @classmethod
@@ -10,6 +10,7 @@ if sys.version_info >= (3, 11):
10
10
  else:
11
11
  import tomli as tomllib
12
12
 
13
+ from ..i18n import _
13
14
  from ..log import RuyiLogger
14
15
  from ..utils.porcelain import PorcelainEntity, PorcelainEntityType
15
16
 
@@ -208,7 +209,10 @@ class FSEntityProvider(BaseEntityProvider):
208
209
  schema_files = list(self._schemas_root.glob("*.jsonschema"))
209
210
  except IOError as e:
210
211
  self._logger.W(
211
- f"failed to access entity schemas directory {self._schemas_root}: {e}"
212
+ _("failed to access entity schemas directory {dir}: {reason}").format(
213
+ dir=self._schemas_root,
214
+ reason=e,
215
+ )
212
216
  )
213
217
  return schemas
214
218
 
@@ -259,7 +263,12 @@ class FSEntityProvider(BaseEntityProvider):
259
263
  with open(file_path, "rb") as f:
260
264
  data = tomllib.load(f)
261
265
  except (IOError, tomllib.TOMLDecodeError) as e:
262
- self._logger.W(f"failed to load entity from {file_path}: {e}")
266
+ self._logger.W(
267
+ _("failed to load entity from {path}: {reason}").format(
268
+ path=file_path,
269
+ reason=e,
270
+ )
271
+ )
263
272
  continue
264
273
 
265
274
  # Extract entity ID from filename (remove .toml extension)