rumen 0.1.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.
rumen-0.1.0/Cargo.lock ADDED
@@ -0,0 +1,216 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "heck"
7
+ version = "0.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
10
+
11
+ [[package]]
12
+ name = "itoa"
13
+ version = "1.0.18"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
16
+
17
+ [[package]]
18
+ name = "libc"
19
+ version = "0.2.191"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
22
+
23
+ [[package]]
24
+ name = "memchr"
25
+ version = "2.7.4"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
28
+
29
+ [[package]]
30
+ name = "once_cell"
31
+ version = "1.21.4"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
34
+
35
+ [[package]]
36
+ name = "portable-atomic"
37
+ version = "1.15.0"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
40
+
41
+ [[package]]
42
+ name = "proc-macro2"
43
+ version = "1.0.107"
44
+ source = "registry+https://github.com/rust-lang/crates.io-index"
45
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
46
+ dependencies = [
47
+ "unicode-ident",
48
+ ]
49
+
50
+ [[package]]
51
+ name = "pyo3"
52
+ version = "0.30.1"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "229ca13b8a15bb0d1487b81ab975bfcd97513416499a8312011b6036e42e0de4"
55
+ dependencies = [
56
+ "libc",
57
+ "once_cell",
58
+ "portable-atomic",
59
+ "pyo3-build-config",
60
+ "pyo3-ffi",
61
+ "pyo3-macros",
62
+ ]
63
+
64
+ [[package]]
65
+ name = "pyo3-build-config"
66
+ version = "0.30.1"
67
+ source = "registry+https://github.com/rust-lang/crates.io-index"
68
+ checksum = "70130e2b6dd1ee589a8788026a429561ca5c40fa7a2fa7914c2282eddae24700"
69
+ dependencies = [
70
+ "target-lexicon",
71
+ ]
72
+
73
+ [[package]]
74
+ name = "pyo3-ffi"
75
+ version = "0.30.1"
76
+ source = "registry+https://github.com/rust-lang/crates.io-index"
77
+ checksum = "5e7ff73c555c286d212d4ce7e5fbda845a973c4f61bd5f160f5f2c3c2a04d1c4"
78
+ dependencies = [
79
+ "libc",
80
+ "pyo3-build-config",
81
+ ]
82
+
83
+ [[package]]
84
+ name = "pyo3-macros"
85
+ version = "0.30.1"
86
+ source = "registry+https://github.com/rust-lang/crates.io-index"
87
+ checksum = "c46d2e5862c7bb8207e3b639f713dda3c499933444784154c11d013c5a691b60"
88
+ dependencies = [
89
+ "proc-macro2",
90
+ "pyo3-macros-backend",
91
+ "quote",
92
+ "syn",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "pyo3-macros-backend"
97
+ version = "0.30.1"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "38b89f066f2ae72a2aef52d1537ca2fcd104440ebfadee06e97aa90738a25b9b"
100
+ dependencies = [
101
+ "heck",
102
+ "proc-macro2",
103
+ "quote",
104
+ "syn",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "quote"
109
+ version = "1.0.37"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
112
+ dependencies = [
113
+ "proc-macro2",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "rumen"
118
+ version = "0.1.0"
119
+ dependencies = [
120
+ "pyo3",
121
+ "serde",
122
+ "serde_json",
123
+ "thiserror",
124
+ ]
125
+
126
+ [[package]]
127
+ name = "serde"
128
+ version = "1.0.229"
129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
130
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
131
+ dependencies = [
132
+ "serde_core",
133
+ "serde_derive",
134
+ ]
135
+
136
+ [[package]]
137
+ name = "serde_core"
138
+ version = "1.0.229"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
141
+ dependencies = [
142
+ "serde_derive",
143
+ ]
144
+
145
+ [[package]]
146
+ name = "serde_derive"
147
+ version = "1.0.229"
148
+ source = "registry+https://github.com/rust-lang/crates.io-index"
149
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
150
+ dependencies = [
151
+ "proc-macro2",
152
+ "quote",
153
+ "syn",
154
+ ]
155
+
156
+ [[package]]
157
+ name = "serde_json"
158
+ version = "1.0.154"
159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
160
+ checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
161
+ dependencies = [
162
+ "itoa",
163
+ "memchr",
164
+ "serde",
165
+ "serde_core",
166
+ "zmij",
167
+ ]
168
+
169
+ [[package]]
170
+ name = "syn"
171
+ version = "3.0.9"
172
+ source = "registry+https://github.com/rust-lang/crates.io-index"
173
+ checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
174
+ dependencies = [
175
+ "proc-macro2",
176
+ "quote",
177
+ "unicode-ident",
178
+ ]
179
+
180
+ [[package]]
181
+ name = "target-lexicon"
182
+ version = "0.13.5"
183
+ source = "registry+https://github.com/rust-lang/crates.io-index"
184
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
185
+
186
+ [[package]]
187
+ name = "thiserror"
188
+ version = "2.0.21"
189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
190
+ checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e"
191
+ dependencies = [
192
+ "thiserror-impl",
193
+ ]
194
+
195
+ [[package]]
196
+ name = "thiserror-impl"
197
+ version = "2.0.21"
198
+ source = "registry+https://github.com/rust-lang/crates.io-index"
199
+ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524"
200
+ dependencies = [
201
+ "proc-macro2",
202
+ "quote",
203
+ "syn",
204
+ ]
205
+
206
+ [[package]]
207
+ name = "unicode-ident"
208
+ version = "1.0.13"
209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
210
+ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
211
+
212
+ [[package]]
213
+ name = "zmij"
214
+ version = "1.0.23"
215
+ source = "registry+https://github.com/rust-lang/crates.io-index"
216
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
rumen-0.1.0/Cargo.toml ADDED
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "rumen"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Fumen v115 decoder with optional Python bindings"
6
+ license = "MIT"
7
+ repository = "https://github.com/A-Minos/rumen"
8
+ include = ["src/**/*.rs", "tests/**/*", "LICENSES/*", "Cargo.toml", "Cargo.lock", "pyproject.toml", "rumen.pyi", "/README.md", "/LICENSE"]
9
+ readme = "README.md"
10
+
11
+ [lib]
12
+ crate-type = ["rlib", "cdylib"]
13
+
14
+ [features]
15
+ python = ["dep:pyo3"]
16
+
17
+ [dependencies]
18
+ pyo3 = { version = "0.30.1", optional = true, features = ["abi3-py311"] }
19
+ thiserror = "2.0.21"
20
+
21
+ [dev-dependencies]
22
+ serde = { version = "1", features = ["derive"] }
23
+ serde_json = "1"
rumen-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shoucandanghehe
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,24 @@
1
+ tetris-fumen 1.1.3 — https://github.com/knewjade/tetris-fumen
2
+ Reference revision: 4a77d0dc52216432227a43847012deed1b2f4536
3
+
4
+ MIT License
5
+
6
+ Copyright (c) 2019
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
rumen-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: rumen
3
+ Version: 0.1.0
4
+ License-File: LICENSE
5
+ License-File: LICENSES/tetris-fumen.txt
6
+ Summary: Fumen v115 decoder backed by Rust
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
10
+
11
+ # rumen
12
+
13
+ **小方块,大吞吐。Rust 干重活,Python 管好用。**
14
+
15
+ 用 Rust 编写的 **fumen v115 解码器**,通过 PyO3 提供 Python 绑定。输入谱面字符串或查看器链接,得到盘面、操作和注释,用来分析谱面、处理训练数据,或者接进你的方块工具。
16
+
17
+ - 每页独立的 `24 × 10` 盘面快照、方块操作与事件标志,支持注释继承和 Quiz。
18
+ - 紧凑盘面存储、原地转义、Quiz 缓冲区复用,减少重复分配与搬运。
19
+ - Python 支持按页访问、批量整数矩阵和类型提示,解码时释放 GIL。
20
+
21
+ ## Python:几行就够
22
+
23
+ 需要 **Python 3.11+、Rust 工具链和 uv**。以下从源码安装,在仓库根目录执行:
24
+
25
+ ```sh
26
+ uv venv
27
+ uv pip install .
28
+ ```
29
+
30
+ 用 `.venv` 中的 Python 运行:
31
+
32
+ ```python
33
+ import rumen
34
+
35
+ fumen = rumen.parse("v115@ghE8JeAgHvhAAAA")
36
+ page = fumen[0]
37
+ matrix = page.to_matrix() # list[list[int]],24 行 × 10 列
38
+
39
+ for page in fumen:
40
+ print(page.notes, page.flag.positions())
41
+
42
+ matrices = fumen.to_matrices() # list[list[list[int]]],页 / 行 / 列
43
+ ```
44
+
45
+ `fumen` 支持长度、下标、负索引和迭代。模型属性只读,导出的矩阵是独立的可变列表。
46
+
47
+ ## Rust:直接接上
48
+
49
+ 在消费项目的 `Cargo.toml` 中添加本地依赖,把路径指向这个仓库:
50
+
51
+ ```toml
52
+ [dependencies]
53
+ rumen = { path = "../rumen" }
54
+ ```
55
+
56
+ ```rust
57
+ use rumen::DecodeError;
58
+
59
+ fn main() -> Result<(), DecodeError> {
60
+ let fumen = rumen::parse("https://fumen.zui.jp/?v115@ghE8JeAgHvhAAAA")?;
61
+
62
+ for page in &fumen.pages {
63
+ println!("{} {:?}", page.notes, page.flag.positions());
64
+ }
65
+ Ok(())
66
+ }
67
+ ```
68
+
69
+ `Fumen` 也实现了 `FromStr`。原生 Rust 使用不需要开启 `python` feature。
70
+
71
+ ### 命令行也能用
72
+
73
+ ```sh
74
+ cargo run --release -- 'v115@ghE8JeAgHvhAAAA'
75
+ ```
76
+
77
+ 输出完整的 Rust Debug 结构,方便检查谱面。
78
+
79
+ ## 接口约定
80
+
81
+ - 盘面是当前页操作前的状态,按 `[y][x]` 访问;左上角为 `(0, 0)`,向右、向下递增,`y = 23` 为垃圾行。
82
+ - 矩阵编码 `0..8` 依次对应 `Empty, I, L, O, Z, T, J, S, Garbage`。
83
+ - `flag.location` 与 `flag.positions()` 使用同一套自上而下的坐标。`positions()` 返回方块占据的四个格子,无活动方块时为 `None`;坐标可能超出盘面。
84
+ - `page.notes` 是继承与 Quiz 推进后的可见注释,`page.flag.note` 表示该页是否显式更新了注释。
85
+ - 解码失败时,Rust 返回 `DecodeError`,Python 抛出 `rumen.DecodeError`(`ValueError` 子类)。
86
+ - 目前仅提供 v115 解码,不含编码器。
87
+
88
+ ## 开发
89
+
90
+ ```sh
91
+ cargo fmt --check
92
+ cargo test --all-features --locked
93
+ cargo clippy --all-targets --all-features --locked -- -D warnings
94
+
95
+ # 先按上文安装 Python 包;以下为 Linux / macOS 路径
96
+ .venv/bin/python -B -m unittest discover -s tests -p test_python.py
97
+ ```
98
+
99
+ ## 发布
100
+
101
+ [Release 工作流](https://github.com/A-Minos/rumen/actions/workflows/Release.yml) 构建 Linux x86_64 / aarch64、macOS x86_64 / arm64、Windows x86_64 的 ABI3 wheel,分别在 CPython 3.11–3.15 上测试,并验证源码包。
102
+
103
+ 推送代码、标签或普通手动运行只做检查。发布 GitHub Release 时,标签须为 `v<Cargo.toml 中的版本>`;全部检查通过后,crates.io 与 PyPI 使用 OIDC 发布同一版本。两端 Trusted Publisher 均绑定 `A-Minos/rumen` 和 `Release.yml`,不设置 Environment 限制。
104
+
105
+ crates.io 首个版本需在本地 `cargo publish --locked`,之后才能配置 Trusted Publisher。首发时推送对应版本标签,不创建 GitHub Release;手动运行工作流,选择该标签并勾选 `publish_pypi`,只补发 PyPI。
106
+
107
+ 项目采用 [MIT](LICENSE) 许可。协议行为参考 [tetris-fumen](https://github.com/knewjade/tetris-fumen),相关第三方许可见 [LICENSES/tetris-fumen.txt](LICENSES/tetris-fumen.txt)。
108
+
rumen-0.1.0/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # rumen
2
+
3
+ **小方块,大吞吐。Rust 干重活,Python 管好用。**
4
+
5
+ 用 Rust 编写的 **fumen v115 解码器**,通过 PyO3 提供 Python 绑定。输入谱面字符串或查看器链接,得到盘面、操作和注释,用来分析谱面、处理训练数据,或者接进你的方块工具。
6
+
7
+ - 每页独立的 `24 × 10` 盘面快照、方块操作与事件标志,支持注释继承和 Quiz。
8
+ - 紧凑盘面存储、原地转义、Quiz 缓冲区复用,减少重复分配与搬运。
9
+ - Python 支持按页访问、批量整数矩阵和类型提示,解码时释放 GIL。
10
+
11
+ ## Python:几行就够
12
+
13
+ 需要 **Python 3.11+、Rust 工具链和 uv**。以下从源码安装,在仓库根目录执行:
14
+
15
+ ```sh
16
+ uv venv
17
+ uv pip install .
18
+ ```
19
+
20
+ 用 `.venv` 中的 Python 运行:
21
+
22
+ ```python
23
+ import rumen
24
+
25
+ fumen = rumen.parse("v115@ghE8JeAgHvhAAAA")
26
+ page = fumen[0]
27
+ matrix = page.to_matrix() # list[list[int]],24 行 × 10 列
28
+
29
+ for page in fumen:
30
+ print(page.notes, page.flag.positions())
31
+
32
+ matrices = fumen.to_matrices() # list[list[list[int]]],页 / 行 / 列
33
+ ```
34
+
35
+ `fumen` 支持长度、下标、负索引和迭代。模型属性只读,导出的矩阵是独立的可变列表。
36
+
37
+ ## Rust:直接接上
38
+
39
+ 在消费项目的 `Cargo.toml` 中添加本地依赖,把路径指向这个仓库:
40
+
41
+ ```toml
42
+ [dependencies]
43
+ rumen = { path = "../rumen" }
44
+ ```
45
+
46
+ ```rust
47
+ use rumen::DecodeError;
48
+
49
+ fn main() -> Result<(), DecodeError> {
50
+ let fumen = rumen::parse("https://fumen.zui.jp/?v115@ghE8JeAgHvhAAAA")?;
51
+
52
+ for page in &fumen.pages {
53
+ println!("{} {:?}", page.notes, page.flag.positions());
54
+ }
55
+ Ok(())
56
+ }
57
+ ```
58
+
59
+ `Fumen` 也实现了 `FromStr`。原生 Rust 使用不需要开启 `python` feature。
60
+
61
+ ### 命令行也能用
62
+
63
+ ```sh
64
+ cargo run --release -- 'v115@ghE8JeAgHvhAAAA'
65
+ ```
66
+
67
+ 输出完整的 Rust Debug 结构,方便检查谱面。
68
+
69
+ ## 接口约定
70
+
71
+ - 盘面是当前页操作前的状态,按 `[y][x]` 访问;左上角为 `(0, 0)`,向右、向下递增,`y = 23` 为垃圾行。
72
+ - 矩阵编码 `0..8` 依次对应 `Empty, I, L, O, Z, T, J, S, Garbage`。
73
+ - `flag.location` 与 `flag.positions()` 使用同一套自上而下的坐标。`positions()` 返回方块占据的四个格子,无活动方块时为 `None`;坐标可能超出盘面。
74
+ - `page.notes` 是继承与 Quiz 推进后的可见注释,`page.flag.note` 表示该页是否显式更新了注释。
75
+ - 解码失败时,Rust 返回 `DecodeError`,Python 抛出 `rumen.DecodeError`(`ValueError` 子类)。
76
+ - 目前仅提供 v115 解码,不含编码器。
77
+
78
+ ## 开发
79
+
80
+ ```sh
81
+ cargo fmt --check
82
+ cargo test --all-features --locked
83
+ cargo clippy --all-targets --all-features --locked -- -D warnings
84
+
85
+ # 先按上文安装 Python 包;以下为 Linux / macOS 路径
86
+ .venv/bin/python -B -m unittest discover -s tests -p test_python.py
87
+ ```
88
+
89
+ ## 发布
90
+
91
+ [Release 工作流](https://github.com/A-Minos/rumen/actions/workflows/Release.yml) 构建 Linux x86_64 / aarch64、macOS x86_64 / arm64、Windows x86_64 的 ABI3 wheel,分别在 CPython 3.11–3.15 上测试,并验证源码包。
92
+
93
+ 推送代码、标签或普通手动运行只做检查。发布 GitHub Release 时,标签须为 `v<Cargo.toml 中的版本>`;全部检查通过后,crates.io 与 PyPI 使用 OIDC 发布同一版本。两端 Trusted Publisher 均绑定 `A-Minos/rumen` 和 `Release.yml`,不设置 Environment 限制。
94
+
95
+ crates.io 首个版本需在本地 `cargo publish --locked`,之后才能配置 Trusted Publisher。首发时推送对应版本标签,不创建 GitHub Release;手动运行工作流,选择该标签并勾选 `publish_pypi`,只补发 PyPI。
96
+
97
+ 项目采用 [MIT](LICENSE) 许可。协议行为参考 [tetris-fumen](https://github.com/knewjade/tetris-fumen),相关第三方许可见 [LICENSES/tetris-fumen.txt](LICENSES/tetris-fumen.txt)。
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.9.4,<2"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "rumen"
7
+ dynamic = ["version"]
8
+ description = "Fumen v115 decoder backed by Rust"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE", "LICENSES/*"]
13
+
14
+ [tool.maturin]
15
+ features = ["python"]
16
+ bindings = "pyo3"
rumen-0.1.0/rumen.pyi ADDED
@@ -0,0 +1,121 @@
1
+ from collections.abc import Iterator
2
+ from typing import ClassVar, SupportsIndex, final
3
+
4
+ __all__ = [
5
+ "Block",
6
+ "ColorScheme",
7
+ "DecodeError",
8
+ "Facing",
9
+ "Flag",
10
+ "Fumen",
11
+ "Location",
12
+ "Page",
13
+ "ViewMode",
14
+ "parse",
15
+ ]
16
+
17
+ class DecodeError(ValueError):
18
+ """A fumen input could not be decoded; the message preserves the Rust error."""
19
+
20
+ @final
21
+ class Block:
22
+ Empty: ClassVar[Block]
23
+ I: ClassVar[Block]
24
+ L: ClassVar[Block]
25
+ O: ClassVar[Block]
26
+ Z: ClassVar[Block]
27
+ T: ClassVar[Block]
28
+ J: ClassVar[Block]
29
+ S: ClassVar[Block]
30
+ Garbage: ClassVar[Block]
31
+ def __int__(self) -> int: ...
32
+ def __eq__(self, other: object, /) -> bool: ...
33
+ def __hash__(self) -> int: ...
34
+
35
+ @final
36
+ class Facing:
37
+ Down: ClassVar[Facing]
38
+ Right: ClassVar[Facing]
39
+ Up: ClassVar[Facing]
40
+ Left: ClassVar[Facing]
41
+ def __int__(self) -> int: ...
42
+ def __eq__(self, other: object, /) -> bool: ...
43
+ def __hash__(self) -> int: ...
44
+
45
+ @final
46
+ class ColorScheme:
47
+ GuideLine: ClassVar[ColorScheme]
48
+ Sega: ClassVar[ColorScheme]
49
+ def __int__(self) -> int: ...
50
+ def __eq__(self, other: object, /) -> bool: ...
51
+ def __hash__(self) -> int: ...
52
+
53
+ @final
54
+ class ViewMode:
55
+ Normal: ClassVar[ViewMode]
56
+ Mini: ClassVar[ViewMode]
57
+ List: ClassVar[ViewMode]
58
+ MiniList: ClassVar[ViewMode]
59
+ def __int__(self) -> int: ...
60
+ def __eq__(self, other: object, /) -> bool: ...
61
+ def __hash__(self) -> int: ...
62
+
63
+ @final
64
+ class Location:
65
+ """Wire coordinate: x=0..9, y=0..23, with (0, 0) at the top-left."""
66
+ @property
67
+ def x(self) -> int: ...
68
+ @property
69
+ def y(self) -> int: ...
70
+ def __eq__(self, other: object, /) -> bool: ...
71
+ def __hash__(self) -> int: ...
72
+
73
+ @final
74
+ class Flag:
75
+ @property
76
+ def mino(self) -> Block: ...
77
+ @property
78
+ def face(self) -> Facing | None: ...
79
+ @property
80
+ def location(self) -> Location | None: ...
81
+ @property
82
+ def up(self) -> bool: ...
83
+ @property
84
+ def mirror(self) -> bool: ...
85
+ @property
86
+ def color_scheme(self) -> ColorScheme: ...
87
+ @property
88
+ def note(self) -> bool: ...
89
+ @property
90
+ def lock(self) -> bool: ...
91
+ def positions(self) -> list[tuple[int, int]] | None:
92
+ """The four occupied cells in top-down coordinates, or None for no mino."""
93
+ def __eq__(self, other: object, /) -> bool: ...
94
+
95
+ @final
96
+ class Page:
97
+ @property
98
+ def flag(self) -> Flag: ...
99
+ @property
100
+ def notes(self) -> str:
101
+ """The resolved comment, including inheritance and quiz progression."""
102
+ def to_matrix(self) -> list[list[int]]:
103
+ """Copy the 24x10 field into block codes 0..8, with garbage at row 23."""
104
+ def __eq__(self, other: object, /) -> bool: ...
105
+
106
+ @final
107
+ class Fumen:
108
+ def __len__(self) -> int: ...
109
+ def __iter__(self) -> Iterator[Page]: ...
110
+ def __getitem__(self, index: SupportsIndex, /) -> Page:
111
+ """Copy one page; negative indices count from the end."""
112
+ def to_matrices(self) -> list[list[list[int]]]:
113
+ """Copy all fields into page/row/column integer lists, without per-cell calls."""
114
+ @property
115
+ def mode(self) -> ViewMode: ...
116
+ @property
117
+ def version(self) -> int: ...
118
+ def __eq__(self, other: object, /) -> bool: ...
119
+
120
+ def parse(input: str) -> Fumen:
121
+ """Decode a v115 string or viewer URL; invalid input raises DecodeError."""
@@ -0,0 +1,32 @@
1
+ #[cfg_attr(feature = "python", pyo3::pyclass(frozen, eq, hash, module = "rumen"))]
2
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3
+ #[repr(u8)]
4
+ pub enum Block {
5
+ Empty,
6
+ I,
7
+ L,
8
+ O,
9
+ Z,
10
+ T,
11
+ J,
12
+ S,
13
+ Garbage,
14
+ }
15
+
16
+ impl TryFrom<u8> for Block {
17
+ type Error = &'static str;
18
+ fn try_from(value: u8) -> Result<Self, Self::Error> {
19
+ Ok(match value {
20
+ 0 => Block::Empty,
21
+ 1 => Block::I,
22
+ 2 => Block::L,
23
+ 3 => Block::O,
24
+ 4 => Block::Z,
25
+ 5 => Block::T,
26
+ 6 => Block::J,
27
+ 7 => Block::S,
28
+ 8 => Block::Garbage,
29
+ _ => return Err("Invalid Block"),
30
+ })
31
+ }
32
+ }
@@ -0,0 +1,25 @@
1
+ const DECODE: [u8; 128] = {
2
+ let alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3
+ let mut table = [u8::MAX; 128];
4
+ let mut digit = 0;
5
+ while digit < alphabet.len() {
6
+ table[alphabet[digit] as usize] = digit as u8;
7
+ digit += 1;
8
+ }
9
+ table
10
+ };
11
+
12
+ pub(crate) fn decode(ch: char) -> Result<u8, &'static str> {
13
+ DECODE
14
+ .get(ch as usize)
15
+ .copied()
16
+ .filter(|&digit| digit < 64)
17
+ .ok_or("Invalid base64 char")
18
+ }
19
+
20
+ pub(crate) fn little_endian_value(data: &[u8]) -> u32 {
21
+ data.iter()
22
+ .enumerate()
23
+ .map(|(i, &n)| n as u32 * 64_u32.pow(i as u32))
24
+ .sum()
25
+ }