hikari-risa 1.0.0__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.
- hikari_risa-1.0.0/.gitignore +27 -0
- hikari_risa-1.0.0/LICENSE +21 -0
- hikari_risa-1.0.0/PKG-INFO +83 -0
- hikari_risa-1.0.0/README.md +49 -0
- hikari_risa-1.0.0/pyproject.toml +161 -0
- hikari_risa-1.0.0/risa/__init__.py +49 -0
- hikari_risa-1.0.0/risa/__main__.py +145 -0
- hikari_risa-1.0.0/risa/_about.py +31 -0
- hikari_risa-1.0.0/risa/binding.py +251 -0
- hikari_risa-1.0.0/risa/client.py +657 -0
- hikari_risa-1.0.0/risa/codecs.py +204 -0
- hikari_risa-1.0.0/risa/context.py +693 -0
- hikari_risa-1.0.0/risa/di.py +75 -0
- hikari_risa-1.0.0/risa/dispatch.py +828 -0
- hikari_risa-1.0.0/risa/errors.py +603 -0
- hikari_risa-1.0.0/risa/internal/__init__.py +22 -0
- hikari_risa-1.0.0/risa/internal/codec.py +547 -0
- hikari_risa-1.0.0/risa/internal/constants.py +36 -0
- hikari_risa-1.0.0/risa/internal/leases.py +197 -0
- hikari_risa-1.0.0/risa/internal/locks.py +87 -0
- hikari_risa-1.0.0/risa/internal/reader.py +88 -0
- hikari_risa-1.0.0/risa/internal/registry.py +208 -0
- hikari_risa-1.0.0/risa/internal/wire.py +118 -0
- hikari_risa-1.0.0/risa/py.typed +0 -0
- hikari_risa-1.0.0/risa/state/__init__.py +25 -0
- hikari_risa-1.0.0/risa/state/anchor.py +279 -0
- hikari_risa-1.0.0/risa/state/backend.py +155 -0
- hikari_risa-1.0.0/risa/state/message.py +278 -0
- hikari_risa-1.0.0/risa/state/placement.py +104 -0
- hikari_risa-1.0.0/risa/state/schema.py +447 -0
- hikari_risa-1.0.0/risa/state/serde.py +69 -0
- hikari_risa-1.0.0/risa/state/stateless.py +94 -0
- hikari_risa-1.0.0/risa/state/stored.py +444 -0
- hikari_risa-1.0.0/risa/store/__init__.py +30 -0
- hikari_risa-1.0.0/risa/store/memory.py +152 -0
- hikari_risa-1.0.0/risa/store/postgres.py +311 -0
- hikari_risa-1.0.0/risa/store/protocol.py +172 -0
- hikari_risa-1.0.0/risa/store/redis.py +254 -0
- hikari_risa-1.0.0/risa/store/testing.py +341 -0
- hikari_risa-1.0.0/risa/ui/__init__.py +26 -0
- hikari_risa-1.0.0/risa/ui/build.py +353 -0
- hikari_risa-1.0.0/risa/ui/nodes.py +918 -0
- hikari_risa-1.0.0/risa/view.py +466 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Tool caches
|
|
13
|
+
.nox/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
|
|
17
|
+
# IDEs
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
|
|
21
|
+
# Secrets
|
|
22
|
+
.env
|
|
23
|
+
|
|
24
|
+
# Documentation site
|
|
25
|
+
site/
|
|
26
|
+
docs/assets/images/
|
|
27
|
+
docs/generated/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ray
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: hikari-risa
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A component handler for hikari with first-class Components V2 support.
|
|
5
|
+
Project-URL: Documentation, https://rayakame.github.io/hikari-risa/
|
|
6
|
+
Project-URL: Repository, https://github.com/rayakame/hikari-risa
|
|
7
|
+
Project-URL: Issues, https://github.com/rayakame/hikari-risa/issues
|
|
8
|
+
Project-URL: Changelog, https://rayakame.github.io/hikari-risa/stable/changelog/
|
|
9
|
+
Author-email: Rayakame <github.android206@passmail.net>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: components,discord,hikari,ui
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: <3.16,>=3.12
|
|
24
|
+
Requires-Dist: hikari~=2.5
|
|
25
|
+
Requires-Dist: linkd~=0.6
|
|
26
|
+
Requires-Dist: msgspec~=0.22
|
|
27
|
+
Provides-Extra: postgres
|
|
28
|
+
Requires-Dist: asyncpg~=0.32; extra == 'postgres'
|
|
29
|
+
Provides-Extra: radixly
|
|
30
|
+
Requires-Dist: radixly~=1.0; extra == 'radixly'
|
|
31
|
+
Provides-Extra: redis
|
|
32
|
+
Requires-Dist: redis~=8.0; extra == 'redis'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<img src="https://raw.githubusercontent.com/rayakame/hikari-risa/a14722c65c6df514d463402cde7f7f48d133bd66/assets/web/banner.webp" alt="hikari-risa" width="100%">
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
# Components V2 views for hikari, with state that survives restarts.
|
|
40
|
+
|
|
41
|
+
risa is a component handler for [hikari](https://github.com/hikari-py/hikari), built for Components V2 first.
|
|
42
|
+
A view keeps its state in the message it renders, or in a store you choose, so a click still lands where it should
|
|
43
|
+
after a restart.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import risa
|
|
47
|
+
from risa import ui
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@risa.register(name="counter")
|
|
51
|
+
class Counter(risa.View):
|
|
52
|
+
count: int = 0
|
|
53
|
+
|
|
54
|
+
def render(self) -> ui.Layout:
|
|
55
|
+
return ui.Container(
|
|
56
|
+
ui.TextDisplay(f"Clicks: **{self.count}**"),
|
|
57
|
+
ui.Row(ui.Button(self.click, label="+1")),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@risa.handler
|
|
61
|
+
async def click(self, ctx: risa.ComponentContext) -> None:
|
|
62
|
+
self.count += 1
|
|
63
|
+
await ctx.rerender()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
risa runs on Python 3.12 to 3.15.
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
uv add hikari-risa
|
|
72
|
+
pip install hikari-risa
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Extras: `hikari-risa[redis]`, `hikari-risa[postgres]` and `hikari-risa[radixly]`.
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
<https://rayakame.github.io/hikari-risa/>
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT, see [LICENSE](https://github.com/rayakame/hikari-risa/blob/main/LICENSE).
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rayakame/hikari-risa/a14722c65c6df514d463402cde7f7f48d133bd66/assets/web/banner.webp" alt="hikari-risa" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Components V2 views for hikari, with state that survives restarts.
|
|
6
|
+
|
|
7
|
+
risa is a component handler for [hikari](https://github.com/hikari-py/hikari), built for Components V2 first.
|
|
8
|
+
A view keeps its state in the message it renders, or in a store you choose, so a click still lands where it should
|
|
9
|
+
after a restart.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import risa
|
|
13
|
+
from risa import ui
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@risa.register(name="counter")
|
|
17
|
+
class Counter(risa.View):
|
|
18
|
+
count: int = 0
|
|
19
|
+
|
|
20
|
+
def render(self) -> ui.Layout:
|
|
21
|
+
return ui.Container(
|
|
22
|
+
ui.TextDisplay(f"Clicks: **{self.count}**"),
|
|
23
|
+
ui.Row(ui.Button(self.click, label="+1")),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
@risa.handler
|
|
27
|
+
async def click(self, ctx: risa.ComponentContext) -> None:
|
|
28
|
+
self.count += 1
|
|
29
|
+
await ctx.rerender()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
risa runs on Python 3.12 to 3.15.
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
uv add hikari-risa
|
|
38
|
+
pip install hikari-risa
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Extras: `hikari-risa[redis]`, `hikari-risa[postgres]` and `hikari-risa[radixly]`.
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
<https://rayakame.github.io/hikari-risa/>
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT, see [LICENSE](https://github.com/rayakame/hikari-risa/blob/main/LICENSE).
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hikari-risa"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A component handler for hikari with first-class Components V2 support."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Rayakame", email = "github.android206@passmail.net" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
license-files = ["LICENSE"]
|
|
11
|
+
requires-python = ">=3.12,<3.16"
|
|
12
|
+
keywords = ["hikari", "discord", "components", "ui"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
|
+
"Framework :: AsyncIO",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Programming Language :: Python :: 3.15",
|
|
22
|
+
"Topic :: Communications :: Chat",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"hikari~=2.5",
|
|
27
|
+
"linkd~=0.6",
|
|
28
|
+
"msgspec~=0.22",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
postgres = [
|
|
33
|
+
"asyncpg~=0.32",
|
|
34
|
+
]
|
|
35
|
+
radixly = [
|
|
36
|
+
"radixly~=1.0",
|
|
37
|
+
]
|
|
38
|
+
redis = [
|
|
39
|
+
"redis~=8.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
hikari-risa = "risa.__main__:main"
|
|
44
|
+
|
|
45
|
+
[project.urls]
|
|
46
|
+
Documentation = "https://rayakame.github.io/hikari-risa/"
|
|
47
|
+
Repository = "https://github.com/rayakame/hikari-risa"
|
|
48
|
+
Issues = "https://github.com/rayakame/hikari-risa/issues"
|
|
49
|
+
Changelog = "https://rayakame.github.io/hikari-risa/stable/changelog/"
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["hatchling>=1.27"]
|
|
53
|
+
build-backend = "hatchling.build"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["risa"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.sdist]
|
|
59
|
+
only-include = ["risa"]
|
|
60
|
+
|
|
61
|
+
[dependency-groups]
|
|
62
|
+
changelog = [
|
|
63
|
+
"towncrier==26.9.0",
|
|
64
|
+
]
|
|
65
|
+
docs = [
|
|
66
|
+
{include-group = "ruff"},
|
|
67
|
+
"griffe-inherited-docstrings==1.1.4",
|
|
68
|
+
"griffelib==2.4.1",
|
|
69
|
+
"mkdocstrings-python==2.0.10",
|
|
70
|
+
"pygments==2.21.0",
|
|
71
|
+
"radixly~=1.0",
|
|
72
|
+
"redis~=8.0",
|
|
73
|
+
"zensical==0.1.0",
|
|
74
|
+
]
|
|
75
|
+
nox = [
|
|
76
|
+
"nox[uv]==2026.10.20",
|
|
77
|
+
]
|
|
78
|
+
images = [
|
|
79
|
+
"pillow==13.0.0",
|
|
80
|
+
]
|
|
81
|
+
lightbulb = [
|
|
82
|
+
"hikari-lightbulb==3.2.6",
|
|
83
|
+
]
|
|
84
|
+
pyright = [
|
|
85
|
+
"asyncpg-stubs==0.32.0",
|
|
86
|
+
"pyright==1.1.414",
|
|
87
|
+
{include-group = "lightbulb"},
|
|
88
|
+
]
|
|
89
|
+
ruff = [
|
|
90
|
+
"ruff==0.17.4",
|
|
91
|
+
]
|
|
92
|
+
slotscheck = [
|
|
93
|
+
"asyncpg~=0.32",
|
|
94
|
+
"redis~=8.0",
|
|
95
|
+
"slotscheck==0.21.0",
|
|
96
|
+
]
|
|
97
|
+
pytest = [
|
|
98
|
+
{include-group = "images"},
|
|
99
|
+
"asyncpg~=0.32",
|
|
100
|
+
"fakeredis[lua]==2.40.0",
|
|
101
|
+
"griffe-inherited-docstrings==1.1.4",
|
|
102
|
+
"griffelib==2.4.1",
|
|
103
|
+
"pygments==2.21.0",
|
|
104
|
+
"pytest==9.2.0",
|
|
105
|
+
"pytest-asyncio==1.4.1.post1",
|
|
106
|
+
"radixly~=1.0",
|
|
107
|
+
"redis~=8.0",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.towncrier]
|
|
111
|
+
directory = "changes"
|
|
112
|
+
filename = "CHANGELOG.md"
|
|
113
|
+
start_string = "<!-- towncrier release notes start -->\n"
|
|
114
|
+
title_format = "## {version} ({project_date})"
|
|
115
|
+
issue_format = "[#{issue}](https://github.com/rayakame/hikari-risa/pull/{issue})"
|
|
116
|
+
|
|
117
|
+
[[tool.towncrier.type]]
|
|
118
|
+
directory = "breaking"
|
|
119
|
+
name = "Breaking changes"
|
|
120
|
+
showcontent = true
|
|
121
|
+
|
|
122
|
+
[[tool.towncrier.type]]
|
|
123
|
+
directory = "deprecation"
|
|
124
|
+
name = "Deprecations"
|
|
125
|
+
showcontent = true
|
|
126
|
+
|
|
127
|
+
[[tool.towncrier.type]]
|
|
128
|
+
directory = "feature"
|
|
129
|
+
name = "Features"
|
|
130
|
+
showcontent = true
|
|
131
|
+
|
|
132
|
+
[[tool.towncrier.type]]
|
|
133
|
+
directory = "bugfix"
|
|
134
|
+
name = "Bug fixes"
|
|
135
|
+
showcontent = true
|
|
136
|
+
|
|
137
|
+
[tool.slotscheck]
|
|
138
|
+
require-subclass = true
|
|
139
|
+
strict-imports = true
|
|
140
|
+
# detect-unused-slots stays off: it cannot see msgspec Structs use the slots it
|
|
141
|
+
# generates for them, so every Struct in the package reports a false positive.
|
|
142
|
+
|
|
143
|
+
[tool.pyright]
|
|
144
|
+
include = ["risa", "tests", "tools", "docs/examples"]
|
|
145
|
+
exclude = [
|
|
146
|
+
"**/__init__.py"
|
|
147
|
+
]
|
|
148
|
+
pythonVersion = "3.12"
|
|
149
|
+
typeCheckingMode = "strict"
|
|
150
|
+
|
|
151
|
+
reportUnnecessaryTypeIgnoreComment = "error"
|
|
152
|
+
|
|
153
|
+
[tool.pytest.ini_options]
|
|
154
|
+
testpaths = ["tests"]
|
|
155
|
+
pythonpath = ["."]
|
|
156
|
+
asyncio_mode = "auto"
|
|
157
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
158
|
+
xfail_strict = true
|
|
159
|
+
filterwarnings = [
|
|
160
|
+
"error",
|
|
161
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Copyright (c) 2025 Rayakame
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
"""A component handler for hikari, built on Discord's Components V2.
|
|
21
|
+
|
|
22
|
+
A view is a class whose fields are its state, whose `render` returns its layout, and
|
|
23
|
+
whose handlers answer the clicks on it. The state travels inside the message by
|
|
24
|
+
default, so views survive restarts with no infrastructure. A view that outgrows the
|
|
25
|
+
message can keep its state in a store.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from risa import codecs as codecs
|
|
31
|
+
from risa import di as di
|
|
32
|
+
from risa import errors as errors
|
|
33
|
+
from risa import state as state
|
|
34
|
+
from risa import store as store
|
|
35
|
+
from risa import ui as ui
|
|
36
|
+
from risa._about import __author__ as __author__
|
|
37
|
+
from risa._about import __copyright__ as __copyright__
|
|
38
|
+
from risa._about import __discord_invite__ as __discord_invite__
|
|
39
|
+
from risa._about import __license__ as __license__
|
|
40
|
+
from risa._about import __url__ as __url__
|
|
41
|
+
from risa._about import __version__ as __version__
|
|
42
|
+
from risa.binding import *
|
|
43
|
+
from risa.client import *
|
|
44
|
+
from risa.context import *
|
|
45
|
+
from risa.di import *
|
|
46
|
+
from risa.errors import *
|
|
47
|
+
from risa.state import *
|
|
48
|
+
from risa.store import *
|
|
49
|
+
from risa.view import *
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Copyright (c) 2025 Rayakame
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
"""The ``hikari-risa`` command, also ``python -m risa``: the versions a bug report needs."""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import ctypes
|
|
26
|
+
import importlib.metadata
|
|
27
|
+
import os
|
|
28
|
+
import pathlib
|
|
29
|
+
import platform
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
import typing
|
|
33
|
+
|
|
34
|
+
import risa
|
|
35
|
+
|
|
36
|
+
if typing.TYPE_CHECKING:
|
|
37
|
+
import collections.abc
|
|
38
|
+
|
|
39
|
+
__all__ = ("main",)
|
|
40
|
+
|
|
41
|
+
_ART: typing.Final[collections.abc.Sequence[str]] = (
|
|
42
|
+
r" _ ",
|
|
43
|
+
r" _____(_)________ _",
|
|
44
|
+
r" / ___/ / ___/ __ `/",
|
|
45
|
+
r" / / / (__ ) /_/ / ",
|
|
46
|
+
r"/_/ /_/____/\__,_/ ",
|
|
47
|
+
)
|
|
48
|
+
# 256-colour codes that read on dark and light terminals.
|
|
49
|
+
_ART_COLOURS: typing.Final[collections.abc.Sequence[str]] = ("38;5;205", "38;5;206", "38;5;170", "38;5;134", "38;5;98")
|
|
50
|
+
_PINK: typing.Final[str] = "38;5;205"
|
|
51
|
+
_BOLD: typing.Final[str] = "1"
|
|
52
|
+
_DIM: typing.Final[str] = "2"
|
|
53
|
+
_LABEL_WIDTH: typing.Final[int] = 10
|
|
54
|
+
_EXTRA: typing.Final[re.Pattern[str]] = re.compile(r"""extra\s*==\s*["']([^"']+)["']""")
|
|
55
|
+
_STD_OUTPUT_HANDLE: typing.Final[int] = -11
|
|
56
|
+
_ENABLE_VIRTUAL_TERMINAL_PROCESSING: typing.Final[int] = 0x0004
|
|
57
|
+
|
|
58
|
+
type _Paint = collections.abc.Callable[[str, str], str]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _escape_codes_work() -> bool:
|
|
62
|
+
# A Windows console reads escape codes only once the program turns on virtual terminal processing.
|
|
63
|
+
if sys.platform != "win32":
|
|
64
|
+
return True
|
|
65
|
+
kernel32 = ctypes.WinDLL("kernel32")
|
|
66
|
+
kernel32.GetStdHandle.restype = ctypes.c_void_p
|
|
67
|
+
kernel32.GetConsoleMode.argtypes = (ctypes.c_void_p, ctypes.POINTER(ctypes.c_uint32))
|
|
68
|
+
kernel32.SetConsoleMode.argtypes = (ctypes.c_void_p, ctypes.c_uint32)
|
|
69
|
+
handle = kernel32.GetStdHandle(_STD_OUTPUT_HANDLE)
|
|
70
|
+
mode = ctypes.c_uint32()
|
|
71
|
+
if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
72
|
+
return False
|
|
73
|
+
return bool(kernel32.SetConsoleMode(handle, mode.value | _ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _section(
|
|
77
|
+
paint: _Paint, label: str, rows: collections.abc.Sequence[collections.abc.Sequence[str]]
|
|
78
|
+
) -> collections.abc.Iterator[str]:
|
|
79
|
+
# The last column goes unpadded, so it may carry colour codes without breaking the alignment.
|
|
80
|
+
widths = [max(len(row[column]) for row in rows) for column in range(len(rows[0]) - 1)] if rows else []
|
|
81
|
+
for index, row in enumerate(rows):
|
|
82
|
+
cells = [cell.ljust(width) for cell, width in zip(row, widths, strict=False)]
|
|
83
|
+
heading = paint((label if index == 0 else "").ljust(_LABEL_WIDTH), _PINK)
|
|
84
|
+
yield f" {heading}{' '.join([*cells, row[-1]])}"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _report(*, colour: bool) -> collections.abc.Iterator[str]:
|
|
88
|
+
def paint(text: str, style: str) -> str:
|
|
89
|
+
return f"\x1b[{style}m{text}\x1b[0m" if colour else text
|
|
90
|
+
|
|
91
|
+
for line, style in zip(_ART[:-1], _ART_COLOURS, strict=False):
|
|
92
|
+
yield paint(line.rstrip(), style)
|
|
93
|
+
yield paint(_ART[-1], _ART_COLOURS[-1]) + paint(f"hikari-risa {risa.__version__}", _BOLD)
|
|
94
|
+
|
|
95
|
+
metadata = importlib.metadata.metadata("hikari-risa")
|
|
96
|
+
urls = dict(url.split(", ", 1) for url in metadata.get_all("Project-URL") or ())
|
|
97
|
+
required: list[collections.abc.Sequence[str]] = []
|
|
98
|
+
extras: list[collections.abc.Sequence[str]] = []
|
|
99
|
+
for requirement in importlib.metadata.requires("hikari-risa") or ():
|
|
100
|
+
name = re.split(r"[^\w.-]", requirement, maxsplit=1)[0]
|
|
101
|
+
try:
|
|
102
|
+
version = importlib.metadata.version(name)
|
|
103
|
+
except importlib.metadata.PackageNotFoundError:
|
|
104
|
+
version = paint("not installed", _DIM)
|
|
105
|
+
if extra := _EXTRA.search(requirement):
|
|
106
|
+
extras.append((extra[1], name, version))
|
|
107
|
+
else:
|
|
108
|
+
required.append((name, version))
|
|
109
|
+
|
|
110
|
+
yield ""
|
|
111
|
+
yield paint(metadata["Summary"], _DIM)
|
|
112
|
+
yield ""
|
|
113
|
+
yield from _section(paint, "python", [(f"{platform.python_implementation()} {platform.python_version()}",)])
|
|
114
|
+
yield from _section(paint, "platform", [(platform.platform(),)])
|
|
115
|
+
yield from _section(paint, "location", [(str(pathlib.Path(risa.__file__).parent),)])
|
|
116
|
+
yield ""
|
|
117
|
+
yield from _section(paint, "requires", required)
|
|
118
|
+
yield ""
|
|
119
|
+
yield from _section(paint, "extras", extras)
|
|
120
|
+
yield ""
|
|
121
|
+
yield from _section(paint, "docs", [(urls["Documentation"],)])
|
|
122
|
+
yield from _section(paint, "issues", [(urls["Issues"],)])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main() -> None:
|
|
126
|
+
"""Print the versions of risa, of Python and of the packages risa installs."""
|
|
127
|
+
parser = argparse.ArgumentParser(
|
|
128
|
+
prog="hikari-risa", description="Print the versions of risa, Python and what risa installs, for a bug report."
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument("--version", action="version", version=f"hikari-risa {risa.__version__}")
|
|
131
|
+
parser.parse_args()
|
|
132
|
+
colour = (
|
|
133
|
+
sys.stdout.isatty()
|
|
134
|
+
and not os.environ.get("NO_COLOR")
|
|
135
|
+
and os.environ.get("TERM") != "dumb"
|
|
136
|
+
and _escape_codes_work()
|
|
137
|
+
)
|
|
138
|
+
report = "\n".join(_report(colour=colour)) + "\n"
|
|
139
|
+
# Before 3.15 a Windows pipe writes in the ANSI code page, which may lack a character of the path.
|
|
140
|
+
encoding = sys.stdout.encoding or "utf-8"
|
|
141
|
+
sys.stdout.write(report.encode(encoding, "backslashreplace").decode(encoding))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright (c) 2025 Rayakame
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
"""Package metadata, re-exported by `risa`."""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import typing
|
|
25
|
+
|
|
26
|
+
__author__: typing.Final[str] = "Rayakame"
|
|
27
|
+
__copyright__: typing.Final[str] = "2025, Rayakame"
|
|
28
|
+
__discord_invite__: typing.Final[str] = "https://discord.gg/Jx4cNGG"
|
|
29
|
+
__url__: typing.Final[str] = "https://github.com/rayakame/hikari-risa"
|
|
30
|
+
__version__: typing.Final[str] = "1.0.0"
|
|
31
|
+
__license__: typing.Final[str] = "MIT"
|