ster 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.
Files changed (51) hide show
  1. ster-0.1.0/.github/workflows/ci.yml +98 -0
  2. ster-0.1.0/.github/workflows/publish.yml +48 -0
  3. ster-0.1.0/.gitignore +11 -0
  4. ster-0.1.0/.pre-commit-config.yaml +21 -0
  5. ster-0.1.0/LICENSE +21 -0
  6. ster-0.1.0/PKG-INFO +307 -0
  7. ster-0.1.0/README.md +253 -0
  8. ster-0.1.0/pyproject.toml +151 -0
  9. ster-0.1.0/ster/__init__.py +1 -0
  10. ster-0.1.0/ster/cli.py +1608 -0
  11. ster-0.1.0/ster/display.py +180 -0
  12. ster-0.1.0/ster/exceptions.py +52 -0
  13. ster-0.1.0/ster/git_log.py +792 -0
  14. ster-0.1.0/ster/git_log_logic.py +201 -0
  15. ster-0.1.0/ster/git_manager.py +814 -0
  16. ster-0.1.0/ster/handles.py +60 -0
  17. ster-0.1.0/ster/help.py +116 -0
  18. ster-0.1.0/ster/html_export.py +199 -0
  19. ster-0.1.0/ster/model.py +170 -0
  20. ster-0.1.0/ster/nav.py +3668 -0
  21. ster-0.1.0/ster/nav_logic.py +583 -0
  22. ster-0.1.0/ster/nav_state.py +390 -0
  23. ster-0.1.0/ster/operations.py +410 -0
  24. ster-0.1.0/ster/project.py +115 -0
  25. ster-0.1.0/ster/store.py +298 -0
  26. ster-0.1.0/ster/taxonomy.ttl +23 -0
  27. ster-0.1.0/ster/test-ontology.txt +90 -0
  28. ster-0.1.0/ster/validator.py +167 -0
  29. ster-0.1.0/ster/wind-bigood.ttl +11 -0
  30. ster-0.1.0/ster/wizard.py +259 -0
  31. ster-0.1.0/ster/workspace.py +144 -0
  32. ster-0.1.0/ster/workspace_ops.py +108 -0
  33. ster-0.1.0/tests/__init__.py +0 -0
  34. ster-0.1.0/tests/conftest.py +110 -0
  35. ster-0.1.0/tests/test_cli.py +569 -0
  36. ster-0.1.0/tests/test_display.py +182 -0
  37. ster-0.1.0/tests/test_git_log.py +550 -0
  38. ster-0.1.0/tests/test_git_log_logic.py +367 -0
  39. ster-0.1.0/tests/test_git_manager.py +474 -0
  40. ster-0.1.0/tests/test_handles.py +115 -0
  41. ster-0.1.0/tests/test_help.py +133 -0
  42. ster-0.1.0/tests/test_html_export.py +338 -0
  43. ster-0.1.0/tests/test_model.py +116 -0
  44. ster-0.1.0/tests/test_nav.py +976 -0
  45. ster-0.1.0/tests/test_nav_logic.py +361 -0
  46. ster-0.1.0/tests/test_nav_state.py +446 -0
  47. ster-0.1.0/tests/test_operations.py +507 -0
  48. ster-0.1.0/tests/test_store.py +292 -0
  49. ster-0.1.0/tests/test_wizard.py +177 -0
  50. ster-0.1.0/tests/test_workspace.py +400 -0
  51. ster-0.1.0/uv.lock +1280 -0
