indentoken 1.0.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.
- indentoken-1.0.0/LICENSE +21 -0
- indentoken-1.0.0/PKG-INFO +395 -0
- indentoken-1.0.0/README.md +375 -0
- indentoken-1.0.0/pyproject.toml +49 -0
- indentoken-1.0.0/pyproject.toml.orig +50 -0
- indentoken-1.0.0/src/indentoken/__init__.py +5 -0
- indentoken-1.0.0/src/indentoken/feature_flags.py +8 -0
- indentoken-1.0.0/src/indentoken/indentation.py +360 -0
- indentoken-1.0.0/src/indentoken/py.typed +0 -0
indentoken-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rockmizu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: indentoken
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: An indentation token which helps you manage text indentations.
|
|
5
|
+
Keywords: indentation,indent,dedent,text formatting,output formatting
|
|
6
|
+
Author: Rockmizu
|
|
7
|
+
Author-email: Rockmizu <Rockmizu@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Topic :: Text Processing :: General
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# indentoken
|
|
22
|
+
|
|
23
|
+
A magical little token to help you manage string indentation.
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
* [Motivation](#motivation)
|
|
28
|
+
* [Features](#features)
|
|
29
|
+
* [Requirements](#requirements)
|
|
30
|
+
* [Installation](#installation)
|
|
31
|
+
* [Usage](#usage)
|
|
32
|
+
* [Basic Indentation and Dedentation](#basic-indentation-and-dedentation)
|
|
33
|
+
* [Using Context Manager for Indentation](#using-context-manager-for-indentation)
|
|
34
|
+
* [Multi-line Text Indentation](#multi-line-text-indentation)
|
|
35
|
+
* [Initialization](#initialization)
|
|
36
|
+
* [Padding](#padding)
|
|
37
|
+
* [Indentation Addition](#indentation-addition)
|
|
38
|
+
* [Multi-parameter Print](#multi-parameter-print)
|
|
39
|
+
* [License](#license)
|
|
40
|
+
|
|
41
|
+
## Motivation
|
|
42
|
+
|
|
43
|
+
Have you ever wanted your program output to look like this?
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
food
|
|
47
|
+
fruit
|
|
48
|
+
apple
|
|
49
|
+
banana
|
|
50
|
+
--------
|
|
51
|
+
meat
|
|
52
|
+
pork
|
|
53
|
+
beef
|
|
54
|
+
--------
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Normally, you need to manually track the current indentation depth:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
61
|
+
|
|
62
|
+
print('food')
|
|
63
|
+
for category, items in foods.items():
|
|
64
|
+
print(f' {category}')
|
|
65
|
+
# ^^ manually track the indentations
|
|
66
|
+
for item in items:
|
|
67
|
+
print(f' {item}')
|
|
68
|
+
# ^^^^ manually track the indentations
|
|
69
|
+
print(f' --------')
|
|
70
|
+
# ^^ manually track the indentations
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This method is not only very prone to errors, but it becomes even harder
|
|
74
|
+
to track the preceding whitespace when breaking down sub-loops
|
|
75
|
+
into functions.
|
|
76
|
+
|
|
77
|
+
How great would it be if there was a tool to help you track indentation.
|
|
78
|
+
|
|
79
|
+
This is exactly where indentoken can help!
|
|
80
|
+
|
|
81
|
+
Now you can write:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from indentoken import Indentation
|
|
85
|
+
|
|
86
|
+
ind = Indentation()
|
|
87
|
+
|
|
88
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
89
|
+
|
|
90
|
+
print('food')
|
|
91
|
+
|
|
92
|
+
with ind.indented_context():
|
|
93
|
+
for category, items in foods.items():
|
|
94
|
+
print(f'{ind}{category}')
|
|
95
|
+
|
|
96
|
+
with ind.indented_context():
|
|
97
|
+
for item in items:
|
|
98
|
+
print(f'{ind}{item}')
|
|
99
|
+
|
|
100
|
+
print(f'{ind}--------')
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Notice how the manual whitespace disappears, replaced by an `ind` object
|
|
104
|
+
that tracks the indentation depth and converts it to a string for you.
|
|
105
|
+
|
|
106
|
+
You can also pass it into a function to continuously track
|
|
107
|
+
the current indentation:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from collections.abc import Iterable
|
|
111
|
+
|
|
112
|
+
from indentoken import Indentation
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main() -> None:
|
|
116
|
+
ind = Indentation()
|
|
117
|
+
|
|
118
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
119
|
+
|
|
120
|
+
print('food')
|
|
121
|
+
|
|
122
|
+
with ind.indented_context():
|
|
123
|
+
for category, items in foods.items():
|
|
124
|
+
print(f'{ind}{category}')
|
|
125
|
+
|
|
126
|
+
with ind.indented_context():
|
|
127
|
+
show_food_items(items, ind=ind)
|
|
128
|
+
|
|
129
|
+
print(f'{ind}--------')
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def show_food_items(items: Iterable[str], *, ind: Indentation) -> None:
|
|
133
|
+
for item in items:
|
|
134
|
+
print(f'{ind}{item}')
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == '__main__':
|
|
138
|
+
main()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
There are more features, please see [Usage](#usage).
|
|
142
|
+
|
|
143
|
+
## Features
|
|
144
|
+
|
|
145
|
+
1. Multifunctional and convenient indentation tracking token.
|
|
146
|
+
2. Complete type annotations, supporting modern type checking
|
|
147
|
+
and type-safe development.
|
|
148
|
+
3. Zero package dependencies, so you don't have to worry about conflicts with
|
|
149
|
+
other packages in your virtual environment during installation.
|
|
150
|
+
4. Pure Python package, no C extensions, usable anywhere Python runs.
|
|
151
|
+
|
|
152
|
+
## Requirements
|
|
153
|
+
|
|
154
|
+
This package supports Python 3.10 and above.
|
|
155
|
+
|
|
156
|
+
## Installation
|
|
157
|
+
|
|
158
|
+
You can install this package using pip:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
pip install indentoken
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Usage
|
|
165
|
+
|
|
166
|
+
The following examples assume you have imported:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from indentoken import Indentation
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Basic Indentation and Dedentation
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
ind = Indentation()
|
|
176
|
+
|
|
177
|
+
# Have no indentation at all initally.
|
|
178
|
+
print(f'{ind}Line 1') # |Line 1
|
|
179
|
+
|
|
180
|
+
# Indent by 1 level.
|
|
181
|
+
ind.indent()
|
|
182
|
+
print(f'{ind}Line 2') # | Line 2
|
|
183
|
+
|
|
184
|
+
# Indent by 3 levels, plus the previous 1 level.
|
|
185
|
+
# Now the indentation is 4 levels deep.
|
|
186
|
+
ind.indent(3)
|
|
187
|
+
print(f'{ind}Line 3') # | Line 3
|
|
188
|
+
|
|
189
|
+
# Dedent by 1 level.
|
|
190
|
+
# Now the indentation is 3 levels deep.
|
|
191
|
+
ind.dedent()
|
|
192
|
+
print(f'{ind}Line 4') # | Line 4
|
|
193
|
+
|
|
194
|
+
# Dedent by 99 levels.
|
|
195
|
+
# Note that the indentation level is always non-negative.
|
|
196
|
+
# The final indentation level will be clamped at 0.
|
|
197
|
+
ind.dedent(99)
|
|
198
|
+
assert ind.level == 0
|
|
199
|
+
print(f'{ind}Line 5') # |Line 5
|
|
200
|
+
|
|
201
|
+
# Directly set the current indentation level to 3.
|
|
202
|
+
ind.level = 3
|
|
203
|
+
print(f'{ind}Line 6') # | Line 6
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Using Context Manager for Indentation
|
|
207
|
+
|
|
208
|
+
If you want to increase indentation within a certain block, I recommend
|
|
209
|
+
using `with` combined with `indented_context()`.
|
|
210
|
+
This ensures that the indentation automatically reverts to
|
|
211
|
+
the correct depth even if an exception is raised.
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
ind = Indentation()
|
|
215
|
+
|
|
216
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
217
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
218
|
+
with ind.indented_context():
|
|
219
|
+
print(f'{ind}This line is indented.') # | This line is indented.
|
|
220
|
+
print(f'{ind}This line is indented.') # | This line is indented.
|
|
221
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
222
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
223
|
+
with ind.indented_context(2):
|
|
224
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
225
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
226
|
+
with ind.indented_context(1): # nested indented context will stack
|
|
227
|
+
print(f'{ind}This line is indented by 3 levels.') # | This line is indented by 3 levels.
|
|
228
|
+
print(f'{ind}This line is indented by 3 levels.') # | This line is indented by 3 levels.
|
|
229
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The output you will get is like this:
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
This line is NOT indented.
|
|
236
|
+
This line is NOT indented.
|
|
237
|
+
This line is indented.
|
|
238
|
+
This line is indented.
|
|
239
|
+
This line is NOT indented.
|
|
240
|
+
This line is NOT indented.
|
|
241
|
+
This line is indented by 2 levels.
|
|
242
|
+
This line is indented by 2 levels.
|
|
243
|
+
This line is indented by 3 levels.
|
|
244
|
+
This line is indented by 3 levels.
|
|
245
|
+
This line is indented by 2 levels.
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Multi-line Text Indentation
|
|
249
|
+
|
|
250
|
+
For multi-line text, you can directly wrap it in `ind`
|
|
251
|
+
or use the `apply_to()` method:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
ind = Indentation(level=1)
|
|
255
|
+
|
|
256
|
+
print('No indentation.')
|
|
257
|
+
print(ind('This is a multi-line text.\nAll lines will be indented.\nThe third line.'))
|
|
258
|
+
# ind('something') is equivalent to ind.apply_to('something')
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
It will output:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
No indentation.
|
|
265
|
+
This is a multi-line text.
|
|
266
|
+
All lines will be indented.
|
|
267
|
+
The third line.
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Initialization
|
|
271
|
+
|
|
272
|
+
During initialization, you can specify the indentation characters,
|
|
273
|
+
initial indentation depth, and padding (which will be mentioned later).
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
ind = Indentation(word='-->', level=2)
|
|
277
|
+
print(f'{ind}Line 1') # |-->-->Line 1
|
|
278
|
+
ind.indent()
|
|
279
|
+
print(f'{ind}Line 2') # |-->-->-->Line 2
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Padding
|
|
283
|
+
|
|
284
|
+
If you want the starting point of the indentation not to begin
|
|
285
|
+
from the far left, you can specify padding:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# Use "+++++" as padding for visualization. You can use whitespaces.
|
|
289
|
+
ind = Indentation(word='-->', padding='+++++')
|
|
290
|
+
print(f'{ind}Line 1') # |+++++Line 1
|
|
291
|
+
ind.indent()
|
|
292
|
+
print(f'{ind}Line 2') # |+++++-->Line 2
|
|
293
|
+
ind.indent()
|
|
294
|
+
print(f'{ind}Line 3') # |+++++-->-->Line 3
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Padding does not change with indentation depth and it always exists,
|
|
298
|
+
even if the indentation depth is zero.
|
|
299
|
+
|
|
300
|
+
You can also specify the `Indentation` object as padding,
|
|
301
|
+
which gives you dynamic padding effects:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
pad = Indentation(word='++', level=2)
|
|
305
|
+
ind = Indentation(word='-->', padding=pad)
|
|
306
|
+
print(f'{ind}Line 1') # |++++Line 1
|
|
307
|
+
ind.indent()
|
|
308
|
+
print(f'{ind}Line 2') # |++++-->Line 2
|
|
309
|
+
ind.indent()
|
|
310
|
+
print(f'{ind}Line 3') # |++++-->-->Line 3
|
|
311
|
+
pad.dedent() # Note that the `pad` object is changing, not `ind`.
|
|
312
|
+
print(f'{ind}Line 4') # |++-->-->Line 4
|
|
313
|
+
|
|
314
|
+
# Convert the padding to `str` on init if you want a fixed padding.
|
|
315
|
+
pad = Indentation(word='++', level=2)
|
|
316
|
+
ind = Indentation(word='-->', level=1, padding=str(pad))
|
|
317
|
+
print(f'{ind}Line 5') # |++++-->Line 5
|
|
318
|
+
pad.dedent() # Note that the `pad` object is changing, not `ind`.
|
|
319
|
+
print(f'{ind}Line 6') # |++++-->Line 6
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Indentation Addition
|
|
323
|
+
|
|
324
|
+
If you temporarily need to deepen the indentation
|
|
325
|
+
without using the heavy `with` statement, you can directly add
|
|
326
|
+
the desired indentation depth to the indentation object.
|
|
327
|
+
|
|
328
|
+
Additionally, `Indentation` addition only changes
|
|
329
|
+
the indentation depth and does not change the padding.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
ind = Indentation(word='-->', level=1)
|
|
333
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
334
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
335
|
+
print(f'{ind + 1}one level deeper, two levels indented') # |-->-->one level deeper, two levels indented
|
|
336
|
+
print(f'{ind + 2}two levels deeper, three levels indented') # |-->-->-->two levels deeper, three levels indented
|
|
337
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
338
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
339
|
+
|
|
340
|
+
ind = Indentation(word='-->', level=1, padding='++')
|
|
341
|
+
print(f'{ind}one level indented') # |++-->one level indented
|
|
342
|
+
print(f'{ind + 1}one level deeper, two levels indented') # |++-->-->one level deeper, two levels indented
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Indentation addition is commutative:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
ind = Indentation(word='-->', level=2)
|
|
349
|
+
assert ind + 1 == 1 + ind
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Multi-parameter Print
|
|
353
|
+
|
|
354
|
+
If you want the following usage of `print` to also receive indentation,
|
|
355
|
+
you can wrap the `print` function with the `Indentation` object.
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
print('Alice', 'Bob', sep=' & ') # |Alice & Bob
|
|
359
|
+
|
|
360
|
+
ind = Indentation(word='-->', level=1)
|
|
361
|
+
ind(print)('Alice', 'Bob', sep=' & ') # |-->Alice & Bob
|
|
362
|
+
# Note that the parentheses wrap the `print`, not the entire statement.
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Note that if you save `ind(print)` for later use, it will bind
|
|
366
|
+
this `Indentation` object.
|
|
367
|
+
If you do not want the `print` to change with the `Indentation` object,
|
|
368
|
+
you can use the `fixed()` method.
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
ind = Indentation(word='-->', level=1)
|
|
372
|
+
|
|
373
|
+
print_indented = ind(print)
|
|
374
|
+
|
|
375
|
+
print_indented('Alice') # |-->Alice
|
|
376
|
+
|
|
377
|
+
ind.indent()
|
|
378
|
+
print_indented('Bob') # |-->-->Bob
|
|
379
|
+
|
|
380
|
+
# ===========================================
|
|
381
|
+
ind = Indentation(word='-->', level=1)
|
|
382
|
+
|
|
383
|
+
# Use ind.fixed() to fixed the indentation.
|
|
384
|
+
print_indented_fixed = ind.fixed(print)
|
|
385
|
+
|
|
386
|
+
print_indented_fixed('Alice') # |-->Alice
|
|
387
|
+
|
|
388
|
+
ind.indent()
|
|
389
|
+
print_indented_fixed('Bob') # |-->Bob
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
This software is distributed under the MIT License.
|
|
395
|
+
Please see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# indentoken
|
|
2
|
+
|
|
3
|
+
A magical little token to help you manage string indentation.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
* [Motivation](#motivation)
|
|
8
|
+
* [Features](#features)
|
|
9
|
+
* [Requirements](#requirements)
|
|
10
|
+
* [Installation](#installation)
|
|
11
|
+
* [Usage](#usage)
|
|
12
|
+
* [Basic Indentation and Dedentation](#basic-indentation-and-dedentation)
|
|
13
|
+
* [Using Context Manager for Indentation](#using-context-manager-for-indentation)
|
|
14
|
+
* [Multi-line Text Indentation](#multi-line-text-indentation)
|
|
15
|
+
* [Initialization](#initialization)
|
|
16
|
+
* [Padding](#padding)
|
|
17
|
+
* [Indentation Addition](#indentation-addition)
|
|
18
|
+
* [Multi-parameter Print](#multi-parameter-print)
|
|
19
|
+
* [License](#license)
|
|
20
|
+
|
|
21
|
+
## Motivation
|
|
22
|
+
|
|
23
|
+
Have you ever wanted your program output to look like this?
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
food
|
|
27
|
+
fruit
|
|
28
|
+
apple
|
|
29
|
+
banana
|
|
30
|
+
--------
|
|
31
|
+
meat
|
|
32
|
+
pork
|
|
33
|
+
beef
|
|
34
|
+
--------
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Normally, you need to manually track the current indentation depth:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
41
|
+
|
|
42
|
+
print('food')
|
|
43
|
+
for category, items in foods.items():
|
|
44
|
+
print(f' {category}')
|
|
45
|
+
# ^^ manually track the indentations
|
|
46
|
+
for item in items:
|
|
47
|
+
print(f' {item}')
|
|
48
|
+
# ^^^^ manually track the indentations
|
|
49
|
+
print(f' --------')
|
|
50
|
+
# ^^ manually track the indentations
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This method is not only very prone to errors, but it becomes even harder
|
|
54
|
+
to track the preceding whitespace when breaking down sub-loops
|
|
55
|
+
into functions.
|
|
56
|
+
|
|
57
|
+
How great would it be if there was a tool to help you track indentation.
|
|
58
|
+
|
|
59
|
+
This is exactly where indentoken can help!
|
|
60
|
+
|
|
61
|
+
Now you can write:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from indentoken import Indentation
|
|
65
|
+
|
|
66
|
+
ind = Indentation()
|
|
67
|
+
|
|
68
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
69
|
+
|
|
70
|
+
print('food')
|
|
71
|
+
|
|
72
|
+
with ind.indented_context():
|
|
73
|
+
for category, items in foods.items():
|
|
74
|
+
print(f'{ind}{category}')
|
|
75
|
+
|
|
76
|
+
with ind.indented_context():
|
|
77
|
+
for item in items:
|
|
78
|
+
print(f'{ind}{item}')
|
|
79
|
+
|
|
80
|
+
print(f'{ind}--------')
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Notice how the manual whitespace disappears, replaced by an `ind` object
|
|
84
|
+
that tracks the indentation depth and converts it to a string for you.
|
|
85
|
+
|
|
86
|
+
You can also pass it into a function to continuously track
|
|
87
|
+
the current indentation:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from collections.abc import Iterable
|
|
91
|
+
|
|
92
|
+
from indentoken import Indentation
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def main() -> None:
|
|
96
|
+
ind = Indentation()
|
|
97
|
+
|
|
98
|
+
foods = {'fruit': ['apple', 'banana'], 'meat': ['pork', 'beef']}
|
|
99
|
+
|
|
100
|
+
print('food')
|
|
101
|
+
|
|
102
|
+
with ind.indented_context():
|
|
103
|
+
for category, items in foods.items():
|
|
104
|
+
print(f'{ind}{category}')
|
|
105
|
+
|
|
106
|
+
with ind.indented_context():
|
|
107
|
+
show_food_items(items, ind=ind)
|
|
108
|
+
|
|
109
|
+
print(f'{ind}--------')
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def show_food_items(items: Iterable[str], *, ind: Indentation) -> None:
|
|
113
|
+
for item in items:
|
|
114
|
+
print(f'{ind}{item}')
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == '__main__':
|
|
118
|
+
main()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
There are more features, please see [Usage](#usage).
|
|
122
|
+
|
|
123
|
+
## Features
|
|
124
|
+
|
|
125
|
+
1. Multifunctional and convenient indentation tracking token.
|
|
126
|
+
2. Complete type annotations, supporting modern type checking
|
|
127
|
+
and type-safe development.
|
|
128
|
+
3. Zero package dependencies, so you don't have to worry about conflicts with
|
|
129
|
+
other packages in your virtual environment during installation.
|
|
130
|
+
4. Pure Python package, no C extensions, usable anywhere Python runs.
|
|
131
|
+
|
|
132
|
+
## Requirements
|
|
133
|
+
|
|
134
|
+
This package supports Python 3.10 and above.
|
|
135
|
+
|
|
136
|
+
## Installation
|
|
137
|
+
|
|
138
|
+
You can install this package using pip:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pip install indentoken
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Usage
|
|
145
|
+
|
|
146
|
+
The following examples assume you have imported:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from indentoken import Indentation
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Basic Indentation and Dedentation
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
ind = Indentation()
|
|
156
|
+
|
|
157
|
+
# Have no indentation at all initally.
|
|
158
|
+
print(f'{ind}Line 1') # |Line 1
|
|
159
|
+
|
|
160
|
+
# Indent by 1 level.
|
|
161
|
+
ind.indent()
|
|
162
|
+
print(f'{ind}Line 2') # | Line 2
|
|
163
|
+
|
|
164
|
+
# Indent by 3 levels, plus the previous 1 level.
|
|
165
|
+
# Now the indentation is 4 levels deep.
|
|
166
|
+
ind.indent(3)
|
|
167
|
+
print(f'{ind}Line 3') # | Line 3
|
|
168
|
+
|
|
169
|
+
# Dedent by 1 level.
|
|
170
|
+
# Now the indentation is 3 levels deep.
|
|
171
|
+
ind.dedent()
|
|
172
|
+
print(f'{ind}Line 4') # | Line 4
|
|
173
|
+
|
|
174
|
+
# Dedent by 99 levels.
|
|
175
|
+
# Note that the indentation level is always non-negative.
|
|
176
|
+
# The final indentation level will be clamped at 0.
|
|
177
|
+
ind.dedent(99)
|
|
178
|
+
assert ind.level == 0
|
|
179
|
+
print(f'{ind}Line 5') # |Line 5
|
|
180
|
+
|
|
181
|
+
# Directly set the current indentation level to 3.
|
|
182
|
+
ind.level = 3
|
|
183
|
+
print(f'{ind}Line 6') # | Line 6
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Using Context Manager for Indentation
|
|
187
|
+
|
|
188
|
+
If you want to increase indentation within a certain block, I recommend
|
|
189
|
+
using `with` combined with `indented_context()`.
|
|
190
|
+
This ensures that the indentation automatically reverts to
|
|
191
|
+
the correct depth even if an exception is raised.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
ind = Indentation()
|
|
195
|
+
|
|
196
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
197
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
198
|
+
with ind.indented_context():
|
|
199
|
+
print(f'{ind}This line is indented.') # | This line is indented.
|
|
200
|
+
print(f'{ind}This line is indented.') # | This line is indented.
|
|
201
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
202
|
+
print(f'{ind}This line is NOT indented.') # |This line is NOT indented.
|
|
203
|
+
with ind.indented_context(2):
|
|
204
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
205
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
206
|
+
with ind.indented_context(1): # nested indented context will stack
|
|
207
|
+
print(f'{ind}This line is indented by 3 levels.') # | This line is indented by 3 levels.
|
|
208
|
+
print(f'{ind}This line is indented by 3 levels.') # | This line is indented by 3 levels.
|
|
209
|
+
print(f'{ind}This line is indented by 2 levels.') # | This line is indented by 2 levels.
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The output you will get is like this:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
This line is NOT indented.
|
|
216
|
+
This line is NOT indented.
|
|
217
|
+
This line is indented.
|
|
218
|
+
This line is indented.
|
|
219
|
+
This line is NOT indented.
|
|
220
|
+
This line is NOT indented.
|
|
221
|
+
This line is indented by 2 levels.
|
|
222
|
+
This line is indented by 2 levels.
|
|
223
|
+
This line is indented by 3 levels.
|
|
224
|
+
This line is indented by 3 levels.
|
|
225
|
+
This line is indented by 2 levels.
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Multi-line Text Indentation
|
|
229
|
+
|
|
230
|
+
For multi-line text, you can directly wrap it in `ind`
|
|
231
|
+
or use the `apply_to()` method:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
ind = Indentation(level=1)
|
|
235
|
+
|
|
236
|
+
print('No indentation.')
|
|
237
|
+
print(ind('This is a multi-line text.\nAll lines will be indented.\nThe third line.'))
|
|
238
|
+
# ind('something') is equivalent to ind.apply_to('something')
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
It will output:
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
No indentation.
|
|
245
|
+
This is a multi-line text.
|
|
246
|
+
All lines will be indented.
|
|
247
|
+
The third line.
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Initialization
|
|
251
|
+
|
|
252
|
+
During initialization, you can specify the indentation characters,
|
|
253
|
+
initial indentation depth, and padding (which will be mentioned later).
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
ind = Indentation(word='-->', level=2)
|
|
257
|
+
print(f'{ind}Line 1') # |-->-->Line 1
|
|
258
|
+
ind.indent()
|
|
259
|
+
print(f'{ind}Line 2') # |-->-->-->Line 2
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Padding
|
|
263
|
+
|
|
264
|
+
If you want the starting point of the indentation not to begin
|
|
265
|
+
from the far left, you can specify padding:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# Use "+++++" as padding for visualization. You can use whitespaces.
|
|
269
|
+
ind = Indentation(word='-->', padding='+++++')
|
|
270
|
+
print(f'{ind}Line 1') # |+++++Line 1
|
|
271
|
+
ind.indent()
|
|
272
|
+
print(f'{ind}Line 2') # |+++++-->Line 2
|
|
273
|
+
ind.indent()
|
|
274
|
+
print(f'{ind}Line 3') # |+++++-->-->Line 3
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Padding does not change with indentation depth and it always exists,
|
|
278
|
+
even if the indentation depth is zero.
|
|
279
|
+
|
|
280
|
+
You can also specify the `Indentation` object as padding,
|
|
281
|
+
which gives you dynamic padding effects:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
pad = Indentation(word='++', level=2)
|
|
285
|
+
ind = Indentation(word='-->', padding=pad)
|
|
286
|
+
print(f'{ind}Line 1') # |++++Line 1
|
|
287
|
+
ind.indent()
|
|
288
|
+
print(f'{ind}Line 2') # |++++-->Line 2
|
|
289
|
+
ind.indent()
|
|
290
|
+
print(f'{ind}Line 3') # |++++-->-->Line 3
|
|
291
|
+
pad.dedent() # Note that the `pad` object is changing, not `ind`.
|
|
292
|
+
print(f'{ind}Line 4') # |++-->-->Line 4
|
|
293
|
+
|
|
294
|
+
# Convert the padding to `str` on init if you want a fixed padding.
|
|
295
|
+
pad = Indentation(word='++', level=2)
|
|
296
|
+
ind = Indentation(word='-->', level=1, padding=str(pad))
|
|
297
|
+
print(f'{ind}Line 5') # |++++-->Line 5
|
|
298
|
+
pad.dedent() # Note that the `pad` object is changing, not `ind`.
|
|
299
|
+
print(f'{ind}Line 6') # |++++-->Line 6
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Indentation Addition
|
|
303
|
+
|
|
304
|
+
If you temporarily need to deepen the indentation
|
|
305
|
+
without using the heavy `with` statement, you can directly add
|
|
306
|
+
the desired indentation depth to the indentation object.
|
|
307
|
+
|
|
308
|
+
Additionally, `Indentation` addition only changes
|
|
309
|
+
the indentation depth and does not change the padding.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
ind = Indentation(word='-->', level=1)
|
|
313
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
314
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
315
|
+
print(f'{ind + 1}one level deeper, two levels indented') # |-->-->one level deeper, two levels indented
|
|
316
|
+
print(f'{ind + 2}two levels deeper, three levels indented') # |-->-->-->two levels deeper, three levels indented
|
|
317
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
318
|
+
print(f'{ind}one level indented') # |-->one level indented
|
|
319
|
+
|
|
320
|
+
ind = Indentation(word='-->', level=1, padding='++')
|
|
321
|
+
print(f'{ind}one level indented') # |++-->one level indented
|
|
322
|
+
print(f'{ind + 1}one level deeper, two levels indented') # |++-->-->one level deeper, two levels indented
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Indentation addition is commutative:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
ind = Indentation(word='-->', level=2)
|
|
329
|
+
assert ind + 1 == 1 + ind
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Multi-parameter Print
|
|
333
|
+
|
|
334
|
+
If you want the following usage of `print` to also receive indentation,
|
|
335
|
+
you can wrap the `print` function with the `Indentation` object.
|
|
336
|
+
|
|
337
|
+
```python
|
|
338
|
+
print('Alice', 'Bob', sep=' & ') # |Alice & Bob
|
|
339
|
+
|
|
340
|
+
ind = Indentation(word='-->', level=1)
|
|
341
|
+
ind(print)('Alice', 'Bob', sep=' & ') # |-->Alice & Bob
|
|
342
|
+
# Note that the parentheses wrap the `print`, not the entire statement.
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Note that if you save `ind(print)` for later use, it will bind
|
|
346
|
+
this `Indentation` object.
|
|
347
|
+
If you do not want the `print` to change with the `Indentation` object,
|
|
348
|
+
you can use the `fixed()` method.
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
ind = Indentation(word='-->', level=1)
|
|
352
|
+
|
|
353
|
+
print_indented = ind(print)
|
|
354
|
+
|
|
355
|
+
print_indented('Alice') # |-->Alice
|
|
356
|
+
|
|
357
|
+
ind.indent()
|
|
358
|
+
print_indented('Bob') # |-->-->Bob
|
|
359
|
+
|
|
360
|
+
# ===========================================
|
|
361
|
+
ind = Indentation(word='-->', level=1)
|
|
362
|
+
|
|
363
|
+
# Use ind.fixed() to fixed the indentation.
|
|
364
|
+
print_indented_fixed = ind.fixed(print)
|
|
365
|
+
|
|
366
|
+
print_indented_fixed('Alice') # |-->Alice
|
|
367
|
+
|
|
368
|
+
ind.indent()
|
|
369
|
+
print_indented_fixed('Bob') # |-->Bob
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
This software is distributed under the MIT License.
|
|
375
|
+
Please see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "indentoken"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "An indentation token which helps you manage text indentations."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = []
|
|
10
|
+
keywords = [
|
|
11
|
+
"indentation",
|
|
12
|
+
"indent",
|
|
13
|
+
"dedent",
|
|
14
|
+
"text formatting",
|
|
15
|
+
"output formatting",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
23
|
+
"Topic :: Text Processing :: General",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[project.authors]]
|
|
29
|
+
name = "Rockmizu"
|
|
30
|
+
email = "Rockmizu@gmail.com"
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
34
|
+
build-backend = "uv_build"
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=9.1.1",
|
|
39
|
+
"ruff>=0.16.5",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 120
|
|
44
|
+
|
|
45
|
+
[tool.ruff.format]
|
|
46
|
+
quote-style = "single"
|
|
47
|
+
|
|
48
|
+
[tool.ruff.lint]
|
|
49
|
+
ignore = ["F541"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "indentoken"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "An indentation token which helps you manage text indentations."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Rockmizu", email = "Rockmizu@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = []
|
|
13
|
+
keywords = [
|
|
14
|
+
"indentation",
|
|
15
|
+
"indent",
|
|
16
|
+
"dedent",
|
|
17
|
+
"text formatting",
|
|
18
|
+
"output formatting",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: Text Processing :: General",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.12.5,<0.13.0"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=9.1.1",
|
|
38
|
+
"ruff>=0.16.5",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.ruff]
|
|
42
|
+
line-length = 120
|
|
43
|
+
|
|
44
|
+
[tool.ruff.format]
|
|
45
|
+
quote-style = "single"
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint]
|
|
48
|
+
ignore = [
|
|
49
|
+
"F541", # f-string without any placeholders
|
|
50
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
# The method rely on CPython implementation detail to work and should
|
|
6
|
+
# only been re-implemented when PEP 533
|
|
7
|
+
# Deterministic cleanup for iterators becomes available.
|
|
8
|
+
INDENTOKEN_ENABLE_IT_METHOD: Final = False
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import functools
|
|
5
|
+
import textwrap
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from typing import Any, Final, ParamSpec, TypeVar, overload
|
|
10
|
+
|
|
11
|
+
from .feature_flags import INDENTOKEN_ENABLE_IT_METHOD
|
|
12
|
+
|
|
13
|
+
__all__ = ['Indentation']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
T = TypeVar('T')
|
|
17
|
+
|
|
18
|
+
P = ParamSpec('P')
|
|
19
|
+
|
|
20
|
+
# A sentinel object
|
|
21
|
+
_MISSING: Final = object()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _wrap_print_with_post_process(
|
|
25
|
+
print_fn: Callable[P, Any],
|
|
26
|
+
post_process: Callable[[str], str],
|
|
27
|
+
) -> Callable[P, None]:
|
|
28
|
+
@functools.wraps(print_fn)
|
|
29
|
+
def print_wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
|
|
30
|
+
file = kwargs.pop('file', _MISSING)
|
|
31
|
+
flush = kwargs.pop('flush', _MISSING)
|
|
32
|
+
buffer = StringIO()
|
|
33
|
+
print_fn(*args, **kwargs, file=buffer, flush=False) # type: ignore[arg-type]
|
|
34
|
+
|
|
35
|
+
text = buffer.getvalue()
|
|
36
|
+
text = post_process(text)
|
|
37
|
+
|
|
38
|
+
output_kwargs: dict[str, Any] = {}
|
|
39
|
+
if file is not _MISSING:
|
|
40
|
+
output_kwargs['file'] = file
|
|
41
|
+
if flush is not _MISSING:
|
|
42
|
+
output_kwargs['flush'] = flush
|
|
43
|
+
|
|
44
|
+
print_fn(text, end='', **output_kwargs) # type: ignore[arg-type]
|
|
45
|
+
|
|
46
|
+
return print_wrapper
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(slots=True, init=False, eq=False)
|
|
50
|
+
class Indentation:
|
|
51
|
+
"""
|
|
52
|
+
A magic token that can be used as an indentation string.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
word: str = ' '
|
|
56
|
+
_level: int = field(init=False)
|
|
57
|
+
padding: str | Indentation = field(default='', kw_only=True)
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
word: str = ' ',
|
|
62
|
+
level: int = 0,
|
|
63
|
+
*,
|
|
64
|
+
padding: str | Indentation = '',
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Initialize the Indentation object.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
word: The string used for indentation (e.g., ' ').
|
|
71
|
+
level: The initial indentation level. Must be non-negative.
|
|
72
|
+
padding: Optional padding object or string.
|
|
73
|
+
"""
|
|
74
|
+
if level < 0:
|
|
75
|
+
raise ValueError('`level` must be non-negative')
|
|
76
|
+
self.word = word
|
|
77
|
+
self.level = level
|
|
78
|
+
self.padding = padding
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def level(self) -> int:
|
|
82
|
+
"""
|
|
83
|
+
Get the current indentation level.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The current indentation level.
|
|
87
|
+
"""
|
|
88
|
+
return self._level
|
|
89
|
+
|
|
90
|
+
@level.setter
|
|
91
|
+
def level(self, level: int, /) -> None:
|
|
92
|
+
"""
|
|
93
|
+
Set the current indentation level.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
level: The new indentation level.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: If the provided `level` is negative.
|
|
100
|
+
"""
|
|
101
|
+
if level < 0:
|
|
102
|
+
raise ValueError('`level` must be non-negative')
|
|
103
|
+
self._level = level
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def padding_str(self) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Get the padding string representation.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The string representation of the padding.
|
|
112
|
+
"""
|
|
113
|
+
return str(self.padding)
|
|
114
|
+
|
|
115
|
+
def copy(self) -> Indentation:
|
|
116
|
+
"""
|
|
117
|
+
Create a copy of the current Indentation object.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
A new Indentation instance with the same properties.
|
|
121
|
+
"""
|
|
122
|
+
return Indentation(
|
|
123
|
+
word=self.word,
|
|
124
|
+
level=self._level,
|
|
125
|
+
padding=self.padding,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def indent(self, delta: int = 1, /) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Increase the indentation level by `delta`.
|
|
131
|
+
|
|
132
|
+
* Positive values increase the indentation level.
|
|
133
|
+
* Negative values decrease the indentation level.
|
|
134
|
+
* The level is clamped at zero.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
delta: The amount to increase the level by. Defaults to 1.
|
|
138
|
+
"""
|
|
139
|
+
self._level = max(0, self.level + delta)
|
|
140
|
+
|
|
141
|
+
def dedent(self, delta: int = 1, /) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Decrease the indentation level by `delta`.
|
|
144
|
+
|
|
145
|
+
* Positive values decrease the indentation level.
|
|
146
|
+
* Negative values increase the indentation level.
|
|
147
|
+
* The level is clamped at zero.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
delta: The amount to decrease the level by. Defaults to 1.
|
|
151
|
+
"""
|
|
152
|
+
self._level = max(0, self.level - delta)
|
|
153
|
+
|
|
154
|
+
@contextlib.contextmanager
|
|
155
|
+
def indented_context(self, level: int = 1, /):
|
|
156
|
+
"""
|
|
157
|
+
Context manager to temporarily change the indentation level.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
level: The amount to change the indentation level by. Defaults to 1.
|
|
161
|
+
|
|
162
|
+
Yields:
|
|
163
|
+
Indentation: The current Indentation instance within the context.
|
|
164
|
+
"""
|
|
165
|
+
if level < -self._level: # noqa: PLR1730
|
|
166
|
+
level = -self._level
|
|
167
|
+
|
|
168
|
+
self.indent(level)
|
|
169
|
+
try:
|
|
170
|
+
yield self
|
|
171
|
+
finally:
|
|
172
|
+
self.dedent(level)
|
|
173
|
+
|
|
174
|
+
def apply_to(self, text: str, /) -> str:
|
|
175
|
+
"""
|
|
176
|
+
Apply the current indentation to a given string.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
text: The string to be indented.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
The indented string.
|
|
183
|
+
"""
|
|
184
|
+
return textwrap.indent(text, prefix=str(self))
|
|
185
|
+
|
|
186
|
+
def fixed(self, print_fn: Callable[P, Any], /) -> Callable[P, None]:
|
|
187
|
+
"""
|
|
188
|
+
Create a print function wrapper that applies indentation.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
print_fn: The original print function to wrap.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A wrapped print function that applies indentation.
|
|
195
|
+
"""
|
|
196
|
+
return _wrap_print_with_post_process(
|
|
197
|
+
print_fn,
|
|
198
|
+
functools.partial(textwrap.indent, prefix=str(self)),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if INDENTOKEN_ENABLE_IT_METHOD:
|
|
202
|
+
|
|
203
|
+
def it(self, iterable: Iterable[T], /, delta: int = 1) -> Iterable[T]:
|
|
204
|
+
"""
|
|
205
|
+
Wrap an iterable to provide the effect of `indented_context()`
|
|
206
|
+
before the iteration completes.
|
|
207
|
+
|
|
208
|
+
This method is particularly useful when you want additional
|
|
209
|
+
indentation during a `for` loop without adding an extra layer of
|
|
210
|
+
indentation to the source code.
|
|
211
|
+
|
|
212
|
+
E.g. You can write
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
ind = Indentation()
|
|
216
|
+
|
|
217
|
+
for item in ind.it(items):
|
|
218
|
+
print(f'{ind}{item}')
|
|
219
|
+
|
|
220
|
+
print(f'{ind}no indentation after loop')
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
instead of
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
ind = Indentation()
|
|
227
|
+
|
|
228
|
+
with ind.indented_context():
|
|
229
|
+
for item in items:
|
|
230
|
+
print(f'{ind}{item}')
|
|
231
|
+
|
|
232
|
+
print(f'{ind}no indentation after loop')
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
iterable: The iterable to yield from.
|
|
237
|
+
delta: The amount to change the indentation level by. Defaults to 1.
|
|
238
|
+
|
|
239
|
+
Yields:
|
|
240
|
+
Elements from the iterable, indented.
|
|
241
|
+
"""
|
|
242
|
+
with self.indented_context(delta):
|
|
243
|
+
yield from iterable
|
|
244
|
+
|
|
245
|
+
@overload
|
|
246
|
+
def __call__(self, text: str, /) -> str: ...
|
|
247
|
+
@overload
|
|
248
|
+
def __call__(self, print_fn: Callable[P, Any], /) -> Callable[P, None]: ...
|
|
249
|
+
def __call__(self, text_or_print_fn: str | Callable[P, Any], /) -> str | Callable[P, None]:
|
|
250
|
+
"""
|
|
251
|
+
Apply the current indentation to a given text or
|
|
252
|
+
wrap a print function to apply indentation to its output.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
text_or_print_fn: The string to indent, or the print function to wrap.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
The indented string if `text_or_print_fn` is a string, or the wrapped print function if it is a callable.
|
|
259
|
+
"""
|
|
260
|
+
if isinstance(text_or_print_fn, str):
|
|
261
|
+
return self.apply_to(text_or_print_fn)
|
|
262
|
+
|
|
263
|
+
return _wrap_print_with_post_process(
|
|
264
|
+
text_or_print_fn,
|
|
265
|
+
self.apply_to,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def __add__(self, amount: int, /) -> str:
|
|
269
|
+
"""
|
|
270
|
+
Return a string, which is the stringified result
|
|
271
|
+
as if adding `delta` level to this object.
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
ind = Indentation('->', level=2)
|
|
275
|
+
s1 = ind + 3 # s1 = '->->->->->'
|
|
276
|
+
assert ind.level == 2 # won't affect the original object
|
|
277
|
+
ind.indent(2)
|
|
278
|
+
s2 = str(ind) # s2 = '->->->->->'
|
|
279
|
+
assert s1 == s2
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
This is useful when you suddenly need more indentation levels
|
|
283
|
+
but don't want to use a context manager.
|
|
284
|
+
|
|
285
|
+
E.g.:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
ind = Indentation()
|
|
289
|
+
|
|
290
|
+
print(f'{ind}not indented')
|
|
291
|
+
with ind.indented_context():
|
|
292
|
+
print(f'{ind}one level indented')
|
|
293
|
+
print(f'{ind}one level indented')
|
|
294
|
+
print(f'{ind + 1}one level deeper; two level indented')
|
|
295
|
+
print(f'{ind}one level indented')
|
|
296
|
+
print(f'{ind}one level indented')
|
|
297
|
+
|
|
298
|
+
# will output:
|
|
299
|
+
# not indented
|
|
300
|
+
# one level indented
|
|
301
|
+
# one level indented
|
|
302
|
+
# one level deeper; two level indented
|
|
303
|
+
# one level indented
|
|
304
|
+
# one level indented
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
times: The number of times to append the indentation string.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
The extended indentation string.
|
|
312
|
+
"""
|
|
313
|
+
return str(self) + self.word * amount
|
|
314
|
+
|
|
315
|
+
def __radd__(self, amount: int, /) -> str:
|
|
316
|
+
"""
|
|
317
|
+
Return a string, which is the stringified result
|
|
318
|
+
as if adding `amount` level to this object.
|
|
319
|
+
|
|
320
|
+
This is useful when you suddenly need more indentation levels
|
|
321
|
+
but don't want to use a context manager.
|
|
322
|
+
|
|
323
|
+
E.g.:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
ind = Indentation()
|
|
327
|
+
|
|
328
|
+
print(f'{ind}not indented')
|
|
329
|
+
with ind.indented_context():
|
|
330
|
+
print(f'{ind}one level indented')
|
|
331
|
+
print(f'{ind}one level indented')
|
|
332
|
+
print(f'{1 + ind}one level deeper; two level indented')
|
|
333
|
+
print(f'{ind}one level indented')
|
|
334
|
+
print(f'{ind}one level indented')
|
|
335
|
+
|
|
336
|
+
# will output:
|
|
337
|
+
# not indented
|
|
338
|
+
# one level indented
|
|
339
|
+
# one level indented
|
|
340
|
+
# one level deeper; two level indented
|
|
341
|
+
# one level indented
|
|
342
|
+
# one level indented
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
times: The number of times to append the indentation string.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
The extended indentation string.
|
|
350
|
+
"""
|
|
351
|
+
return str(self) + self.word * amount
|
|
352
|
+
|
|
353
|
+
def __str__(self) -> str:
|
|
354
|
+
"""
|
|
355
|
+
Return the string representation of the indentation.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
The string representing the indentation (padding + word * level).
|
|
359
|
+
"""
|
|
360
|
+
return str(self.padding) + self.word * self._level
|
|
File without changes
|