senpy-linter 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.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: senpy-linter
3
+ Version: 0.1.0
4
+ Summary: Fast semantic linter for Ren'Py
5
+ Author: Your Name
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: rich>=13.0.0
8
+ Requires-Dist: pygls>=1.3.0
@@ -0,0 +1,219 @@
1
+ [English](README.md) | [Русский](README_ru.md)
2
+
3
+ # Senpy (`senpy-linter`)
4
+
5
+ <p align="center">
6
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue?logo=python&logoColor=white" alt="Python 3.10+">
7
+ <img src="https://img.shields.io/badge/Ren'Py-6.99%20%7C%207%20%7C%208-ff69b4?logo=renpy&logoColor=white" alt="Ren'Py 6.99 / 7 / 8">
8
+ <img src="https://img.shields.io/badge/Speed-77k%20lines%20in%200.3s-brightgreen" alt="Blazingly Fast">
9
+ <img src="https://img.shields.io/badge/License-MIT-green" alt="License: MIT">
10
+ <img src="https://img.shields.io/badge/Code%20Style-Ruff-000000.svg" alt="Ruff">
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="README.md"><b>English</b></a> | <a href="README.ru.md"><b>Русский</b></a>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <b>"Notice me, Senpy! (⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄)"</b><br>
19
+ <i>Blazingly fast semantic linter, control-flow analyzer, and personal tsundere for Ren'Py visual novels.</i>
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## Why Senpy?
25
+
26
+ The standard built-in `renpy lint` only checks for basic syntax and broken labels. It is **completely blind** to runtime Python execution bugs. As a result, developers and QA teams often discover catastrophic `NameError` or `KeyError` crashes only after 40 hours of manual playtesting.
27
+
28
+ **Senpy** bridges this gap:
29
+
30
+ * **Blazingly Fast:** Analyzes **77,000+ lines across 50 files in ~0.3 seconds** *(tested on DDLC: Blue Skies, warm SSD cache)*.
31
+ * **Intra-Menu Flow Analysis:** Traces `menu:` choices to catch variables defined in one branch but missing in another.
32
+ * **Save Game Protection:** Enforces state management rules, warning about variables assigned without a top-level `default`.
33
+ * **Screen Safety Guard:** Prevents dangerous state mutations inside reactive screens that cause lag, redraw loops, and broken rollbacks.
34
+ * **Zero-Footprint `.rpyc` Sandbox:** Automatically decompiles compiled `.rpyc` files into an ephemeral temporary sandbox without cluttering your game folder.
35
+ * **Safe Auto-Fixing (`--fix`):** Automatically patches deterministic syntax oversights like unclosed text tags.
36
+ * **Tsundere Mode (`--tsundere`):** Provides colorful, anime-style diagnostic feedback for your visual novel.
37
+
38
+ ---
39
+
40
+ ## Installation
41
+
42
+ ### Option 1: From PyPI
43
+
44
+ ```bash
45
+ pip install senpy-linter
46
+ ```
47
+
48
+ ### Option 2: From Source (Development Mode)
49
+
50
+ ```bash
51
+ git clone https://github.com/Xilleet/senpy.git
52
+ cd senpy
53
+ pip install -e .
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ Run Senpy from anywhere in your terminal by passing the path to your Ren'Py game folder:
59
+
60
+ ```bash
61
+ # Analyze a Ren'Py game directory
62
+ senpy "path/to/game"
63
+
64
+ # Automatically apply safe fixes (e.g. unclosed dialogue tags)
65
+ senpy "path/to/game" --fix
66
+
67
+ # Ignore specific error codes
68
+ senpy "path/to/game" --ignore RPY104 RPY303
69
+
70
+ # Auto-decompile orphaned .rpyc files into a temporary sandbox
71
+ senpy "path/to/game" --decompile
72
+
73
+ # Limit the number of displayed error cards
74
+ senpy "path/to/game" --max-errors 30
75
+ ```
76
+
77
+ ## Diagnostics & Rules Reference
78
+
79
+ | Code | Category | Severity | Description |
80
+ |---|---|---|---|
81
+ | `RPY101` | Variables | 🔴 Error | Variable in text interpolation `[var]` is not declared via `default` or `define` (features fuzzy “did you mean?” suggestions). |
82
+ | `RPY102` | Python | 💥 Critical | Augmented assignment (`$ var += 1`) before declaration via `default` (guaranteed runtime `NameError`). |
83
+ | `RPY103` | Logic | 💥 Critical | Undeclared variable evaluated in an `if` / `elif` condition. |
84
+ | `RPY104` | State | ⚠️ Warning | Variable dynamically created via `$` without a top-level `default` (breaks save/load compatibility across patches). |
85
+ | `RPY201` | Screens | 💥 Critical | Direct state mutation inside a screen (causes lag, prediction loops, and breaks rollback). |
86
+ | `RPY202` | Screens | 🔴 Error | Screen accesses variable without a default (causes crash in the main menu or before game start). |
87
+ | `RPY301` | Dialogue | ⚠️ Warning | Unclosed or mismatched text tags (`{b}`, `{color}`, `{size}`) in dialogue. Fixable via `--fix`. |
88
+ | `RPY302` | Story | 💥 Critical | Jump or call to a non-existent label (causes `ScriptError`). |
89
+ | `RPY303` | Dead Code | ℹ️ Notice | Label is declared but never jumped to or called anywhere in the project. |
90
+ | `RPY304` | Formatting | 🔴 Error | Malformed hex color (e.g. `{#12345}`) or unknown color name in a `{color}` tag. |
91
+ | `RPY401` | Flow Graph | 💥 Critical | Variable is defined only in specific `menu:` choices but missing in others (crashes when the player takes an uninitialized path). |
92
+ | `RPY501` | Assets | 🔴 Error | Audio file referenced by `play music` or `play sound` is not found on disk or inside `.rpa` archives. |
93
+
94
+ ## Examples in Action
95
+
96
+ ### 1. Control-Flow Branching Crash (`RPY401`)
97
+
98
+ ```renpy
99
+ label dungeon:
100
+ menu:
101
+ "Take the torch":
102
+ $ has_torch = True
103
+ "You grabbed a torch."
104
+
105
+ "Walk in darkness":
106
+ "You decided to risk it."
107
+
108
+ # Merged path:
109
+ if has_torch: # <-- RPY401 triggered!
110
+ "The light guides your way."
111
+ ```
112
+
113
+ **Senpy output:**
114
+
115
+ ```text
116
+ [WARNING] RPY401: Variable 'has_torch' is only defined in menu choice 'Take the torch',
117
+ but missing in 'Walk in darkness'. When choosing 'Walk in darkness', this condition will crash with NameError.
118
+ ```
119
+
120
+ ### 2. Screen Mutation Anti-Pattern (`RPY201`)
121
+
122
+ ```renpy
123
+ screen shop():
124
+ $ player_gold -= 10 # <-- RPY201 triggered!
125
+ ```
126
+
127
+ **Senpy output:**
128
+
129
+ ```text
130
+ Error [RPY201]: Direct state mutation 'player_gold' inside screen 'shop'.
131
+ Screens are evaluated predictively and continuously! Use screen Actions (e.g. SetVariable) instead.
132
+ ```
133
+
134
+ ### 3. Safe Auto-Fixing (`--fix` for `RPY301`)
135
+
136
+ **Before running `senpy --fix`:**
137
+
138
+ ```renpy
139
+ "Attention: {b}danger zone!"
140
+ ```
141
+
142
+ **After running `senpy --fix`:**
143
+
144
+ ```renpy
145
+ "Attention: {b}danger zone!{/b}"
146
+ ```
147
+
148
+ **Tsundere Mode (--tsundere)**
149
+ ```bash
150
+ senpy path/to/game --tsundere
151
+ ```
152
+
153
+ ```text
154
+ (¬_¬ ) «П-подумаешь! Сканирую твою директорию: game/... Н-не то чтобы мне был интересен твой сценарий, бака!»
155
+
156
+ ┌────────────────────────────────────────────────────────────────────────┐
157
+ │ game/gallery.rpy:81 │
158
+ │ 81 │ $ start = gallery_page * maxperpage │
159
+ │ │
160
+ │ (╯°□°)╯︵ ┻━┻ «Ты меняешь переменную ПРЯМО ВНУТРИ ЭКРАНА?! │
161
+ │ Экраны так не работают, тупица!» │
162
+ │ [WARNING] RPY201: Direct state mutation 'start' inside screen. │
163
+ └────────────────────────────────────────────────────────────────────────┘
164
+
165
+ (⁄ ⁄>⁄ ▽ ⁄<⁄ ⁄) «Я... я закрыла за тобой 3 открытых тега через --fix! Н-но не думай, что ты мне нравишься!»
166
+ ```
167
+
168
+ ## Configuration (`senpy.toml`)
169
+
170
+ Place a `senpy.toml` file in your game directory or workspace root to customize rules without passing CLI arguments:
171
+
172
+ ```toml
173
+ [tool.senpy]
174
+ # Automatically decompile compiled .rpyc files into a temporary RAM sandbox
175
+ decompile = true
176
+
177
+ # Maximum error cards rendered in the console
178
+ max_errors = 50
179
+
180
+ # Ignore rules that don't match your workflow (e.g. dynamic routers)
181
+ ignore = [
182
+ "RPY104", # Ignore dynamic assignments
183
+ "RPY303", # Ignore unreachable labels
184
+ ]
185
+ ```
186
+
187
+ ### Inline Suppression (`# noqa`)
188
+
189
+ You can suppress any diagnostic on a specific line using the standard `# noqa` syntax:
190
+
191
+ ```renpy
192
+ "Warning: {b}unclosed style" # noqa
193
+ $ dynamic_var = 1 # noqa: RPY104
194
+ ```
195
+
196
+ ## Zero-Footprint `.rpyc` Decompilation
197
+
198
+ When analyzing compiled distributions or mods, source `.rpy` files are often missing. Senpy detects orphaned `.rpyc` files and can decompile them on the fly:
199
+
200
+ 1. Copies `.rpyc` files into a temporary OS sandbox directory (`%TEMP%` on Windows).
201
+ 2. If `unrpyc` is not installed on your system, Senpy offers to download it automatically from GitHub into `~/.senpy/tools/`.
202
+ 3. Runs analysis on the decompiled scripts in the temporary sandbox.
203
+ 4. Cleans up and deletes the temporary sandbox immediately upon completion. Your game directory remains clean.
204
+
205
+ ## Contributing
206
+
207
+ Contributions, bug reports, and rule suggestions are welcome!
208
+
209
+ Feel free to open an issue or submit a pull request on GitHub.
210
+
211
+ 1. Fork the project.
212
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
213
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
214
+ 4. Push to the branch (`git push origin feature/AmazingFeature`).
215
+ 5. Open a pull request.
216
+
217
+ ## License
218
+
219
+ Distributed under the MIT License. Free for commercial and non-commercial visual novel development.
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "senpy-linter"
7
+ version = "0.1.0"
8
+ description = "Fast semantic linter for Ren'Py"
9
+ authors = [{ name = "Your Name" }]
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "rich>=13.0.0",
13
+ "pygls>=1.3.0",
14
+ ]
15
+
16
+ [project.scripts]
17
+ senpy = "senpy.cli:main"
18
+
19
+ [tool.setuptools.packages.find]
20
+ include = ["senpy"]
21
+ exclude = ["senpy-vscode", "senpy-vscode.*", "test_game"]
File without changes