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.
Files changed (101) hide show
  1. ruyi/__init__.py +21 -0
  2. ruyi/__main__.py +98 -0
  3. ruyi/cli/__init__.py +5 -0
  4. ruyi/cli/builtin_commands.py +14 -0
  5. ruyi/cli/cmd.py +224 -0
  6. ruyi/cli/completer.py +50 -0
  7. ruyi/cli/completion.py +26 -0
  8. ruyi/cli/config_cli.py +153 -0
  9. ruyi/cli/main.py +111 -0
  10. ruyi/cli/self_cli.py +295 -0
  11. ruyi/cli/user_input.py +127 -0
  12. ruyi/cli/version_cli.py +45 -0
  13. ruyi/config/__init__.py +401 -0
  14. ruyi/config/editor.py +92 -0
  15. ruyi/config/errors.py +76 -0
  16. ruyi/config/news.py +39 -0
  17. ruyi/config/schema.py +197 -0
  18. ruyi/device/__init__.py +0 -0
  19. ruyi/device/provision.py +591 -0
  20. ruyi/device/provision_cli.py +40 -0
  21. ruyi/log/__init__.py +272 -0
  22. ruyi/mux/.gitignore +1 -0
  23. ruyi/mux/__init__.py +0 -0
  24. ruyi/mux/runtime.py +213 -0
  25. ruyi/mux/venv/__init__.py +12 -0
  26. ruyi/mux/venv/emulator_cfg.py +41 -0
  27. ruyi/mux/venv/maker.py +782 -0
  28. ruyi/mux/venv/venv_cli.py +92 -0
  29. ruyi/mux/venv_cfg.py +214 -0
  30. ruyi/pluginhost/__init__.py +0 -0
  31. ruyi/pluginhost/api.py +206 -0
  32. ruyi/pluginhost/ctx.py +222 -0
  33. ruyi/pluginhost/paths.py +135 -0
  34. ruyi/pluginhost/plugin_cli.py +37 -0
  35. ruyi/pluginhost/unsandboxed.py +246 -0
  36. ruyi/py.typed +0 -0
  37. ruyi/resource_bundle/__init__.py +20 -0
  38. ruyi/resource_bundle/__main__.py +55 -0
  39. ruyi/resource_bundle/data.py +26 -0
  40. ruyi/ruyipkg/__init__.py +0 -0
  41. ruyi/ruyipkg/admin_checksum.py +88 -0
  42. ruyi/ruyipkg/admin_cli.py +83 -0
  43. ruyi/ruyipkg/atom.py +184 -0
  44. ruyi/ruyipkg/augmented_pkg.py +212 -0
  45. ruyi/ruyipkg/canonical_dump.py +320 -0
  46. ruyi/ruyipkg/checksum.py +39 -0
  47. ruyi/ruyipkg/cli_completion.py +42 -0
  48. ruyi/ruyipkg/distfile.py +208 -0
  49. ruyi/ruyipkg/entity.py +387 -0
  50. ruyi/ruyipkg/entity_cli.py +123 -0
  51. ruyi/ruyipkg/entity_provider.py +273 -0
  52. ruyi/ruyipkg/fetch.py +271 -0
  53. ruyi/ruyipkg/host.py +55 -0
  54. ruyi/ruyipkg/install.py +554 -0
  55. ruyi/ruyipkg/install_cli.py +150 -0
  56. ruyi/ruyipkg/list.py +126 -0
  57. ruyi/ruyipkg/list_cli.py +79 -0
  58. ruyi/ruyipkg/list_filter.py +173 -0
  59. ruyi/ruyipkg/msg.py +99 -0
  60. ruyi/ruyipkg/news.py +123 -0
  61. ruyi/ruyipkg/news_cli.py +78 -0
  62. ruyi/ruyipkg/news_store.py +183 -0
  63. ruyi/ruyipkg/pkg_manifest.py +657 -0
  64. ruyi/ruyipkg/profile.py +208 -0
  65. ruyi/ruyipkg/profile_cli.py +33 -0
  66. ruyi/ruyipkg/protocols.py +55 -0
  67. ruyi/ruyipkg/repo.py +763 -0
  68. ruyi/ruyipkg/state.py +345 -0
  69. ruyi/ruyipkg/unpack.py +369 -0
  70. ruyi/ruyipkg/unpack_method.py +91 -0
  71. ruyi/ruyipkg/update_cli.py +54 -0
  72. ruyi/telemetry/__init__.py +0 -0
  73. ruyi/telemetry/aggregate.py +72 -0
  74. ruyi/telemetry/event.py +41 -0
  75. ruyi/telemetry/node_info.py +192 -0
  76. ruyi/telemetry/provider.py +411 -0
  77. ruyi/telemetry/scope.py +43 -0
  78. ruyi/telemetry/store.py +238 -0
  79. ruyi/telemetry/telemetry_cli.py +127 -0
  80. ruyi/utils/__init__.py +0 -0
  81. ruyi/utils/ar.py +74 -0
  82. ruyi/utils/ci.py +63 -0
  83. ruyi/utils/frontmatter.py +38 -0
  84. ruyi/utils/git.py +169 -0
  85. ruyi/utils/global_mode.py +204 -0
  86. ruyi/utils/l10n.py +83 -0
  87. ruyi/utils/markdown.py +73 -0
  88. ruyi/utils/nuitka.py +33 -0
  89. ruyi/utils/porcelain.py +51 -0
  90. ruyi/utils/prereqs.py +77 -0
  91. ruyi/utils/ssl_patch.py +170 -0
  92. ruyi/utils/templating.py +34 -0
  93. ruyi/utils/toml.py +115 -0
  94. ruyi/utils/url.py +7 -0
  95. ruyi/utils/xdg_basedir.py +80 -0
  96. ruyi/version.py +67 -0
  97. ruyi-0.39.0.dist-info/LICENSE-Apache.txt +201 -0
  98. ruyi-0.39.0.dist-info/METADATA +403 -0
  99. ruyi-0.39.0.dist-info/RECORD +101 -0
  100. ruyi-0.39.0.dist-info/WHEEL +4 -0
  101. ruyi-0.39.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,183 @@
