splatfold 0.2.0__py3-none-any.whl
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,1911 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splatfold
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Fold local Python wildcard imports into one standalone source file.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/WithSofie/splatfold
|
|
7
|
+
Project-URL: Repository, https://github.com/WithSofie/splatfold
|
|
8
|
+
Project-URL: Issues, https://github.com/WithSofie/splatfold/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/WithSofie/splatfold/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: bundler,include,preprocessor,single-file,source-code
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
24
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest<9,>=8; extra == "test"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: coverage[toml]<8,>=7.6; extra == "dev"
|
|
32
|
+
Requires-Dist: mypy<1.19,>=1.14; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest<9,>=8; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.9; extra == "dev"
|
|
35
|
+
Provides-Extra: release
|
|
36
|
+
Requires-Dist: build>=1.2; extra == "release"
|
|
37
|
+
Requires-Dist: check-wheel-contents<1,>=0.6; extra == "release"
|
|
38
|
+
Requires-Dist: packaging>=24.2; extra == "release"
|
|
39
|
+
Requires-Dist: twine>=6; extra == "release"
|
|
40
|
+
Requires-Dist: validate-pyproject>=0.24; extra == "release"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# Splatfold User Manual
|
|
44
|
+
|
|
45
|
+
**Splatfold** is a single-file Python source preprocessor. It gives local
|
|
46
|
+
wildcard imports an additional build-time meaning while keeping the development
|
|
47
|
+
source valid, ordinary Python.
|
|
48
|
+
|
|
49
|
+
> Splat modules open. Fold them into one file.
|
|
50
|
+
|
|
51
|
+
Upgrading from the former Obtuse project? See the
|
|
52
|
+
[migration guide](https://github.com/WithSofie/splatfold/blob/main/MIGRATING.md)
|
|
53
|
+
for command compatibility and intentional safety changes.
|
|
54
|
+
|
|
55
|
+
It recursively expands:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from module import *
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
into the source code of the referenced local Python file, eventually producing a single combined Python file that can be distributed or executed independently.
|
|
63
|
+
|
|
64
|
+
Conceptually, it is similar to the C/C++:
|
|
65
|
+
|
|
66
|
+
```c
|
|
67
|
+
#include "module.c"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
mechanism.
|
|
71
|
+
|
|
72
|
+
However, during development, your project remains a normal multi-file Python project. IDEs, LSPs, autocomplete, static analyzers, type checkers, code navigation, and refactoring tools can continue to understand the project normally.
|
|
73
|
+
|
|
74
|
+
## Installation
|
|
75
|
+
|
|
76
|
+
Splatfold requires Python 3.9 or newer. After the first PyPI release, install it
|
|
77
|
+
with:
|
|
78
|
+
|
|
79
|
+
```zsh
|
|
80
|
+
python3 -m pip install splatfold
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For now, install a local checkout with:
|
|
84
|
+
|
|
85
|
+
```zsh
|
|
86
|
+
python3 -m pip install -e .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The project intentionally keeps its complete implementation in the standalone
|
|
90
|
+
`splatfold.py` module. It has no runtime dependencies outside the Python
|
|
91
|
+
standard library.
|
|
92
|
+
|
|
93
|
+
CI tests Splatfold on CPython 3.9 through 3.14. Splatfold uses the parser from
|
|
94
|
+
the interpreter that runs it, so that interpreter must understand every syntax
|
|
95
|
+
feature used by the input project. It validates and folds source; it does not
|
|
96
|
+
transpile newer Python syntax for older interpreters.
|
|
97
|
+
|
|
98
|
+
## Command-line and Python APIs
|
|
99
|
+
|
|
100
|
+
The preferred command-line form uses a positional input:
|
|
101
|
+
|
|
102
|
+
```zsh
|
|
103
|
+
splatfold main.py -o dist/app.py
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The original `-i` and `--input` spellings remain supported:
|
|
107
|
+
|
|
108
|
+
```zsh
|
|
109
|
+
splatfold -i main.py -o dist/app.py
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Programmatic builds do not write automatically:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from splatfold import build
|
|
116
|
+
|
|
117
|
+
result = build("main.py", output="dist/app.py")
|
|
118
|
+
print(result.included_paths)
|
|
119
|
+
result.write()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This separation makes validation and build-system integration side-effect free
|
|
123
|
+
until the caller explicitly writes the validated result.
|
|
124
|
+
|
|
125
|
+
`build()` always reads, resolves, renders, and compile-checks the complete
|
|
126
|
+
artifact before returning. Its keyword arguments correspond to the CLI's
|
|
127
|
+
resolution, strictness, guard, marker, and tracing options. The returned
|
|
128
|
+
`BuildResult` exposes:
|
|
129
|
+
|
|
130
|
+
- `source`: validated generated Python source;
|
|
131
|
+
- `included_paths`: absolute paths in render order, beginning with the entry;
|
|
132
|
+
- `unresolved_wildcards`: preserved imports as `(source_path, module)` pairs;
|
|
133
|
+
- `cycles`: skipped recursion edges as `(importer, target)` pairs;
|
|
134
|
+
- `output_path`: the absolute default or requested destination.
|
|
135
|
+
|
|
136
|
+
`BuildResult.write()` performs the only output mutation. It uses an atomic
|
|
137
|
+
same-directory replacement, refuses to overwrite any included source, and
|
|
138
|
+
revalidates the current `source` value immediately before writing. It returns
|
|
139
|
+
the destination path. `SplatfoldError` is the public base exception;
|
|
140
|
+
`ResolutionError` identifies unresolved imports in strict mode.
|
|
141
|
+
|
|
142
|
+
The CLI returns status `0` on success and status `2` for argument, resolution,
|
|
143
|
+
source, rendering, validation, or write errors. Diagnostics go to standard
|
|
144
|
+
error; `--list-deps` writes its machine-friendly path list to standard output.
|
|
145
|
+
Splatfold is still pre-1.0, so incompatible public-API changes require a minor
|
|
146
|
+
version increment and changelog entry.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
# 1. Basic Usage
|
|
151
|
+
|
|
152
|
+
Suppose your project looks like this:
|
|
153
|
+
|
|
154
|
+
```text
|
|
155
|
+
project/
|
|
156
|
+
├── main.py
|
|
157
|
+
├── tools.py
|
|
158
|
+
└── utils.py
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`main.py`:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from tools import *
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
print(double("hello"))
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`tools.py`:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from utils import *
|
|
177
|
+
|
|
178
|
+
def double(value):
|
|
179
|
+
return duplicate(value)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`utils.py`:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
def duplicate(value):
|
|
186
|
+
return value + value
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Run the following command from the `project/` directory:
|
|
190
|
+
|
|
191
|
+
```zsh
|
|
192
|
+
splatfold main.py
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The preprocessor generates:
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
main.flat.py
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Both:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
from tools import *
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
and:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from utils import *
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
are recursively replaced with their actual source code.
|
|
214
|
+
|
|
215
|
+
You can then run:
|
|
216
|
+
|
|
217
|
+
```zsh
|
|
218
|
+
python3 main.flat.py
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
# 2. Basic Command Format
|
|
224
|
+
|
|
225
|
+
Preferred form:
|
|
226
|
+
|
|
227
|
+
```zsh
|
|
228
|
+
splatfold INPUT [options]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The most common command is:
|
|
232
|
+
|
|
233
|
+
```zsh
|
|
234
|
+
splatfold main.py
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
To specify the output file:
|
|
238
|
+
|
|
239
|
+
```zsh
|
|
240
|
+
splatfold \
|
|
241
|
+
main.py \
|
|
242
|
+
-o dist/app.py
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
# 3. Positional input and `-i` / `--input`
|
|
248
|
+
|
|
249
|
+
The preferred positional argument specifies the entry-point Python file:
|
|
250
|
+
|
|
251
|
+
```zsh
|
|
252
|
+
splatfold main.py
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
For compatibility, `-i` and `--input` specify the same file:
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
|
|
259
|
+
```zsh
|
|
260
|
+
splatfold -i main.py
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Equivalent to:
|
|
264
|
+
|
|
265
|
+
```zsh
|
|
266
|
+
splatfold --input main.py
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The input file is the starting point of the dependency graph.
|
|
270
|
+
|
|
271
|
+
For example:
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
main.py
|
|
275
|
+
↓
|
|
276
|
+
tools.py
|
|
277
|
+
↓
|
|
278
|
+
utils.py
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Running:
|
|
282
|
+
|
|
283
|
+
```zsh
|
|
284
|
+
splatfold -i main.py
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
starts from `main.py` and recursively traces all expandable:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
from ... import *
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
statements.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
# 4. `-o` / `--output`
|
|
298
|
+
|
|
299
|
+
Specifies the generated output file.
|
|
300
|
+
|
|
301
|
+
Example:
|
|
302
|
+
|
|
303
|
+
```zsh
|
|
304
|
+
splatfold \
|
|
305
|
+
-i main.py \
|
|
306
|
+
-o build/app.py
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
This generates:
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
build/app.py
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
If `-o` is omitted:
|
|
316
|
+
|
|
317
|
+
```zsh
|
|
318
|
+
splatfold -i main.py
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
then:
|
|
322
|
+
|
|
323
|
+
```text
|
|
324
|
+
main.py
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
generates:
|
|
328
|
+
|
|
329
|
+
```text
|
|
330
|
+
main.flat.py
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Likewise:
|
|
334
|
+
|
|
335
|
+
```text
|
|
336
|
+
server.py
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
generates:
|
|
340
|
+
|
|
341
|
+
```text
|
|
342
|
+
server.flat.py
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
# 5. Which Imports Are Expanded?
|
|
348
|
+
|
|
349
|
+
Only wildcard imports of the form:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from module import *
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
are treated as include directives.
|
|
356
|
+
|
|
357
|
+
For example:
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
from tools import *
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
is expanded if the preprocessor can resolve a local:
|
|
364
|
+
|
|
365
|
+
```text
|
|
366
|
+
tools.py
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
file.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Imports That Are Expanded
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from tools import *
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
from package.tools import *
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
```python
|
|
384
|
+
from .tools import *
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from ..common import *
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from . import *
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
An expanded wildcard import must occupy its own physical line. A trailing
|
|
396
|
+
comment is allowed:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from tools import * # folded by Splatfold
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Splatfold rejects semicolon-separated forms such as
|
|
403
|
+
`from tools import *; ready = True`, because replacing the whole physical line
|
|
404
|
+
would otherwise silently discard unrelated code. Future imports that Splatfold
|
|
405
|
+
hoists follow the same rule.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Imports That Are Not Expanded
|
|
410
|
+
|
|
411
|
+
A normal import:
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
import tools
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
is not expanded.
|
|
418
|
+
|
|
419
|
+
A named import:
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
from tools import helper
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
is not expanded.
|
|
426
|
+
|
|
427
|
+
An aliased import:
|
|
428
|
+
|
|
429
|
+
```python
|
|
430
|
+
import tools as t
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
is not expanded.
|
|
434
|
+
|
|
435
|
+
These imports remain in the generated file unchanged.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
# 6. Standard Library and Third-Party Imports
|
|
440
|
+
|
|
441
|
+
By default, if:
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from something import *
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
cannot be resolved to a local Python source file, it is preserved unchanged.
|
|
448
|
+
|
|
449
|
+
For example:
|
|
450
|
+
|
|
451
|
+
```python
|
|
452
|
+
from math import *
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
remains:
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
from math import *
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
in the generated output.
|
|
462
|
+
|
|
463
|
+
This allows normal standard-library and third-party imports to continue working.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
# 7. `--strict`
|
|
468
|
+
|
|
469
|
+
If you want every wildcard import to resolve to a local source file, use:
|
|
470
|
+
|
|
471
|
+
```zsh
|
|
472
|
+
splatfold \
|
|
473
|
+
-i main.py \
|
|
474
|
+
--strict
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
For example:
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
from nonexistent import *
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
If no local:
|
|
484
|
+
|
|
485
|
+
```text
|
|
486
|
+
nonexistent.py
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
can be found, normal mode:
|
|
490
|
+
|
|
491
|
+
```zsh
|
|
492
|
+
splatfold -i main.py
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
preserves:
|
|
496
|
+
|
|
497
|
+
```python
|
|
498
|
+
from nonexistent import *
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
However:
|
|
502
|
+
|
|
503
|
+
```zsh
|
|
504
|
+
splatfold \
|
|
505
|
+
-i main.py \
|
|
506
|
+
--strict
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
reports an error and stops preprocessing.
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
# 8. Module Resolution Rules
|
|
514
|
+
|
|
515
|
+
For:
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
from tools import *
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
the preprocessor attempts to resolve:
|
|
522
|
+
|
|
523
|
+
```text
|
|
524
|
+
tools/__init__.py
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
or:
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
tools.py
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
For:
|
|
534
|
+
|
|
535
|
+
```python
|
|
536
|
+
from package.tools import *
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
it attempts to resolve:
|
|
540
|
+
|
|
541
|
+
```text
|
|
542
|
+
package/tools/__init__.py
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
or:
|
|
546
|
+
|
|
547
|
+
```text
|
|
548
|
+
package/tools.py
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
This package-before-module order matches normal Python imports when both forms
|
|
552
|
+
exist on the same search-path entry.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
# 9. `-r` / `--root`
|
|
557
|
+
|
|
558
|
+
`--root` specifies the source root used for absolute local imports.
|
|
559
|
+
|
|
560
|
+
By default:
|
|
561
|
+
|
|
562
|
+
> root = directory containing the input file
|
|
563
|
+
|
|
564
|
+
For example:
|
|
565
|
+
|
|
566
|
+
```text
|
|
567
|
+
project/
|
|
568
|
+
├── splatfold.py
|
|
569
|
+
├── main.py
|
|
570
|
+
└── lib/
|
|
571
|
+
└── tools.py
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
If the source contains:
|
|
575
|
+
|
|
576
|
+
```python
|
|
577
|
+
from lib.tools import *
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
then:
|
|
581
|
+
|
|
582
|
+
```zsh
|
|
583
|
+
splatfold -i main.py
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
normally resolves it correctly.
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
If your project instead looks like:
|
|
591
|
+
|
|
592
|
+
```text
|
|
593
|
+
project/
|
|
594
|
+
├── splatfold.py
|
|
595
|
+
├── app/
|
|
596
|
+
│ └── main.py
|
|
597
|
+
└── src/
|
|
598
|
+
└── tools.py
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
and:
|
|
602
|
+
|
|
603
|
+
```python
|
|
604
|
+
from tools import *
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
should resolve relative to:
|
|
608
|
+
|
|
609
|
+
```text
|
|
610
|
+
src/
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
use:
|
|
614
|
+
|
|
615
|
+
```zsh
|
|
616
|
+
splatfold \
|
|
617
|
+
-i app/main.py \
|
|
618
|
+
--root src
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
or the short form:
|
|
622
|
+
|
|
623
|
+
```zsh
|
|
624
|
+
splatfold \
|
|
625
|
+
-i app/main.py \
|
|
626
|
+
-r src
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
# 10. `-I` / `--search-path`
|
|
632
|
+
|
|
633
|
+
Additional local module search directories can be supplied with:
|
|
634
|
+
|
|
635
|
+
```text
|
|
636
|
+
-I / --search-path
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Example project:
|
|
640
|
+
|
|
641
|
+
```text
|
|
642
|
+
project/
|
|
643
|
+
├── splatfold.py
|
|
644
|
+
├── main.py
|
|
645
|
+
├── src/
|
|
646
|
+
│ └── tools.py
|
|
647
|
+
└── shared/
|
|
648
|
+
└── common.py
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Run:
|
|
652
|
+
|
|
653
|
+
```zsh
|
|
654
|
+
splatfold \
|
|
655
|
+
-i main.py \
|
|
656
|
+
-I src \
|
|
657
|
+
-I shared
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
`-I` may be specified multiple times.
|
|
661
|
+
|
|
662
|
+
Example:
|
|
663
|
+
|
|
664
|
+
```zsh
|
|
665
|
+
splatfold \
|
|
666
|
+
-i main.py \
|
|
667
|
+
-I src \
|
|
668
|
+
-I shared \
|
|
669
|
+
-I vendor
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
---
|
|
673
|
+
|
|
674
|
+
# 11. Relative Imports
|
|
675
|
+
|
|
676
|
+
Normal Python relative imports are supported.
|
|
677
|
+
|
|
678
|
+
For example:
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
from .tools import *
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
and:
|
|
685
|
+
|
|
686
|
+
```python
|
|
687
|
+
from ..common import *
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
can be resolved according to the source file's package location.
|
|
691
|
+
|
|
692
|
+
Example:
|
|
693
|
+
|
|
694
|
+
```text
|
|
695
|
+
package/
|
|
696
|
+
├── main.py
|
|
697
|
+
├── tools.py
|
|
698
|
+
└── helpers/
|
|
699
|
+
└── common.py
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Modules may continue to use standard Python relative-import syntax.
|
|
703
|
+
|
|
704
|
+
---
|
|
705
|
+
|
|
706
|
+
# 12. Circular Dependencies
|
|
707
|
+
|
|
708
|
+
Circular dependencies do not cause infinite recursion.
|
|
709
|
+
|
|
710
|
+
For example:
|
|
711
|
+
|
|
712
|
+
```text
|
|
713
|
+
main.py
|
|
714
|
+
↓
|
|
715
|
+
another.py
|
|
716
|
+
↓
|
|
717
|
+
tools.py
|
|
718
|
+
↓
|
|
719
|
+
another.py
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
That is:
|
|
723
|
+
|
|
724
|
+
`main.py`
|
|
725
|
+
|
|
726
|
+
```python
|
|
727
|
+
from another import *
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
`another.py`
|
|
731
|
+
|
|
732
|
+
```python
|
|
733
|
+
from tools import *
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
`tools.py`
|
|
737
|
+
|
|
738
|
+
```python
|
|
739
|
+
from another import *
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
The preprocessor detects:
|
|
743
|
+
|
|
744
|
+
```text
|
|
745
|
+
another → tools → another
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
and stops recursively expanding that cycle.
|
|
749
|
+
|
|
750
|
+
Each source file is emitted at most once.
|
|
751
|
+
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
# 13. Duplicate Dependencies
|
|
755
|
+
|
|
756
|
+
For example:
|
|
757
|
+
|
|
758
|
+
```text
|
|
759
|
+
main.py
|
|
760
|
+
├── a.py
|
|
761
|
+
│ └── common.py
|
|
762
|
+
└── b.py
|
|
763
|
+
└── common.py
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Even though:
|
|
767
|
+
|
|
768
|
+
```text
|
|
769
|
+
common.py
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
is referenced through two different paths, it is emitted only once.
|
|
773
|
+
|
|
774
|
+
This behavior is called:
|
|
775
|
+
|
|
776
|
+
```text
|
|
777
|
+
include once
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
# 14. Default Source Markers
|
|
783
|
+
|
|
784
|
+
By default, the generated file contains structural markers such as:
|
|
785
|
+
|
|
786
|
+
```python
|
|
787
|
+
# >>> splatfold: begin tools.py
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
and:
|
|
791
|
+
|
|
792
|
+
```python
|
|
793
|
+
# <<< splatfold: end tools.py
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
These indicate which generated sections came from which dependency.
|
|
797
|
+
|
|
798
|
+
Example:
|
|
799
|
+
|
|
800
|
+
```python
|
|
801
|
+
# >>> splatfold: begin tools.py
|
|
802
|
+
|
|
803
|
+
def double(value):
|
|
804
|
+
return value * 2
|
|
805
|
+
|
|
806
|
+
# <<< splatfold: end tools.py
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Circular or duplicate includes may also produce explanatory comments.
|
|
810
|
+
|
|
811
|
+
These markers are useful for:
|
|
812
|
+
|
|
813
|
+
* reading generated files;
|
|
814
|
+
* debugging;
|
|
815
|
+
* understanding dependencies;
|
|
816
|
+
* identifying source boundaries.
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
# 15. `--no-markers`
|
|
821
|
+
|
|
822
|
+
To generate a cleaner output file:
|
|
823
|
+
|
|
824
|
+
```zsh
|
|
825
|
+
splatfold \
|
|
826
|
+
-i main.py \
|
|
827
|
+
--no-markers
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
This disables structural comments such as:
|
|
831
|
+
|
|
832
|
+
```python
|
|
833
|
+
# >>> splatfold: begin ...
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
and:
|
|
837
|
+
|
|
838
|
+
```python
|
|
839
|
+
# <<< splatfold: end ...
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
The actual Python code is unaffected.
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
# 16. `--inline-source-map`
|
|
847
|
+
|
|
848
|
+
If you want each generated line to indicate which original file and line number it came from, use:
|
|
849
|
+
|
|
850
|
+
```zsh
|
|
851
|
+
splatfold \
|
|
852
|
+
-i main.py \
|
|
853
|
+
--inline-source-map
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
For example, if line 70 of:
|
|
857
|
+
|
|
858
|
+
```text
|
|
859
|
+
main.py
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
contains:
|
|
863
|
+
|
|
864
|
+
```python
|
|
865
|
+
print("hello")
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
the output becomes:
|
|
869
|
+
|
|
870
|
+
```python
|
|
871
|
+
print("hello") # main.py 70
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
Example:
|
|
875
|
+
|
|
876
|
+
```python
|
|
877
|
+
def main(): # main.py 68
|
|
878
|
+
message = "hello" # main.py 69
|
|
879
|
+
print(message) # main.py 70
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
Dependencies are traced in the same way:
|
|
883
|
+
|
|
884
|
+
```python
|
|
885
|
+
def double(value): # tools.py 21
|
|
886
|
+
return value * 2 # tools.py 22
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
This is useful for:
|
|
890
|
+
|
|
891
|
+
* debugging;
|
|
892
|
+
* inspecting flattened output;
|
|
893
|
+
* comparing generated code to source files;
|
|
894
|
+
* quickly locating original code.
|
|
895
|
+
|
|
896
|
+
---
|
|
897
|
+
|
|
898
|
+
## Inline Source Map Safety Rules
|
|
899
|
+
|
|
900
|
+
The preprocessor does not add inline comments where doing so would change Python semantics.
|
|
901
|
+
|
|
902
|
+
For example:
|
|
903
|
+
|
|
904
|
+
```python
|
|
905
|
+
MESSAGE = """hello
|
|
906
|
+
world
|
|
907
|
+
"""
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
must not become:
|
|
911
|
+
|
|
912
|
+
```python
|
|
913
|
+
MESSAGE = """hello # main.py 10
|
|
914
|
+
world # main.py 11
|
|
915
|
+
"""
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
because the inserted text would become part of the string.
|
|
919
|
+
|
|
920
|
+
Therefore, such lines are not forcibly annotated.
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
Likewise:
|
|
925
|
+
|
|
926
|
+
```python
|
|
927
|
+
value = 1 + \
|
|
928
|
+
2
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
must not become:
|
|
932
|
+
|
|
933
|
+
```python
|
|
934
|
+
value = 1 + \ # main.py 20
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
because that would break Python syntax.
|
|
938
|
+
|
|
939
|
+
Therefore:
|
|
940
|
+
|
|
941
|
+
> `--inline-source-map` annotates source lines whenever it is safe to do so, but never at the cost of changing program behavior.
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
945
|
+
# 17. `--source-marker`
|
|
946
|
+
|
|
947
|
+
The second source-tracing mode is:
|
|
948
|
+
|
|
949
|
+
```zsh
|
|
950
|
+
splatfold \
|
|
951
|
+
-i main.py \
|
|
952
|
+
--source-marker
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
Instead of adding a comment after every line, it inserts source-location markers before contiguous source regions.
|
|
956
|
+
|
|
957
|
+
For example:
|
|
958
|
+
|
|
959
|
+
```python
|
|
960
|
+
# >>> splatfold: source main.py:70
|
|
961
|
+
print("hello")
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
Or:
|
|
965
|
+
|
|
966
|
+
```python
|
|
967
|
+
# >>> splatfold: source main.py:68
|
|
968
|
+
def main():
|
|
969
|
+
message = "hello"
|
|
970
|
+
print(message)
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
This indicates that the first source line after the marker corresponds to:
|
|
974
|
+
|
|
975
|
+
```text
|
|
976
|
+
main.py:68
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
and subsequent uninterrupted source lines continue from there.
|
|
980
|
+
|
|
981
|
+
---
|
|
982
|
+
|
|
983
|
+
If preprocessing switches to another dependency:
|
|
984
|
+
|
|
985
|
+
```python
|
|
986
|
+
# >>> splatfold: source main.py:20
|
|
987
|
+
|
|
988
|
+
def foo():
|
|
989
|
+
pass
|
|
990
|
+
|
|
991
|
+
# >>> splatfold: source tools.py:1
|
|
992
|
+
|
|
993
|
+
def helper():
|
|
994
|
+
pass
|
|
995
|
+
|
|
996
|
+
# >>> splatfold: source main.py:24
|
|
997
|
+
|
|
998
|
+
def bar():
|
|
999
|
+
pass
|
|
1000
|
+
```
|
|
1001
|
+
|
|
1002
|
+
This representation is more compact than an inline source map.
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
# 18. `--inline-source-map` vs. `--source-marker`
|
|
1007
|
+
|
|
1008
|
+
### Inline Source Map
|
|
1009
|
+
|
|
1010
|
+
Use:
|
|
1011
|
+
|
|
1012
|
+
```zsh
|
|
1013
|
+
--inline-source-map
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
Output:
|
|
1017
|
+
|
|
1018
|
+
```python
|
|
1019
|
+
x = 10 # main.py 20
|
|
1020
|
+
y = 20 # main.py 21
|
|
1021
|
+
print(x + y) # main.py 22
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
Advantages:
|
|
1025
|
+
|
|
1026
|
+
* source location is visible on every annotated line;
|
|
1027
|
+
* very convenient for debugging.
|
|
1028
|
+
|
|
1029
|
+
Disadvantages:
|
|
1030
|
+
|
|
1031
|
+
* generated output is longer;
|
|
1032
|
+
* source comments are more visually intrusive.
|
|
1033
|
+
|
|
1034
|
+
---
|
|
1035
|
+
|
|
1036
|
+
### Source Marker
|
|
1037
|
+
|
|
1038
|
+
Use:
|
|
1039
|
+
|
|
1040
|
+
```zsh
|
|
1041
|
+
--source-marker
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
Output:
|
|
1045
|
+
|
|
1046
|
+
```python
|
|
1047
|
+
# >>> splatfold: source main.py:20
|
|
1048
|
+
x = 10
|
|
1049
|
+
y = 20
|
|
1050
|
+
print(x + y)
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
Advantages:
|
|
1054
|
+
|
|
1055
|
+
* cleaner output;
|
|
1056
|
+
* closer to the original source;
|
|
1057
|
+
* source provenance is still preserved.
|
|
1058
|
+
|
|
1059
|
+
For normal inspection, prefer:
|
|
1060
|
+
|
|
1061
|
+
```zsh
|
|
1062
|
+
--source-marker
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
For detailed debugging, prefer:
|
|
1066
|
+
|
|
1067
|
+
```zsh
|
|
1068
|
+
--inline-source-map
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
1073
|
+
# 19. The Two Source-Tracing Modes Are Mutually Exclusive
|
|
1074
|
+
|
|
1075
|
+
Do not use:
|
|
1076
|
+
|
|
1077
|
+
```zsh
|
|
1078
|
+
splatfold \
|
|
1079
|
+
-i main.py \
|
|
1080
|
+
--inline-source-map \
|
|
1081
|
+
--source-marker
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
at the same time.
|
|
1085
|
+
|
|
1086
|
+
These options are mutually exclusive.
|
|
1087
|
+
|
|
1088
|
+
Choose one source-tracing mode.
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
# 20. `--source-marker` with `--no-markers`
|
|
1093
|
+
|
|
1094
|
+
This is a useful combination:
|
|
1095
|
+
|
|
1096
|
+
```zsh
|
|
1097
|
+
splatfold \
|
|
1098
|
+
-i main.py \
|
|
1099
|
+
--source-marker \
|
|
1100
|
+
--no-markers
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
Structural markers such as:
|
|
1104
|
+
|
|
1105
|
+
```python
|
|
1106
|
+
# >>> splatfold: begin tools.py
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
are disabled.
|
|
1110
|
+
|
|
1111
|
+
However, source-location markers such as:
|
|
1112
|
+
|
|
1113
|
+
```python
|
|
1114
|
+
# >>> splatfold: source tools.py:20
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
are still generated.
|
|
1118
|
+
|
|
1119
|
+
This gives relatively clean output while retaining source tracing.
|
|
1120
|
+
|
|
1121
|
+
---
|
|
1122
|
+
|
|
1123
|
+
# 21. `if __name__ == "__main__"`
|
|
1124
|
+
|
|
1125
|
+
Suppose a dependency:
|
|
1126
|
+
|
|
1127
|
+
```text
|
|
1128
|
+
tools.py
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
contains:
|
|
1132
|
+
|
|
1133
|
+
```python
|
|
1134
|
+
def helper():
|
|
1135
|
+
pass
|
|
1136
|
+
|
|
1137
|
+
if __name__ == "__main__":
|
|
1138
|
+
print("testing tools")
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
Under normal Python imports:
|
|
1142
|
+
|
|
1143
|
+
```python
|
|
1144
|
+
from tools import *
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
the following code does not execute:
|
|
1148
|
+
|
|
1149
|
+
```python
|
|
1150
|
+
print("testing tools")
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
Blindly flattening the entire file would change that behavior.
|
|
1154
|
+
|
|
1155
|
+
Therefore, by default, the preprocessor removes conventional top-level:
|
|
1156
|
+
|
|
1157
|
+
```python
|
|
1158
|
+
if __name__ == "__main__":
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
blocks from dependency files.
|
|
1162
|
+
|
|
1163
|
+
However, the root input file's own:
|
|
1164
|
+
|
|
1165
|
+
```python
|
|
1166
|
+
if __name__ == "__main__":
|
|
1167
|
+
main()
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
block is preserved.
|
|
1171
|
+
|
|
1172
|
+
A dependency guard with an `else` clause is not a conventional removable main
|
|
1173
|
+
guard: normal importing executes that `else` branch. Splatfold therefore fails
|
|
1174
|
+
with a clear error instead of silently deleting live code. Rewrite that module
|
|
1175
|
+
so import-time definitions live outside the guard, or use
|
|
1176
|
+
`--keep-main-guards` when literal inclusion is intentionally desired.
|
|
1177
|
+
|
|
1178
|
+
---
|
|
1179
|
+
|
|
1180
|
+
# 22. `--keep-main-guards`
|
|
1181
|
+
|
|
1182
|
+
If you intentionally want dependency:
|
|
1183
|
+
|
|
1184
|
+
```python
|
|
1185
|
+
if __name__ == "__main__":
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
blocks to remain in the output, use:
|
|
1189
|
+
|
|
1190
|
+
```zsh
|
|
1191
|
+
splatfold \
|
|
1192
|
+
-i main.py \
|
|
1193
|
+
--keep-main-guards
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
This option is generally not recommended.
|
|
1197
|
+
|
|
1198
|
+
Use it only when you explicitly want behavior closer to literal textual inclusion.
|
|
1199
|
+
|
|
1200
|
+
---
|
|
1201
|
+
|
|
1202
|
+
# 23. `from __future__ import ...`
|
|
1203
|
+
|
|
1204
|
+
The preprocessor automatically handles special imports such as:
|
|
1205
|
+
|
|
1206
|
+
```python
|
|
1207
|
+
from __future__ import annotations
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
Python requires future imports to appear near the beginning of the module.
|
|
1211
|
+
|
|
1212
|
+
Therefore, if a dependency contains:
|
|
1213
|
+
|
|
1214
|
+
```python
|
|
1215
|
+
from __future__ import annotations
|
|
1216
|
+
```
|
|
1217
|
+
|
|
1218
|
+
the preprocessor does not simply copy it into the middle of the generated file.
|
|
1219
|
+
|
|
1220
|
+
Instead it:
|
|
1221
|
+
|
|
1222
|
+
1. collects future imports;
|
|
1223
|
+
2. deduplicates them;
|
|
1224
|
+
3. removes them from their original positions;
|
|
1225
|
+
4. places them in a valid position near the top of the final module.
|
|
1226
|
+
|
|
1227
|
+
For example:
|
|
1228
|
+
|
|
1229
|
+
```python
|
|
1230
|
+
"""Application."""
|
|
1231
|
+
|
|
1232
|
+
from __future__ import annotations
|
|
1233
|
+
```
|
|
1234
|
+
|
|
1235
|
+
remains a valid module structure.
|
|
1236
|
+
|
|
1237
|
+
Users normally do not need to handle this manually.
|
|
1238
|
+
|
|
1239
|
+
---
|
|
1240
|
+
|
|
1241
|
+
# 24. Source Encodings
|
|
1242
|
+
|
|
1243
|
+
The preprocessor uses Python's own source-encoding detection rules.
|
|
1244
|
+
|
|
1245
|
+
For example:
|
|
1246
|
+
|
|
1247
|
+
```python
|
|
1248
|
+
# -*- coding: latin-1 -*-
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
is supported.
|
|
1252
|
+
|
|
1253
|
+
Different dependencies may use different encodings.
|
|
1254
|
+
|
|
1255
|
+
The final generated file is always written as:
|
|
1256
|
+
|
|
1257
|
+
```text
|
|
1258
|
+
UTF-8
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
Therefore, source files do not need to be manually converted first.
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
# 25. Shebang Handling
|
|
1266
|
+
|
|
1267
|
+
If the root input file begins with:
|
|
1268
|
+
|
|
1269
|
+
```python
|
|
1270
|
+
#!/usr/bin/env python3
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
the shebang is preserved in the generated file.
|
|
1274
|
+
|
|
1275
|
+
Dependency shebangs are not treated as additional final-file shebangs.
|
|
1276
|
+
|
|
1277
|
+
---
|
|
1278
|
+
|
|
1279
|
+
# 26. `--check-only`
|
|
1280
|
+
|
|
1281
|
+
To verify that the project can be flattened successfully without writing an output file, use:
|
|
1282
|
+
|
|
1283
|
+
```zsh
|
|
1284
|
+
splatfold \
|
|
1285
|
+
-i main.py \
|
|
1286
|
+
--check-only
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
This still performs:
|
|
1290
|
+
|
|
1291
|
+
1. dependency discovery;
|
|
1292
|
+
2. source parsing;
|
|
1293
|
+
3. circular dependency detection;
|
|
1294
|
+
4. preprocessing;
|
|
1295
|
+
5. flattening;
|
|
1296
|
+
6. final Python syntax validation.
|
|
1297
|
+
|
|
1298
|
+
However, it does not write:
|
|
1299
|
+
|
|
1300
|
+
```text
|
|
1301
|
+
main.flat.py
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
This is useful for validation before a build.
|
|
1305
|
+
|
|
1306
|
+
A recommended workflow is:
|
|
1307
|
+
|
|
1308
|
+
```zsh
|
|
1309
|
+
splatfold \
|
|
1310
|
+
-i main.py \
|
|
1311
|
+
--check-only
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
and, if that succeeds:
|
|
1315
|
+
|
|
1316
|
+
```zsh
|
|
1317
|
+
splatfold -i main.py
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
---
|
|
1321
|
+
|
|
1322
|
+
# 27. `--list-deps`
|
|
1323
|
+
|
|
1324
|
+
To display the source files used by the build:
|
|
1325
|
+
|
|
1326
|
+
```zsh
|
|
1327
|
+
splatfold \
|
|
1328
|
+
-i main.py \
|
|
1329
|
+
--list-deps
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
Example output:
|
|
1333
|
+
|
|
1334
|
+
```text
|
|
1335
|
+
main.py
|
|
1336
|
+
another.py
|
|
1337
|
+
tools.py
|
|
1338
|
+
common.py
|
|
1339
|
+
```
|
|
1340
|
+
|
|
1341
|
+
The files are shown in their actual processing/inclusion order.
|
|
1342
|
+
|
|
1343
|
+
This is useful for:
|
|
1344
|
+
|
|
1345
|
+
* checking dependencies;
|
|
1346
|
+
* detecting unexpectedly included files;
|
|
1347
|
+
* debugging module resolution.
|
|
1348
|
+
|
|
1349
|
+
---
|
|
1350
|
+
|
|
1351
|
+
# 28. `-v` / `--verbose`
|
|
1352
|
+
|
|
1353
|
+
Enable verbose diagnostics:
|
|
1354
|
+
|
|
1355
|
+
```zsh
|
|
1356
|
+
splatfold \
|
|
1357
|
+
-i main.py \
|
|
1358
|
+
-v
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
or:
|
|
1362
|
+
|
|
1363
|
+
```zsh
|
|
1364
|
+
splatfold \
|
|
1365
|
+
-i main.py \
|
|
1366
|
+
--verbose
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
This prints additional information about dependency resolution, unresolved imports, circular dependencies, and related processing.
|
|
1370
|
+
|
|
1371
|
+
A useful debugging command is:
|
|
1372
|
+
|
|
1373
|
+
```zsh
|
|
1374
|
+
splatfold \
|
|
1375
|
+
-i main.py \
|
|
1376
|
+
--check-only \
|
|
1377
|
+
--list-deps \
|
|
1378
|
+
-v
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
---
|
|
1382
|
+
|
|
1383
|
+
# 29. Displaying the Version
|
|
1384
|
+
|
|
1385
|
+
Run:
|
|
1386
|
+
|
|
1387
|
+
```zsh
|
|
1388
|
+
splatfold --version
|
|
1389
|
+
```
|
|
1390
|
+
|
|
1391
|
+
Current version:
|
|
1392
|
+
|
|
1393
|
+
```text
|
|
1394
|
+
splatfold 0.2.0
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
---
|
|
1398
|
+
|
|
1399
|
+
# 30. Displaying CLI Help
|
|
1400
|
+
|
|
1401
|
+
Run:
|
|
1402
|
+
|
|
1403
|
+
```zsh
|
|
1404
|
+
splatfold --help
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
or:
|
|
1408
|
+
|
|
1409
|
+
```zsh
|
|
1410
|
+
splatfold -h
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
to display all available command-line options.
|
|
1414
|
+
|
|
1415
|
+
---
|
|
1416
|
+
|
|
1417
|
+
# 31. Recommended Development Workflow
|
|
1418
|
+
|
|
1419
|
+
During development, keep the project as a normal multi-file Python project:
|
|
1420
|
+
|
|
1421
|
+
```text
|
|
1422
|
+
project/
|
|
1423
|
+
├── main.py
|
|
1424
|
+
├── tools.py
|
|
1425
|
+
├── parser.py
|
|
1426
|
+
├── utils.py
|
|
1427
|
+
└── splatfold.py
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
Write normal Python imports such as:
|
|
1431
|
+
|
|
1432
|
+
```python
|
|
1433
|
+
from tools import *
|
|
1434
|
+
from parser import *
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1437
|
+
Your IDE continues to treat them as regular Python modules.
|
|
1438
|
+
|
|
1439
|
+
---
|
|
1440
|
+
|
|
1441
|
+
During development, run the original project normally:
|
|
1442
|
+
|
|
1443
|
+
```zsh
|
|
1444
|
+
python3 main.py
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
---
|
|
1448
|
+
|
|
1449
|
+
To validate flattening:
|
|
1450
|
+
|
|
1451
|
+
```zsh
|
|
1452
|
+
splatfold \
|
|
1453
|
+
-i main.py \
|
|
1454
|
+
--check-only \
|
|
1455
|
+
--list-deps
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
---
|
|
1459
|
+
|
|
1460
|
+
To create a debugging build:
|
|
1461
|
+
|
|
1462
|
+
```zsh
|
|
1463
|
+
splatfold \
|
|
1464
|
+
-i main.py \
|
|
1465
|
+
--inline-source-map
|
|
1466
|
+
```
|
|
1467
|
+
|
|
1468
|
+
---
|
|
1469
|
+
|
|
1470
|
+
To create relatively clean output while retaining source tracing:
|
|
1471
|
+
|
|
1472
|
+
```zsh
|
|
1473
|
+
splatfold \
|
|
1474
|
+
-i main.py \
|
|
1475
|
+
--source-marker \
|
|
1476
|
+
--no-markers
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
---
|
|
1480
|
+
|
|
1481
|
+
For a clean release build:
|
|
1482
|
+
|
|
1483
|
+
```zsh
|
|
1484
|
+
splatfold \
|
|
1485
|
+
-i main.py \
|
|
1486
|
+
-o dist/app.py \
|
|
1487
|
+
--no-markers
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
---
|
|
1491
|
+
|
|
1492
|
+
Finally, test the generated file:
|
|
1493
|
+
|
|
1494
|
+
```zsh
|
|
1495
|
+
python3 dist/app.py
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
# 32. Recommended Debugging Command
|
|
1501
|
+
|
|
1502
|
+
If something goes wrong, first run:
|
|
1503
|
+
|
|
1504
|
+
```zsh
|
|
1505
|
+
splatfold \
|
|
1506
|
+
-i main.py \
|
|
1507
|
+
--check-only \
|
|
1508
|
+
--list-deps \
|
|
1509
|
+
-v
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
This allows you to:
|
|
1513
|
+
|
|
1514
|
+
* avoid modifying any output file;
|
|
1515
|
+
* inspect dependencies;
|
|
1516
|
+
* inspect module resolution;
|
|
1517
|
+
* validate the final Python source.
|
|
1518
|
+
|
|
1519
|
+
If additional source-level tracing is needed:
|
|
1520
|
+
|
|
1521
|
+
```zsh
|
|
1522
|
+
splatfold \
|
|
1523
|
+
-i main.py \
|
|
1524
|
+
--inline-source-map
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
Then inspect:
|
|
1528
|
+
|
|
1529
|
+
```text
|
|
1530
|
+
main.flat.py
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
and its:
|
|
1534
|
+
|
|
1535
|
+
```python
|
|
1536
|
+
# filename.py LINE
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
annotations.
|
|
1540
|
+
|
|
1541
|
+
---
|
|
1542
|
+
|
|
1543
|
+
# 33. Syntax Errors
|
|
1544
|
+
|
|
1545
|
+
If any source file contains invalid Python syntax, for example:
|
|
1546
|
+
|
|
1547
|
+
```python
|
|
1548
|
+
def hello(
|
|
1549
|
+
print("hello")
|
|
1550
|
+
```
|
|
1551
|
+
|
|
1552
|
+
the preprocessor detects the error before generating the final output.
|
|
1553
|
+
|
|
1554
|
+
The error report includes:
|
|
1555
|
+
|
|
1556
|
+
* filename;
|
|
1557
|
+
* line number;
|
|
1558
|
+
* column number;
|
|
1559
|
+
* Python's syntax error message.
|
|
1560
|
+
|
|
1561
|
+
The final flattened output is also validated with:
|
|
1562
|
+
|
|
1563
|
+
```python
|
|
1564
|
+
compile()
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
Therefore:
|
|
1568
|
+
|
|
1569
|
+
> Individual source files being valid does not guarantee that the flattened result is valid. The final output is checked again.
|
|
1570
|
+
|
|
1571
|
+
---
|
|
1572
|
+
|
|
1573
|
+
# 34. The Preprocessor Does Not Execute Project Source Code
|
|
1574
|
+
|
|
1575
|
+
During preprocessing, the tool only:
|
|
1576
|
+
|
|
1577
|
+
* reads source code;
|
|
1578
|
+
* parses source code;
|
|
1579
|
+
* analyzes imports;
|
|
1580
|
+
* generates source code;
|
|
1581
|
+
* compile-checks source code.
|
|
1582
|
+
|
|
1583
|
+
It does not discover dependencies by executing:
|
|
1584
|
+
|
|
1585
|
+
```python
|
|
1586
|
+
import your_module
|
|
1587
|
+
```
|
|
1588
|
+
|
|
1589
|
+
and it does not use:
|
|
1590
|
+
|
|
1591
|
+
```python
|
|
1592
|
+
exec(...)
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
to run project source code.
|
|
1596
|
+
|
|
1597
|
+
Therefore, preprocessing itself does not trigger module runtime side effects.
|
|
1598
|
+
|
|
1599
|
+
---
|
|
1600
|
+
|
|
1601
|
+
# 35. Complete Example
|
|
1602
|
+
|
|
1603
|
+
Project:
|
|
1604
|
+
|
|
1605
|
+
```text
|
|
1606
|
+
project/
|
|
1607
|
+
├── splatfold.py
|
|
1608
|
+
├── main.py
|
|
1609
|
+
├── another.py
|
|
1610
|
+
└── tools.py
|
|
1611
|
+
```
|
|
1612
|
+
|
|
1613
|
+
`main.py`:
|
|
1614
|
+
|
|
1615
|
+
```python
|
|
1616
|
+
from another import *
|
|
1617
|
+
|
|
1618
|
+
def run():
|
|
1619
|
+
print(double_string("21"))
|
|
1620
|
+
|
|
1621
|
+
if __name__ == "__main__":
|
|
1622
|
+
run()
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
`another.py`:
|
|
1626
|
+
|
|
1627
|
+
```python
|
|
1628
|
+
from tools import *
|
|
1629
|
+
|
|
1630
|
+
def is_string(value):
|
|
1631
|
+
return isinstance(value, str)
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
`tools.py`:
|
|
1635
|
+
|
|
1636
|
+
```python
|
|
1637
|
+
from another import *
|
|
1638
|
+
|
|
1639
|
+
def double_string(value):
|
|
1640
|
+
if is_string(value):
|
|
1641
|
+
return str(int(value) * 2)
|
|
1642
|
+
|
|
1643
|
+
return value
|
|
1644
|
+
```
|
|
1645
|
+
|
|
1646
|
+
This contains a circular dependency:
|
|
1647
|
+
|
|
1648
|
+
```text
|
|
1649
|
+
another
|
|
1650
|
+
↓
|
|
1651
|
+
tools
|
|
1652
|
+
↓
|
|
1653
|
+
another
|
|
1654
|
+
```
|
|
1655
|
+
|
|
1656
|
+
Run:
|
|
1657
|
+
|
|
1658
|
+
```zsh
|
|
1659
|
+
splatfold \
|
|
1660
|
+
-i main.py \
|
|
1661
|
+
--source-marker
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
The preprocessor:
|
|
1665
|
+
|
|
1666
|
+
1. reads `main.py`;
|
|
1667
|
+
2. resolves `another.py`;
|
|
1668
|
+
3. resolves `tools.py`;
|
|
1669
|
+
4. encounters `another.py` again;
|
|
1670
|
+
5. detects the cycle;
|
|
1671
|
+
6. does not expand the same source file again;
|
|
1672
|
+
7. generates a single:
|
|
1673
|
+
|
|
1674
|
+
```text
|
|
1675
|
+
main.flat.py
|
|
1676
|
+
```
|
|
1677
|
+
8. validates the final Python syntax.
|
|
1678
|
+
|
|
1679
|
+
Then run:
|
|
1680
|
+
|
|
1681
|
+
```zsh
|
|
1682
|
+
python3 main.flat.py
|
|
1683
|
+
```
|
|
1684
|
+
|
|
1685
|
+
Expected output:
|
|
1686
|
+
|
|
1687
|
+
```text
|
|
1688
|
+
42
|
|
1689
|
+
```
|
|
1690
|
+
|
|
1691
|
+
---
|
|
1692
|
+
|
|
1693
|
+
# 36. When Should You Use This Tool?
|
|
1694
|
+
|
|
1695
|
+
This tool is suitable when you:
|
|
1696
|
+
|
|
1697
|
+
* want to develop a normal multi-file Python project;
|
|
1698
|
+
* want to distribute a single Python file;
|
|
1699
|
+
* want IDE and LSP support during development;
|
|
1700
|
+
* do not want to introduce a custom `#include` syntax;
|
|
1701
|
+
* are building small utilities;
|
|
1702
|
+
* are building single-file CLIs;
|
|
1703
|
+
* are building scripts;
|
|
1704
|
+
* are building plugins;
|
|
1705
|
+
* want easily distributable Python source;
|
|
1706
|
+
* are experimenting with compiler/preprocessor-style tooling.
|
|
1707
|
+
|
|
1708
|
+
---
|
|
1709
|
+
|
|
1710
|
+
# 37. When Should You Not Use It?
|
|
1711
|
+
|
|
1712
|
+
If the application heavily depends on true Python module namespaces, for example:
|
|
1713
|
+
|
|
1714
|
+
```python
|
|
1715
|
+
import foo
|
|
1716
|
+
|
|
1717
|
+
foo.value
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
or relies heavily on:
|
|
1721
|
+
|
|
1722
|
+
```python
|
|
1723
|
+
__name__
|
|
1724
|
+
__package__
|
|
1725
|
+
__file__
|
|
1726
|
+
sys.modules
|
|
1727
|
+
```
|
|
1728
|
+
|
|
1729
|
+
as well as complex:
|
|
1730
|
+
|
|
1731
|
+
* import hooks;
|
|
1732
|
+
* plugin systems;
|
|
1733
|
+
* dynamic imports;
|
|
1734
|
+
* module initialization side effects;
|
|
1735
|
+
* C extension modules;
|
|
1736
|
+
|
|
1737
|
+
then you should not assume that flattened behavior will always exactly match the original multi-module project.
|
|
1738
|
+
|
|
1739
|
+
Splatfold deliberately expands only module-level wildcard imports. Imports
|
|
1740
|
+
inside functions, classes, conditionals, and exception handlers remain normal
|
|
1741
|
+
Python imports.
|
|
1742
|
+
|
|
1743
|
+
Included modules share one generated global namespace. Unlike normal module
|
|
1744
|
+
imports, flattening does not preserve a private namespace per source file, and
|
|
1745
|
+
`__all__` does not hide definitions that are physically present in the combined
|
|
1746
|
+
file. Projects should therefore avoid conflicting top-level names and should
|
|
1747
|
+
not depend on module-specific metadata or initialization isolation.
|
|
1748
|
+
|
|
1749
|
+
Collected `from __future__ import ...` statements are hoisted to the generated
|
|
1750
|
+
module header, as Python requires. A future feature used by one dependency
|
|
1751
|
+
therefore applies to the entire generated module; projects should keep future
|
|
1752
|
+
feature choices consistent across their source tree.
|
|
1753
|
+
|
|
1754
|
+
This tool is a:
|
|
1755
|
+
|
|
1756
|
+
```text
|
|
1757
|
+
source preprocessor / source flattener
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
not a complete:
|
|
1761
|
+
|
|
1762
|
+
```text
|
|
1763
|
+
Python import system emulator
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
---
|
|
1767
|
+
|
|
1768
|
+
# 38. Most Important Design Rule
|
|
1769
|
+
|
|
1770
|
+
Development source code should continue to use normal Python:
|
|
1771
|
+
|
|
1772
|
+
```python
|
|
1773
|
+
from tools import *
|
|
1774
|
+
```
|
|
1775
|
+
|
|
1776
|
+
Your IDE understands it as normal Python.
|
|
1777
|
+
|
|
1778
|
+
The preprocessor simply gives that existing syntax an additional build-time meaning:
|
|
1779
|
+
|
|
1780
|
+
> Include this local module's source code in the generated single-file output.
|
|
1781
|
+
|
|
1782
|
+
Therefore:
|
|
1783
|
+
|
|
1784
|
+
> Keep the original multi-file project as the real source code.
|
|
1785
|
+
|
|
1786
|
+
Treat:
|
|
1787
|
+
|
|
1788
|
+
```text
|
|
1789
|
+
*.flat.py
|
|
1790
|
+
```
|
|
1791
|
+
|
|
1792
|
+
as:
|
|
1793
|
+
|
|
1794
|
+
> automatically generated build artifacts.
|
|
1795
|
+
|
|
1796
|
+
Do not manually edit `.flat.py`.
|
|
1797
|
+
|
|
1798
|
+
Make changes in the original:
|
|
1799
|
+
|
|
1800
|
+
```text
|
|
1801
|
+
.py
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
files, then run the preprocessor again.
|
|
1805
|
+
|
|
1806
|
+
---
|
|
1807
|
+
|
|
1808
|
+
# 39. Common Command Reference
|
|
1809
|
+
|
|
1810
|
+
Basic build:
|
|
1811
|
+
|
|
1812
|
+
```zsh
|
|
1813
|
+
splatfold -i main.py
|
|
1814
|
+
```
|
|
1815
|
+
|
|
1816
|
+
Specify output:
|
|
1817
|
+
|
|
1818
|
+
```zsh
|
|
1819
|
+
splatfold \
|
|
1820
|
+
-i main.py \
|
|
1821
|
+
-o app.py
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
Validation only:
|
|
1825
|
+
|
|
1826
|
+
```zsh
|
|
1827
|
+
splatfold \
|
|
1828
|
+
-i main.py \
|
|
1829
|
+
--check-only
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
List dependencies:
|
|
1833
|
+
|
|
1834
|
+
```zsh
|
|
1835
|
+
splatfold \
|
|
1836
|
+
-i main.py \
|
|
1837
|
+
--list-deps
|
|
1838
|
+
```
|
|
1839
|
+
|
|
1840
|
+
Verbose diagnostics:
|
|
1841
|
+
|
|
1842
|
+
```zsh
|
|
1843
|
+
splatfold \
|
|
1844
|
+
-i main.py \
|
|
1845
|
+
--check-only \
|
|
1846
|
+
--list-deps \
|
|
1847
|
+
-v
|
|
1848
|
+
```
|
|
1849
|
+
|
|
1850
|
+
Inline source tracing:
|
|
1851
|
+
|
|
1852
|
+
```zsh
|
|
1853
|
+
splatfold \
|
|
1854
|
+
-i main.py \
|
|
1855
|
+
--inline-source-map
|
|
1856
|
+
```
|
|
1857
|
+
|
|
1858
|
+
Source-marker tracing:
|
|
1859
|
+
|
|
1860
|
+
```zsh
|
|
1861
|
+
splatfold \
|
|
1862
|
+
-i main.py \
|
|
1863
|
+
--source-marker
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1866
|
+
Clean source-marker build:
|
|
1867
|
+
|
|
1868
|
+
```zsh
|
|
1869
|
+
splatfold \
|
|
1870
|
+
-i main.py \
|
|
1871
|
+
--source-marker \
|
|
1872
|
+
--no-markers
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
Clean release build:
|
|
1876
|
+
|
|
1877
|
+
```zsh
|
|
1878
|
+
splatfold \
|
|
1879
|
+
-i main.py \
|
|
1880
|
+
-o dist/app.py \
|
|
1881
|
+
--no-markers
|
|
1882
|
+
```
|
|
1883
|
+
|
|
1884
|
+
Strict build:
|
|
1885
|
+
|
|
1886
|
+
```zsh
|
|
1887
|
+
splatfold \
|
|
1888
|
+
-i main.py \
|
|
1889
|
+
--strict
|
|
1890
|
+
```
|
|
1891
|
+
|
|
1892
|
+
Additional module search paths:
|
|
1893
|
+
|
|
1894
|
+
```zsh
|
|
1895
|
+
splatfold \
|
|
1896
|
+
-i main.py \
|
|
1897
|
+
-I src \
|
|
1898
|
+
-I shared
|
|
1899
|
+
```
|
|
1900
|
+
|
|
1901
|
+
Display help:
|
|
1902
|
+
|
|
1903
|
+
```zsh
|
|
1904
|
+
splatfold --help
|
|
1905
|
+
```
|
|
1906
|
+
|
|
1907
|
+
Display version:
|
|
1908
|
+
|
|
1909
|
+
```zsh
|
|
1910
|
+
splatfold --version
|
|
1911
|
+
```
|