rusto 0.3.2__tar.gz → 0.4.1__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.
- rusto-0.4.1/PKG-INFO +189 -0
- rusto-0.4.1/README.md +178 -0
- {rusto-0.3.2 → rusto-0.4.1}/pyproject.toml +2 -2
- rusto-0.4.1/rusto/__init__.py +928 -0
- rusto-0.4.1/rusto/generated/librusto.so +0 -0
- {rusto-0.3.2 → rusto-0.4.1}/rusto/generated/rusto.py +1046 -302
- rusto-0.4.1/rusto.egg-info/PKG-INFO +189 -0
- rusto-0.4.1/tests/test_rusto.py +216 -0
- rusto-0.3.2/PKG-INFO +0 -41
- rusto-0.3.2/README.md +0 -30
- rusto-0.3.2/rusto/__init__.py +0 -58
- rusto-0.3.2/rusto/generated/librusto.so +0 -0
- rusto-0.3.2/rusto.egg-info/PKG-INFO +0 -41
- rusto-0.3.2/tests/test_rusto.py +0 -61
- {rusto-0.3.2 → rusto-0.4.1}/rusto.egg-info/SOURCES.txt +0 -0
- {rusto-0.3.2 → rusto-0.4.1}/rusto.egg-info/dependency_links.txt +0 -0
- {rusto-0.3.2 → rusto-0.4.1}/rusto.egg-info/requires.txt +0 -0
- {rusto-0.3.2 → rusto-0.4.1}/rusto.egg-info/top_level.txt +0 -0
- {rusto-0.3.2 → rusto-0.4.1}/setup.cfg +0 -0
rusto-0.4.1/PKG-INFO
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rusto
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Pure Rust OCR library for Python based on RapidOCR, Indra OCR, and pure-Rust MNN (rusto-nn)
|
|
5
|
+
Author-email: RustO Contributors <contact@rusto.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Provides-Extra: test
|
|
10
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
11
|
+
|
|
12
|
+
# rusto (Python SDK)
|
|
13
|
+
|
|
14
|
+
Official Python bindings for **RustO!** — a high-performance, pure-Rust OCR engine powered by Indra OCR, PaddleOCR, and the pure-Rust MNN (`rusto-nn`) inference runtime via UniFFI.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Zero-Config PP-OCRv6 Tiny Default**: `RustO.initialize()` automatically downloads and caches the default `ppocrv6-tiny` (FP32, Apache 2.0) models on first run from Hugging Face Hub.
|
|
19
|
+
- **Context Manager Support**: Deterministic native resource cleanup with `with RustO.initialize() as engine:` or `engine.close()`.
|
|
20
|
+
- **Full Document Pipeline**: Neural document unwarping (`with_auto_unwarp()`), page orientation detection (`with_orientation_detection()`), semantic layout analysis, and reading-order recovery (`with_layout_reading()`).
|
|
21
|
+
- **Fluent Builder API**: Chain `.as_lines()`, `.as_words()`, `.as_spatial()`, `.as_tsv()`, or `.with_output(...)` into a single `.run()` terminal method.
|
|
22
|
+
- **Zero-Copy Raw RGB Support**: Feed NumPy / OpenCV / Pillow RGB8 buffers directly via `.rgb(pixels, width, height, stride)`.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install rusto
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from rusto import RustO
|
|
34
|
+
|
|
35
|
+
# Zero-config initialization (auto-downloads PP-OCRv6 Tiny FP32 models from Hugging Face Hub if not cached)
|
|
36
|
+
with RustO.initialize() as engine:
|
|
37
|
+
result = (
|
|
38
|
+
engine.detect_text("invoice.png")
|
|
39
|
+
.with_auto_unwarp()
|
|
40
|
+
.with_layout_reading()
|
|
41
|
+
.as_lines()
|
|
42
|
+
.run()
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if result.is_STRUCTURED():
|
|
46
|
+
for item in result.results:
|
|
47
|
+
print(
|
|
48
|
+
f"[{item.score:.2f}] ({item.layout_type or 'line'} #{item.region_id}) {item.text}"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Reconstructed 2D spatial text layout
|
|
52
|
+
spatial = engine.detect_text("invoice.png").as_spatial().run()
|
|
53
|
+
if spatial.is_SPATIAL():
|
|
54
|
+
print(spatial.text)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
You can also manage the engine lifecycle explicitly without a `with` block:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from rusto import InitializeConfig, RustO
|
|
61
|
+
|
|
62
|
+
engine = RustO.initialize(InitializeConfig.indra())
|
|
63
|
+
try:
|
|
64
|
+
result = engine.detect_text("invoice.png").as_words().run()
|
|
65
|
+
if result.is_STRUCTURED():
|
|
66
|
+
for word in result.results:
|
|
67
|
+
print(word.text, word.frame)
|
|
68
|
+
finally:
|
|
69
|
+
engine.close()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Custom Configuration & Offline Models (.mnn / .rten)
|
|
73
|
+
|
|
74
|
+
Select canonical model tiers (`"indraocr"`, `"ppocrv6-tiny"`, `"ppocrv6-small"`, `"ppocrv5-mobile"`, `"ppocrv4-mobile"`), languages, or local model paths:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from rusto import (
|
|
78
|
+
DetectionConfig,
|
|
79
|
+
InitializeConfig,
|
|
80
|
+
LayoutConfig,
|
|
81
|
+
RecognitionConfig,
|
|
82
|
+
RustO,
|
|
83
|
+
UnwarpConfig,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Preset by canonical model + language (auto-downloaded to cache)
|
|
87
|
+
cfg = InitializeConfig.for_model("ppocrv6-tiny", lang="latin")
|
|
88
|
+
engine = RustO.initialize(cfg)
|
|
89
|
+
engine.close()
|
|
90
|
+
|
|
91
|
+
# Explicit local assets for offline environments
|
|
92
|
+
offline_cfg = InitializeConfig(
|
|
93
|
+
template="indra",
|
|
94
|
+
enable_reading_order=True,
|
|
95
|
+
detection=DetectionConfig(enabled=True, model_path="./models/indra_det.mnn"),
|
|
96
|
+
recognition=RecognitionConfig(
|
|
97
|
+
enabled=True,
|
|
98
|
+
model_path="./models/indra_rec.mnn",
|
|
99
|
+
dict_path="./models/indra_meta.json",
|
|
100
|
+
),
|
|
101
|
+
unwarp=UnwarpConfig(enabled=True, model_path="./models/uvdoc.mnn"),
|
|
102
|
+
layout=LayoutConfig(enabled=True, model_path="./models/layout_cdla.mnn"),
|
|
103
|
+
)
|
|
104
|
+
with RustO.initialize(offline_cfg) as offline_engine:
|
|
105
|
+
res = offline_engine.detect_text("scan.png").with_auto_unwarp().as_lines().run()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pre-download models into a target cache directory ahead of time (e.g., during Docker image build):
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from rusto import download_model
|
|
112
|
+
|
|
113
|
+
paths = download_model(model="ppocrv6-tiny", cache_dir="./models-cache")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Input Sources & Raw RGB Buffers
|
|
117
|
+
|
|
118
|
+
Pass a file path (`str` or `Path`), encoded image `bytes`, `ImageSource`, or raw interleaved 24-bit RGB pixels:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from pathlib import Path
|
|
122
|
+
import numpy as np
|
|
123
|
+
from rusto import RustO
|
|
124
|
+
|
|
125
|
+
with RustO.initialize() as engine:
|
|
126
|
+
# 1. Encoded PNG/JPEG bytes
|
|
127
|
+
png_bytes = Path("invoice.png").read_bytes()
|
|
128
|
+
res_bytes = engine.detect_text(png_bytes).as_lines().run()
|
|
129
|
+
|
|
130
|
+
# 2. Raw HxWx3 uint8 RGB array (e.g. from Pillow / imageio)
|
|
131
|
+
rgb_array = np.zeros((720, 1280, 3), dtype=np.uint8)
|
|
132
|
+
height, width, _ = rgb_array.shape
|
|
133
|
+
stride = width * 3
|
|
134
|
+
|
|
135
|
+
res_rgb = (
|
|
136
|
+
engine.detect_text()
|
|
137
|
+
.rgb(rgb_array.tobytes(), width, height, stride)
|
|
138
|
+
.as_lines()
|
|
139
|
+
.run()
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 6-Phase Pipeline Options
|
|
144
|
+
|
|
145
|
+
Customize every phase of OCR execution per request:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from rusto import (
|
|
149
|
+
DetectionRunOptions,
|
|
150
|
+
OutputGranularity,
|
|
151
|
+
PostDetectionOptions,
|
|
152
|
+
PostRecognitionOptions,
|
|
153
|
+
PreDetectionOptions,
|
|
154
|
+
PreRecognitionOptions,
|
|
155
|
+
RecognitionRunOptions,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
result = (
|
|
159
|
+
engine.detect_text("document.jpg")
|
|
160
|
+
.with_orientation_detection()
|
|
161
|
+
.with_auto_unwarp()
|
|
162
|
+
.with_layout_reading()
|
|
163
|
+
.with_pre_detection(PreDetectionOptions(descreen_strength=0.3, target_max_side=1280))
|
|
164
|
+
.with_detection(DetectionRunOptions(limit_side_len=960, limit_type="max"))
|
|
165
|
+
.with_post_detection(PostDetectionOptions(threshold=0.3, box_threshold=0.6, unclip_ratio=1.6))
|
|
166
|
+
.with_pre_recognition(PreRecognitionOptions(line_y_threshold=0.5, word_x_threshold=0.4))
|
|
167
|
+
.with_recognition(RecognitionRunOptions(batch_size=8))
|
|
168
|
+
.with_post_recognition(PostRecognitionOptions(score_threshold=0.5))
|
|
169
|
+
.with_output(OutputGranularity.LINES)
|
|
170
|
+
.run()
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Result Reference
|
|
175
|
+
|
|
176
|
+
`DetectTextResult` is a tagged union returned by `.run()`:
|
|
177
|
+
|
|
178
|
+
- `result.is_STRUCTURED()` -> `True` for `.as_lines()` and `.as_words()`. Access `result.results` (`list[TextResult]`):
|
|
179
|
+
- `item.text` (`str`): Recognized text string.
|
|
180
|
+
- `item.score` (`float`): Confidence score (`0.0` – `1.0`).
|
|
181
|
+
- `item.box_points` (`list[Point]`): 4 polygon vertices (`x`, `y`).
|
|
182
|
+
- `item.frame` (`Frame`): Axis-aligned bounding box (`width`, `height`, `top`, `left`).
|
|
183
|
+
- `item.region_id` (`int | None`): Layout region index when `with_layout_reading()` is active.
|
|
184
|
+
- `item.layout_type` (`LayoutType | None`): Semantic region category (`TEXT`, `TITLE`, `TABLE`, `HEADER`, `FOOTER`, `FIGURE`, etc.).
|
|
185
|
+
- `result.is_SPATIAL()` -> `True` for `.as_spatial()`. Access `result.text` (`str`).
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
Apache-2.0 OR MIT
|
rusto-0.4.1/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# rusto (Python SDK)
|
|
2
|
+
|
|
3
|
+
Official Python bindings for **RustO!** — a high-performance, pure-Rust OCR engine powered by Indra OCR, PaddleOCR, and the pure-Rust MNN (`rusto-nn`) inference runtime via UniFFI.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero-Config PP-OCRv6 Tiny Default**: `RustO.initialize()` automatically downloads and caches the default `ppocrv6-tiny` (FP32, Apache 2.0) models on first run from Hugging Face Hub.
|
|
8
|
+
- **Context Manager Support**: Deterministic native resource cleanup with `with RustO.initialize() as engine:` or `engine.close()`.
|
|
9
|
+
- **Full Document Pipeline**: Neural document unwarping (`with_auto_unwarp()`), page orientation detection (`with_orientation_detection()`), semantic layout analysis, and reading-order recovery (`with_layout_reading()`).
|
|
10
|
+
- **Fluent Builder API**: Chain `.as_lines()`, `.as_words()`, `.as_spatial()`, `.as_tsv()`, or `.with_output(...)` into a single `.run()` terminal method.
|
|
11
|
+
- **Zero-Copy Raw RGB Support**: Feed NumPy / OpenCV / Pillow RGB8 buffers directly via `.rgb(pixels, width, height, stride)`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install rusto
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from rusto import RustO
|
|
23
|
+
|
|
24
|
+
# Zero-config initialization (auto-downloads PP-OCRv6 Tiny FP32 models from Hugging Face Hub if not cached)
|
|
25
|
+
with RustO.initialize() as engine:
|
|
26
|
+
result = (
|
|
27
|
+
engine.detect_text("invoice.png")
|
|
28
|
+
.with_auto_unwarp()
|
|
29
|
+
.with_layout_reading()
|
|
30
|
+
.as_lines()
|
|
31
|
+
.run()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if result.is_STRUCTURED():
|
|
35
|
+
for item in result.results:
|
|
36
|
+
print(
|
|
37
|
+
f"[{item.score:.2f}] ({item.layout_type or 'line'} #{item.region_id}) {item.text}"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Reconstructed 2D spatial text layout
|
|
41
|
+
spatial = engine.detect_text("invoice.png").as_spatial().run()
|
|
42
|
+
if spatial.is_SPATIAL():
|
|
43
|
+
print(spatial.text)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also manage the engine lifecycle explicitly without a `with` block:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from rusto import InitializeConfig, RustO
|
|
50
|
+
|
|
51
|
+
engine = RustO.initialize(InitializeConfig.indra())
|
|
52
|
+
try:
|
|
53
|
+
result = engine.detect_text("invoice.png").as_words().run()
|
|
54
|
+
if result.is_STRUCTURED():
|
|
55
|
+
for word in result.results:
|
|
56
|
+
print(word.text, word.frame)
|
|
57
|
+
finally:
|
|
58
|
+
engine.close()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Custom Configuration & Offline Models (.mnn / .rten)
|
|
62
|
+
|
|
63
|
+
Select canonical model tiers (`"indraocr"`, `"ppocrv6-tiny"`, `"ppocrv6-small"`, `"ppocrv5-mobile"`, `"ppocrv4-mobile"`), languages, or local model paths:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from rusto import (
|
|
67
|
+
DetectionConfig,
|
|
68
|
+
InitializeConfig,
|
|
69
|
+
LayoutConfig,
|
|
70
|
+
RecognitionConfig,
|
|
71
|
+
RustO,
|
|
72
|
+
UnwarpConfig,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Preset by canonical model + language (auto-downloaded to cache)
|
|
76
|
+
cfg = InitializeConfig.for_model("ppocrv6-tiny", lang="latin")
|
|
77
|
+
engine = RustO.initialize(cfg)
|
|
78
|
+
engine.close()
|
|
79
|
+
|
|
80
|
+
# Explicit local assets for offline environments
|
|
81
|
+
offline_cfg = InitializeConfig(
|
|
82
|
+
template="indra",
|
|
83
|
+
enable_reading_order=True,
|
|
84
|
+
detection=DetectionConfig(enabled=True, model_path="./models/indra_det.mnn"),
|
|
85
|
+
recognition=RecognitionConfig(
|
|
86
|
+
enabled=True,
|
|
87
|
+
model_path="./models/indra_rec.mnn",
|
|
88
|
+
dict_path="./models/indra_meta.json",
|
|
89
|
+
),
|
|
90
|
+
unwarp=UnwarpConfig(enabled=True, model_path="./models/uvdoc.mnn"),
|
|
91
|
+
layout=LayoutConfig(enabled=True, model_path="./models/layout_cdla.mnn"),
|
|
92
|
+
)
|
|
93
|
+
with RustO.initialize(offline_cfg) as offline_engine:
|
|
94
|
+
res = offline_engine.detect_text("scan.png").with_auto_unwarp().as_lines().run()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Pre-download models into a target cache directory ahead of time (e.g., during Docker image build):
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from rusto import download_model
|
|
101
|
+
|
|
102
|
+
paths = download_model(model="ppocrv6-tiny", cache_dir="./models-cache")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Input Sources & Raw RGB Buffers
|
|
106
|
+
|
|
107
|
+
Pass a file path (`str` or `Path`), encoded image `bytes`, `ImageSource`, or raw interleaved 24-bit RGB pixels:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from pathlib import Path
|
|
111
|
+
import numpy as np
|
|
112
|
+
from rusto import RustO
|
|
113
|
+
|
|
114
|
+
with RustO.initialize() as engine:
|
|
115
|
+
# 1. Encoded PNG/JPEG bytes
|
|
116
|
+
png_bytes = Path("invoice.png").read_bytes()
|
|
117
|
+
res_bytes = engine.detect_text(png_bytes).as_lines().run()
|
|
118
|
+
|
|
119
|
+
# 2. Raw HxWx3 uint8 RGB array (e.g. from Pillow / imageio)
|
|
120
|
+
rgb_array = np.zeros((720, 1280, 3), dtype=np.uint8)
|
|
121
|
+
height, width, _ = rgb_array.shape
|
|
122
|
+
stride = width * 3
|
|
123
|
+
|
|
124
|
+
res_rgb = (
|
|
125
|
+
engine.detect_text()
|
|
126
|
+
.rgb(rgb_array.tobytes(), width, height, stride)
|
|
127
|
+
.as_lines()
|
|
128
|
+
.run()
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 6-Phase Pipeline Options
|
|
133
|
+
|
|
134
|
+
Customize every phase of OCR execution per request:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from rusto import (
|
|
138
|
+
DetectionRunOptions,
|
|
139
|
+
OutputGranularity,
|
|
140
|
+
PostDetectionOptions,
|
|
141
|
+
PostRecognitionOptions,
|
|
142
|
+
PreDetectionOptions,
|
|
143
|
+
PreRecognitionOptions,
|
|
144
|
+
RecognitionRunOptions,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
result = (
|
|
148
|
+
engine.detect_text("document.jpg")
|
|
149
|
+
.with_orientation_detection()
|
|
150
|
+
.with_auto_unwarp()
|
|
151
|
+
.with_layout_reading()
|
|
152
|
+
.with_pre_detection(PreDetectionOptions(descreen_strength=0.3, target_max_side=1280))
|
|
153
|
+
.with_detection(DetectionRunOptions(limit_side_len=960, limit_type="max"))
|
|
154
|
+
.with_post_detection(PostDetectionOptions(threshold=0.3, box_threshold=0.6, unclip_ratio=1.6))
|
|
155
|
+
.with_pre_recognition(PreRecognitionOptions(line_y_threshold=0.5, word_x_threshold=0.4))
|
|
156
|
+
.with_recognition(RecognitionRunOptions(batch_size=8))
|
|
157
|
+
.with_post_recognition(PostRecognitionOptions(score_threshold=0.5))
|
|
158
|
+
.with_output(OutputGranularity.LINES)
|
|
159
|
+
.run()
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Result Reference
|
|
164
|
+
|
|
165
|
+
`DetectTextResult` is a tagged union returned by `.run()`:
|
|
166
|
+
|
|
167
|
+
- `result.is_STRUCTURED()` -> `True` for `.as_lines()` and `.as_words()`. Access `result.results` (`list[TextResult]`):
|
|
168
|
+
- `item.text` (`str`): Recognized text string.
|
|
169
|
+
- `item.score` (`float`): Confidence score (`0.0` – `1.0`).
|
|
170
|
+
- `item.box_points` (`list[Point]`): 4 polygon vertices (`x`, `y`).
|
|
171
|
+
- `item.frame` (`Frame`): Axis-aligned bounding box (`width`, `height`, `top`, `left`).
|
|
172
|
+
- `item.region_id` (`int | None`): Layout region index when `with_layout_reading()` is active.
|
|
173
|
+
- `item.layout_type` (`LayoutType | None`): Semantic region category (`TEXT`, `TITLE`, `TABLE`, `HEADER`, `FOOTER`, `FIGURE`, etc.).
|
|
174
|
+
- `result.is_SPATIAL()` -> `True` for `.as_spatial()`. Access `result.text` (`str`).
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
Apache-2.0 OR MIT
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rusto"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Pure Rust OCR library for Python based on RapidOCR and
|
|
7
|
+
version = "0.4.1"
|
|
8
|
+
description = "Pure Rust OCR library for Python based on RapidOCR, Indra OCR, and pure-Rust MNN (rusto-nn)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "RustO Contributors", email = "contact@rusto.dev" }]
|
|
11
11
|
license = "MIT"
|