1
+ import functools
2
+ import re
3
+ from typing import Any, Final, TypedDict
4
+
5
+ from ..config.news import NewsReadStatusStore
6
+ from ..utils import frontmatter
7
+ from ..utils.porcelain import PorcelainEntity, PorcelainEntityType
8
+ from ..utils.l10n import match_lang_code
9
+
10
+ NEWS_FILENAME_RE: Final = re.compile(r"^(\d+-\d{2}-\d{2}-.*?)(\.[0-9A-Za-z_-]+)?\.md$")
11
+
12
+
13
+ @functools.total_ordering
14
+ class NewsItemNameMetadata:
15
+ def __init__(self, id: str, lang: str) -> None:
16
+ self.id = id
17
+ self.lang = lang
18
+
19
+ def __eq__(self, other: Any) -> bool:
20
+ if not isinstance(other, NewsItemNameMetadata):
21
+ return NotImplemented
22
+ return self.id == other.id
23
+
24
+ def __lt__(self, other: Any) -> bool:
25
+ if not isinstance(other, NewsItemNameMetadata):
26
+ return NotImplemented
27
+
28
+ # order by id in lexical order
29
+ return self.id < other.id
30
+
31
+
32
+ def parse_news_filename(filename: str) -> NewsItemNameMetadata | None:
33
+ m = NEWS_FILENAME_RE.match(filename)
34
+ if m is None:
35
+ return None
36
+
37
+ id = m.group(1)
38
+ lang = m.group(2)
39
+ if not lang:
40
+ lang = "zh_CN" # TODO: kill after l10n work is complete
41
+ else:
42
+ lang = lang[1:] # strip the dot prefix
43
+
44
+ return NewsItemNameMetadata(id, lang)
45
+
46
+
47
+ class NewsItemStore:
48
+ def __init__(self, rs_store: NewsReadStatusStore) -> None:
49
+ self._buf_news_by_ids: dict[str, NewsItem] = {}
50
+ self._newsitems: list[NewsItem]
51
+ self._rs_store = rs_store
52
+
53
+ def add(self, filename: str, contents: str) -> None:
54
+ md = parse_news_filename(filename)
55
+ if md is None:
56
+ return None
57
+
58
+ post = frontmatter.loads(contents)
59
+
60
+ if ni := self._buf_news_by_ids.get(md.id):
61
+ ni.add_lang(md, post)
62
+ else:
63
+ ni = NewsItem(md.id)
64
+ ni.add_lang(md, post)
65
+ self._buf_news_by_ids[md.id] = ni
66
+
67
+ def finalize(self) -> None:
68
+ self._newsitems = list(self._buf_news_by_ids.values())
69
+ # sort in intended display order
70
+ self._newsitems.sort()
71
+
72
+ # mark the news item instances with ordinals
73
+ for i, ni in enumerate(self._newsitems):
74
+ ni.ordinal = i + 1
75
+
76
+ # also read statuses
77
+ for ni in self._newsitems:
78
+ ni.is_read = ni.id in self._rs_store
79
+
80
+ def list(self, only_unread: bool) -> list["NewsItem"]:
81
+ if not only_unread:
82
+ return self._newsitems
83
+ return [x for x in self._newsitems if not x.is_read]
84
+
85
+ def mark_as_read(self, *ids: str) -> None:
86
+ if not ids:
87
+ return
88
+ for id in ids:
89
+ self._rs_store.add(id)
90
+ self._rs_store.save()
91
+
92
+
93
+ @functools.total_ordering
94
+ class NewsItem:
95
+ def __init__(self, id: str) -> None:
96
+ self._id = id
97
+ self._content_by_lang: dict[str, NewsItemContent] = {}
98
+
99
+ # these fields are updated later in store initialization code
100
+ self.ordinal = 0
101
+ self.is_read = False
102
+
103
+ def __eq__(self, other: Any) -> bool:
104
+ if not isinstance(other, NewsItem):
105
+ return NotImplemented
106
+ return self._id == other._id and self.ordinal == other.ordinal
107
+
108
+ def __lt__(self, other: Any) -> bool:
109
+ if not isinstance(other, NewsItem):
110
+ return NotImplemented
111
+ return self._id < other._id or self.ordinal < other.ordinal
112
+
113
+ @property
114
+ def id(self) -> str:
115
+ return self._id
116
+
117
+ def __contains__(self, lang: str) -> bool:
118
+ return lang in self._content_by_lang
119
+
120
+ def __getitem__(self, lang: str) -> "NewsItemContent":
121
+ return self._content_by_lang[lang]
122
+
123
+ def add_lang(self, md: NewsItemNameMetadata, post: frontmatter.Post) -> None:
124
+ self._content_by_lang[md.lang] = NewsItemContent(md, post)
125
+
126
+ def __delitem__(self, lang: str) -> None:
127
+ del self._content_by_lang[lang]
128
+
129
+ def get_content_for_lang(self, lang: str) -> "NewsItemContent":
130
+ resolved_lang_code = match_lang_code(lang, self._content_by_lang.keys())
131
+ return self[resolved_lang_code]
132
+
133
+ def to_porcelain(self) -> "PorcelainNewsItemV1":
134
+ return {
135
+ "ty": PorcelainEntityType.NewsItemV1,
136
+ "id": self.id,
137
+ "ord": self.ordinal,
138
+ "is_read": self.is_read,
139
+ "langs": [x.to_porcelain() for x in self._content_by_lang.values()],
140
+ }
141
+
142
+
143
+ class NewsItemContent:
144
+ def __init__(
145
+ self,
146
+ md: NewsItemNameMetadata,
147
+ post: frontmatter.Post,
148
+ ) -> None:
149
+ self._md = md
150
+ self._post = post
151
+
152
+ @property
153
+ def lang(self) -> str:
154
+ return self._md.lang
155
+
156
+ @property
157
+ def display_title(self) -> str:
158
+ metadata_title = self._post.get("title")
159
+ return metadata_title if isinstance(metadata_title, str) else self._md.id
160
+
161
+ @property
162
+ def content(self) -> str:
163
+ return self._post.content
164
+
165
+ def to_porcelain(self) -> "PorcelainNewsItemContentV1":
166
+ return {
167
+ "lang": self.lang,
168
+ "display_title": self.display_title,
169
+ "content": self.content,
170
+ }
171
+
172
+
173
+ class PorcelainNewsItemContentV1(TypedDict):
174
+ lang: str
175
+ display_title: str
176
+ content: str
177
+
178
+
179
+ class PorcelainNewsItemV1(PorcelainEntity):
180
+ id: str
181
+ ord: int
182
+ is_read: bool
183
+ langs: list[PorcelainNewsItemContentV1]