@@ -0,0 +1,98 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["master", "main"]
6
+ pull_request:
7
+ branches: ["master", "main"]
8
+
9
+ # Cancel any in-progress run for the same branch / PR
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ # ── Jobs run in parallel; 'test' is the only matrix job ───────────────────────
15
+
16
+ jobs:
17
+
18
+ # ── 1. Lint & format ────────────────────────────────────────────────────────
19
+ lint:
20
+ name: Lint (ruff)
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: astral-sh/setup-uv@v5
26
+
27
+ - name: Install ruff
28
+ run: uv tool install ruff
29
+
30
+ - name: ruff — check rules
31
+ run: ruff check .
32
+
33
+ - name: ruff — check formatting
34
+ run: ruff format --check .
35
+
36
+ # ── 2. Static type checking ──────────────────────────────────────────────────
37
+ typecheck:
38
+ name: Type check (mypy)
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+
43
+ - uses: astral-sh/setup-uv@v5
44
+
45
+ - name: Install deps
46
+ run: uv sync --extra html --extra dev
47
+
48
+ - name: mypy
49
+ run: uv run mypy ster/
50
+
51
+ # ── 3. Security ──────────────────────────────────────────────────────────────
52
+ security:
53
+ name: Security (bandit + pip-audit)
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - uses: astral-sh/setup-uv@v5
59
+
60
+ - name: Install deps
61
+ run: uv sync --extra html --extra dev
62
+
63
+ - name: bandit — static analysis
64
+ run: uv run bandit -r ster/ -c pyproject.toml
65
+
66
+ - name: pip-audit — dependency CVEs
67
+ run: uv run pip-audit
68
+
69
+ # ── 4. Tests + coverage ──────────────────────────────────────────────────────
70
+ test:
71
+ name: Test (Python ${{ matrix.python-version }})
72
+ runs-on: ubuntu-latest
73
+ strategy:
74
+ fail-fast: false
75
+ matrix:
76
+ python-version: ["3.11", "3.12", "3.13"]
77
+
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+
81
+ - uses: astral-sh/setup-uv@v5
82
+ with:
83
+ python-version: ${{ matrix.python-version }}
84
+
85
+ - name: Install deps
86
+ run: uv sync --extra html --extra dev
87
+
88
+ - name: Run tests with coverage
89
+ run: uv run pytest --tb=short -q --cov=ster --cov-report=xml --cov-report=term-missing
90
+
91
+ - name: Upload coverage to Codecov
92
+ # Upload only once (latest Python) to avoid duplicate reports
93
+ if: matrix.python-version == '3.13'
94
+ uses: codecov/codecov-action@v4
95
+ with:
96
+ files: coverage.xml
97
+ fail_ci_if_error: false # don't break CI if Codecov is unreachable
98
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,48 @@
1
+ name: Publish to PyPI
2
+
3
+ # Trigger: push a tag like v0.1.0
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ build:
11
+ name: Build distribution
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v5
18
+
19
+ - name: Build sdist and wheel
20
+ run: uv build
21
+
22
+ - name: Store distribution packages
23
+ uses: actions/upload-artifact@v4
24
+ with:
25
+ name: python-package-distributions
26
+ path: dist/
27
+
28
+ publish-to-pypi:
29
+ name: Publish to PyPI
30
+ needs: build
31
+ runs-on: ubuntu-latest
32
+
33
+ # Trusted Publisher — configure once on pypi.org, no token secret needed
34
+ environment:
35
+ name: pypi
36
+ url: https://pypi.org/p/ster
37
+ permissions:
38
+ id-token: write # required for OIDC
39
+
40
+ steps:
41
+ - name: Download distribution packages
42
+ uses: actions/download-artifact@v4
43
+ with:
44
+ name: python-package-distributions
45
+ path: dist/
46
+
47
+ - name: Publish to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
ster-0.1.0/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .coverage
10
+ htmlcov/
11
+ *.egg
@@ -0,0 +1,21 @@
1
+ # Pre-commit hooks — mirrors the CI lint job so issues are caught locally.
2
+ # Install once: pip install pre-commit && pre-commit install
3
+ # Run manually: pre-commit run --all-files
4
+
5
+ repos:
6
+ - repo: https://github.com/astral-sh/ruff-pre-commit
7
+ rev: v0.4.4
8
+ hooks:
9
+ - id: ruff # linting
10
+ args: [--fix]
11
+ - id: ruff-format # formatting
12
+
13
+ - repo: https://github.com/pre-commit/pre-commit-hooks
14
+ rev: v4.6.0
15
+ hooks:
16
+ - id: trailing-whitespace
17
+ - id: end-of-file-fixer
18
+ - id: check-yaml
19
+ - id: check-toml
20
+ - id: check-merge-conflict
21
+ - id: debug-statements # catch leftover breakpoint() / pdb calls
ster-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 gbelbe
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.
ster-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: ster
3
+ Version: 0.1.0
4
+ Summary: Interactive terminal editor for SKOS taxonomy files
5
+ Project-URL: Homepage, https://github.com/gbelbe/ster
6
+ Project-URL: Repository, https://github.com/gbelbe/ster
7
+ Project-URL: Issues, https://github.com/gbelbe/ster/issues
8
+ License: MIT License
9
+
10
+ Copyright (c) 2024 gbelbe
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Requires-Python: >=3.11
31
+ Requires-Dist: rdflib>=7.0
32
+ Requires-Dist: rich>=13.0
33
+ Requires-Dist: typer>=0.12
34
+ Provides-Extra: all
35
+ Requires-Dist: bandit[toml]>=1.7; extra == 'all'
36
+ Requires-Dist: coverage[toml]>=7.0; extra == 'all'
37
+ Requires-Dist: mypy>=1.10; extra == 'all'
38
+ Requires-Dist: pip-audit>=2.7; extra == 'all'
39
+ Requires-Dist: pylode>=3.0; extra == 'all'
40
+ Requires-Dist: pytest-cov>=5.0; extra == 'all'
41
+ Requires-Dist: pytest>=8.0; extra == 'all'
42
+ Requires-Dist: ruff>=0.4; extra == 'all'
43
+ Provides-Extra: dev
44
+ Requires-Dist: bandit[toml]>=1.7; extra == 'dev'
45
+ Requires-Dist: coverage[toml]>=7.0; extra == 'dev'
46
+ Requires-Dist: mypy>=1.10; extra == 'dev'
47
+ Requires-Dist: pip-audit>=2.7; extra == 'dev'
48
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
49
+ Requires-Dist: pytest>=8.0; extra == 'dev'
50
+ Requires-Dist: ruff>=0.4; extra == 'dev'
51
+ Provides-Extra: html
52
+ Requires-Dist: pylode>=3.0; extra == 'html'
53
+ Description-Content-Type: text/markdown
54
+
55
+ # ster
56
+
57
+ [![CI](https://github.com/gbelbe/ster/actions/workflows/ci.yml/badge.svg)](https://github.com/gbelbe/ster/actions/workflows/ci.yml)
58
+ [![codecov](https://codecov.io/gh/gbelbe/ster/graph/badge.svg)](https://codecov.io/gh/gbelbe/ster)
59
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
60
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
61
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
62
+
63
+ [![rdflib](https://img.shields.io/pypi/v/rdflib?label=rdflib&color=orange)](https://pypi.org/project/rdflib/)
64
+ [![typer](https://img.shields.io/pypi/v/typer?label=typer&color=brightgreen)](https://pypi.org/project/typer/)
65
+ [![rich](https://img.shields.io/pypi/v/rich?label=rich&color=purple)](https://pypi.org/project/rich/)
66
+ [![pylode](https://img.shields.io/pypi/v/pylode?label=pylode+%5Boptional%5D&color=blue)](https://pypi.org/project/pylode/)
67
+
68
+ ```
69
+ _____ ______ ______ ____
70
+ / ___//_ __// ____// __ \
71
+ \__ \ / / / __/ / /_/ /
72
+ ___/ / / / / /___ / _, _/
73
+ /____/ /_/ /_____//_/ |_|
74
+
75
+ [ Breton: "Meaning" or "Sense" ]
76
+ [ Simple Taxonomy EditoR ]
77
+ ```
78
+
79
+ **ster** is an interactive terminal editor for [SKOS](https://www.w3.org/TR/skos-reference/) taxonomy files.
80
+ Browse, create, and edit concepts in a full-screen TUI — no GUI, no database, just clean Turtle files.
81
+
82
+ > *ster* is the Breton word for *meaning*, with homonyms for *river* and *star*.
83
+ > Let it guide your semantic voyage, keeping the flow and always following your star.
84
+
85
+ ---
86
+
87
+ ## Features
88
+
89
+ ### Interactive TUI
90
+ - Full-screen tree browser with keyboard navigation
91
+ - Inline concept creation, renaming, deletion, and label editing
92
+ - Detail panel: view and edit all SKOS fields (labels, definitions, scope notes, related links…)
93
+ - Fold / unfold subtrees; shows hidden-concept count
94
+ - Visual `⇔` indicator for concepts that carry cross-scheme mapping links
95
+
96
+ ### Multi-file workspace
97
+ - Open several `.ttl` files at once and see a merged taxonomy view
98
+ - Edits are always written to the correct source file automatically
99
+
100
+ ### Cross-scheme mapping
101
+ - Add `exactMatch`, `closeMatch`, `broadMatch`, `narrowMatch`, `relatedMatch` links between concepts in different files
102
+ - Remove links from the detail view — works even when the target file has been deleted
103
+ - Both source and target files are saved and staged in git on every change
104
+
105
+ ### Git integration
106
+ - Stage, commit, and push changes without leaving the terminal
107
+ - Browse full commit history with diffs inside the TUI
108
+
109
+ ### HTML export
110
+ - Generate a browsable, wiki-style HTML page from any taxonomy via [pyLODE](https://github.com/RDFLib/pyLODE)
111
+ - One HTML file per language detected in the taxonomy
112
+ - Sticky language-switcher bar links between language versions
113
+ - Available from the main menu or `ster export`
114
+
115
+ ### Other
116
+ - Step-by-step **init wizard** (`ster init`)
117
+ - Auto-detection of taxonomy files in the current directory
118
+ - Round-trip safe: reads and writes `.ttl`, `.rdf`, `.jsonld`
119
+ - SKOS integrity validation
120
+
121
+ ---
122
+
123
+ ## Installation
124
+
125
+ ### Minimal (TUI + editing)
126
+
127
+ ```bash
128
+ pip install ster
129
+ ```
130
+
131
+ ### With HTML export
132
+
133
+ ```bash
134
+ pip install "ster[html]"
135
+ ```
136
+
137
+ ### From source
138
+
139
+ ```bash
140
+ git clone https://github.com/gbelbe/ster.git
141
+ cd ster
142
+ pip install -e . # core only
143
+ pip install -e ".[html]" # with HTML export
144
+ pip install -e ".[dev]" # with test suite
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Dependencies
150
+
151
+ | Group | Package | Purpose |
152
+ |---|---|---|
153
+ | core | `rdflib>=7.0` | RDF parsing and serialisation |
154
+ | core | `typer[all]>=0.12` | CLI framework |
155
+ | core | `rich>=13.0` | Terminal rendering, prompts, tables |
156
+ | `[html]` | `pylode>=3.0` | HTML generation from SKOS (VocPub profile) |
157
+ | `[dev]` | `pytest>=8.0` | Test suite |
158
+ | `[dev]` | `pytest-cov>=5.0` | Coverage reporting |
159
+
160
+ pyLODE is **not** installed by default. When you trigger an HTML export, ster will offer to install it automatically.
161
+
162
+ ---
163
+
164
+ ## Quick start
165
+
166
+ ### Launch the interactive editor
167
+
168
+ ```bash
169
+ ster
170
+ ```
171
+
172
+ The home screen lists all taxonomy files in the current directory. Use arrow keys to check files, then press **Enter** to open them.
173
+
174
+ ```
175
+ ┌─────────────────────────────────────────────────────┐
176
+ │ [ ] equipement.ttl 7 concepts │
177
+ │ [x] windvane-taxonomy.ttl 23 concepts │
178
+ └─────────────────────────────────────────────────────┘
179
+ ↵ Open checked files
180
+ ⎇ Browse git history
181
+ 🌐 Generate webpage
182
+ + Create new taxonomy
183
+ ✕ Quit
184
+ ```
185
+
186
+ ### Keyboard shortcuts (TUI)
187
+
188
+ | Key | Action |
189
+ |---|---|
190
+ | `↑` `↓` | Navigate tree / fields |
191
+ | `Enter` | Expand/collapse node or open detail |
192
+ | `a` | Add a child concept |
193
+ | `A` | Add a top-level concept |
194
+ | `d` | Delete selected concept |
195
+ | `e` | Edit selected field in detail panel |
196
+ | `m` | Add a mapping link to another concept |
197
+ | `g` | Commit & push changes |
198
+ | `?` | Help screen |
199
+ | `q` / `Esc` | Back / quit |
200
+
201
+ ### Create a new taxonomy
202
+
203
+ ```bash
204
+ ster init my-taxonomy.ttl
205
+ ```
206
+
207
+ The wizard walks you through name, description, base URI, languages, and author.
208
+
209
+ ### Export to HTML
210
+
211
+ ```bash
212
+ ster export my-taxonomy.ttl # generates ./html/my-taxonomy_en.html …
213
+ ster export my-taxonomy.ttl -l en,fr # specific languages only
214
+ ster export my-taxonomy.ttl -o /tmp # custom output directory
215
+ ```
216
+
217
+ Or use the **🌐 Generate webpage** option from the main menu.
218
+
219
+ ### Validate
220
+
221
+ ```bash
222
+ ster validate my-taxonomy.ttl
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Architecture
228
+
229
+ ```
230
+ ster/
231
+ ├── model.py — Pure dataclasses: Concept, ConceptScheme, Taxonomy
232
+ ├── store.py — RDF I/O via rdflib (.ttl / .rdf / .jsonld)
233
+ ├── operations.py — All SKOS mutations (add, remove, move, relate…)
234
+ ├── workspace.py — Multi-file workspace: merged view + per-file saves
235
+ ├── workspace_ops.py — Cross-file mapping operations
236
+ ├── nav.py — Full-screen TUI (curses): tree, detail, inline edit
237
+ ├── cli.py — Typer entry-points (ster, ster init, ster export…)
238
+ ├── html_export.py — HTML generation via pyLODE VocPub
239
+ ├── git_manager.py — Git staging, commit, push
240
+ ├── git_log.py — Git history browser (TUI)
241
+ ├── wizard.py — Init wizard
242
+ ├── handles.py — Short handle generation from camelCase URIs
243
+ └── validator.py — SKOS integrity checks
244
+ ```
245
+
246
+ Each layer depends only on the layers below it, keeping every module independently testable.
247
+
248
+ ---
249
+
250
+ ## Supported formats
251
+
252
+ | Extension | Format |
253
+ |---|---|
254
+ | `.ttl` | Turtle (recommended) |
255
+ | `.rdf` / `.xml` | RDF/XML |
256
+ | `.jsonld` / `.json` | JSON-LD |
257
+
258
+ ---
259
+
260
+ ## Development
261
+
262
+ ```bash
263
+ pip install -e ".[dev]"
264
+ pytest
265
+ pytest --cov=ster --cov-report=term-missing
266
+ ```
267
+
268
+ ---
269
+
270
+ ## CI / CD
271
+
272
+ Every push and pull request runs four parallel jobs via GitHub Actions:
273
+
274
+ | Job | Tool | What it checks |
275
+ |---|---|---|
276
+ | **Lint** | [ruff](https://docs.astral.sh/ruff/) | Code style, import order, common bugs |
277
+ | **Type check** | [mypy](https://mypy.readthedocs.io/) | Static type correctness |
278
+ | **Security** | [bandit](https://bandit.readthedocs.io/) + [pip-audit](https://pypi.org/project/pip-audit/) | SAST + known CVEs in dependencies |
279
+ | **Tests** | [pytest](https://pytest.org/) × Python 3.11 / 3.12 / 3.13 | Full test suite + coverage report |
280
+
281
+ Coverage is uploaded to [Codecov](https://codecov.io/gh/gbelbe/ster) on every run.
282
+
283
+ ### Run checks locally
284
+
285
+ ```bash
286
+ pip install -e ".[dev]"
287
+
288
+ ruff check . # lint
289
+ ruff format --check . # format
290
+ mypy ster/ # types
291
+ bandit -r ster/ -c pyproject.toml # security
292
+ pip-audit # dependency CVEs
293
+ pytest --cov=ster # tests + coverage
294
+ ```
295
+
296
+ Or install the pre-commit hooks to run ruff automatically on every commit:
297
+
298
+ ```bash
299
+ pip install pre-commit
300
+ pre-commit install
301
+ ```
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT