nya-codeblock 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nya_codeblock-0.1.0/.editorconfig +9 -0
- nya_codeblock-0.1.0/.gitignore +10 -0
- nya_codeblock-0.1.0/.gitmodules +3 -0
- nya_codeblock-0.1.0/.python-version +1 -0
- nya_codeblock-0.1.0/PKG-INFO +10 -0
- nya_codeblock-0.1.0/README.md +3 -0
- nya_codeblock-0.1.0/pypi-uv-publish.sh/LICENSE +21 -0
- nya_codeblock-0.1.0/pypi-uv-publish.sh/README.md +10 -0
- nya_codeblock-0.1.0/pypi-uv-publish.sh/publish.sh +24 -0
- nya_codeblock-0.1.0/pyproject.toml +18 -0
- nya_codeblock-0.1.0/ruff.toml +606 -0
- nya_codeblock-0.1.0/src/nya_codeblock/__init__.py +9 -0
- nya_codeblock-0.1.0/src/nya_codeblock/__main__.py +0 -0
- nya_codeblock-0.1.0/src/nya_codeblock/_version.py +1 -0
- nya_codeblock-0.1.0/src/nya_codeblock/codeblock.py +120 -0
- nya_codeblock-0.1.0/src/nya_codeblock/uncodeblock.py +19 -0
- nya_codeblock-0.1.0/tests/__init__.py +0 -0
- nya_codeblock-0.1.0/tests/test_codeblock.py +25 -0
- nya_codeblock-0.1.0/uv.lock +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nya-codeblock
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Provide helpful functions to convert str into a markdown/discord codeblock. Ported from my own package, tcrutils.
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# nya-codeblock
|
|
9
|
+
|
|
10
|
+
Provide helpful functions to convert str into a markdown/discord codeblock. Ported from my own package, tcrutils.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 anamoyee
|
|
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,10 @@
|
|
|
1
|
+
# A git submodule for easy deployment of python packages using uv and GNU pass
|
|
2
|
+
|
|
3
|
+
Fork if your pass path differs
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
Once per repository:
|
|
7
|
+
`git submodule add https://github.com/anamoyee/pypi-uv-publish.sh`
|
|
8
|
+
|
|
9
|
+
Every time you want to publish to PyPI:
|
|
10
|
+
`./pypi-uv-publish.sh/publish.sh`
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
|
|
5
|
+
cd .. # meant to be used as a submodule that resides at root of the pyproject.
|
|
6
|
+
|
|
7
|
+
# build, then publish via uv
|
|
8
|
+
|
|
9
|
+
# assume PYPI token is stored in GNU password-store
|
|
10
|
+
# at /pypi_tokens/{TOKEN_NAME}
|
|
11
|
+
|
|
12
|
+
TOKEN_NAME="__main__"
|
|
13
|
+
|
|
14
|
+
if [[ TOKEN_NAME == "" ]]; then
|
|
15
|
+
echo "Provide a TOKEN_NAME in a form of a (GNU password-store) path" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
rm ./dist -rf # remove builds of old versions, dont upload more and more every time
|
|
20
|
+
|
|
21
|
+
uv build
|
|
22
|
+
|
|
23
|
+
UV_PUBLISH_USERNAME="__token__" UV_PUBLISH_PASSWORD="$(/usr/bin/env pass "pypi_tokens/${TOKEN_NAME}")" uv publish
|
|
24
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = 'nya-codeblock'
|
|
3
|
+
dynamic = ['version']
|
|
4
|
+
description = 'Provide helpful functions to convert str into a markdown/discord codeblock. Ported from my own package, tcrutils.'
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["hatchling"]
|
|
11
|
+
build-backend = "hatchling.build"
|
|
12
|
+
|
|
13
|
+
[tool.hatch.version]
|
|
14
|
+
path = "src/nya_codeblock/_version.py"
|
|
15
|
+
pattern = '__version__(?:: str)? *= *\"(?P<version>[^\"]+)\"'
|
|
16
|
+
|
|
17
|
+
[dependency-groups]
|
|
18
|
+
dev = ["pytest>=9.1.1"]
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
# Exclude a variety of commonly ignored directories.
|
|
2
|
+
exclude = [ #
|
|
3
|
+
".bzr",
|
|
4
|
+
".direnv",
|
|
5
|
+
".eggs",
|
|
6
|
+
".git",
|
|
7
|
+
".git-rewrite",
|
|
8
|
+
".hg",
|
|
9
|
+
".ipynb_checkpoints",
|
|
10
|
+
".mypy_cache",
|
|
11
|
+
".nox",
|
|
12
|
+
".pants.d",
|
|
13
|
+
".pyenv",
|
|
14
|
+
".pytest_cache",
|
|
15
|
+
".pytype",
|
|
16
|
+
".ruff_cache",
|
|
17
|
+
".svn",
|
|
18
|
+
".tox",
|
|
19
|
+
".venv",
|
|
20
|
+
"venv",
|
|
21
|
+
".vscode",
|
|
22
|
+
"__pypackages__",
|
|
23
|
+
"_build",
|
|
24
|
+
"buck-out",
|
|
25
|
+
"build",
|
|
26
|
+
"dist",
|
|
27
|
+
"node_modules",
|
|
28
|
+
"site-packages",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
line-length = 150
|
|
32
|
+
indent-width = 4
|
|
33
|
+
|
|
34
|
+
target-version = "py314"
|
|
35
|
+
|
|
36
|
+
[format]
|
|
37
|
+
quote-style = "double"
|
|
38
|
+
indent-style = "tab"
|
|
39
|
+
|
|
40
|
+
nested-string-quote-style = "preferred"
|
|
41
|
+
|
|
42
|
+
skip-magic-trailing-comma = false
|
|
43
|
+
|
|
44
|
+
line-ending = "auto"
|
|
45
|
+
|
|
46
|
+
docstring-code-format = true
|
|
47
|
+
docstring-code-line-length = "dynamic"
|
|
48
|
+
|
|
49
|
+
[lint]
|
|
50
|
+
# Allow select greek letters
|
|
51
|
+
allowed-confusables = ["α", "β", "γ", "δ", "ε", "ζ", "η", "θ", "κ", "λ", "μ", "ξ", "π", "σ", "ς", "τ", "φ", "ψ", "ω", "Γ", "Δ", "Π", "Σ", "Ω"]
|
|
52
|
+
|
|
53
|
+
preview = true
|
|
54
|
+
|
|
55
|
+
pydocstyle.convention = "google"
|
|
56
|
+
# (using: )
|
|
57
|
+
#
|
|
58
|
+
# Args:
|
|
59
|
+
# whatever
|
|
60
|
+
#
|
|
61
|
+
# (instead of: )
|
|
62
|
+
#
|
|
63
|
+
# Args
|
|
64
|
+
# ----
|
|
65
|
+
# whatever
|
|
66
|
+
|
|
67
|
+
flake8-annotations.suppress-dummy-args = true
|
|
68
|
+
flake8-annotations.suppress-none-returning = true
|
|
69
|
+
|
|
70
|
+
extend-per-file-ignores = { "tests/**/*.py" = ["ANN"], "test*.py" = ["ANN"] } # don't require annotations for tests
|
|
71
|
+
|
|
72
|
+
select = [
|
|
73
|
+
# commented-out code
|
|
74
|
+
#"ERA001",
|
|
75
|
+
|
|
76
|
+
# FastAPI
|
|
77
|
+
"FAST",
|
|
78
|
+
|
|
79
|
+
# flake8-2020
|
|
80
|
+
"YTT101",
|
|
81
|
+
"YTT102",
|
|
82
|
+
"YTT103",
|
|
83
|
+
|
|
84
|
+
"YTT201",
|
|
85
|
+
"YTT202",
|
|
86
|
+
"YTT203",
|
|
87
|
+
"YTT204",
|
|
88
|
+
|
|
89
|
+
"YTT301", # "python 10.0" :sob:
|
|
90
|
+
"YTT302",
|
|
91
|
+
"YTT303",
|
|
92
|
+
|
|
93
|
+
# flake8-annotations
|
|
94
|
+
"ANN001",
|
|
95
|
+
"ANN002",
|
|
96
|
+
"ANN003",
|
|
97
|
+
"ANN201",
|
|
98
|
+
"ANN202",
|
|
99
|
+
#"ANN204", # obvious methods shouldn't be annotated (think: __init__ -> None, __repr__ -> str)
|
|
100
|
+
"ANN205",
|
|
101
|
+
"ANN206",
|
|
102
|
+
|
|
103
|
+
# flake8-async
|
|
104
|
+
"ASYNC",
|
|
105
|
+
|
|
106
|
+
# flake8-bandit
|
|
107
|
+
"S113", # request without a timeout
|
|
108
|
+
|
|
109
|
+
# flake8 boolean fly trap
|
|
110
|
+
"FBT001", # def f(b: bool): ... (should be (*, b: ...) )
|
|
111
|
+
"FBT002", # def f(b=False): ... (should be (*, b=...) )
|
|
112
|
+
# Specifically not opting in for the caller side (FBT003), only definition side. This could prevent integration with badly written libraries...
|
|
113
|
+
|
|
114
|
+
# flake8-bugbear
|
|
115
|
+
"B002",
|
|
116
|
+
"B003",
|
|
117
|
+
"B004",
|
|
118
|
+
"B005",
|
|
119
|
+
"B007",
|
|
120
|
+
"B009",
|
|
121
|
+
"B010",
|
|
122
|
+
"B011",
|
|
123
|
+
"B012",
|
|
124
|
+
"B013",
|
|
125
|
+
"B014",
|
|
126
|
+
"B015",
|
|
127
|
+
"B016",
|
|
128
|
+
"B018",
|
|
129
|
+
"B019",
|
|
130
|
+
"B020",
|
|
131
|
+
"B021",
|
|
132
|
+
"B022",
|
|
133
|
+
"B023",
|
|
134
|
+
"B024",
|
|
135
|
+
"B025",
|
|
136
|
+
"B026",
|
|
137
|
+
"B027",
|
|
138
|
+
"B028",
|
|
139
|
+
"B029",
|
|
140
|
+
"B030",
|
|
141
|
+
"B031",
|
|
142
|
+
"B033",
|
|
143
|
+
"B034",
|
|
144
|
+
"B035",
|
|
145
|
+
"B039",
|
|
146
|
+
"B043",
|
|
147
|
+
"B901",
|
|
148
|
+
"B903",
|
|
149
|
+
"B904",
|
|
150
|
+
"B905",
|
|
151
|
+
"B909",
|
|
152
|
+
"B911",
|
|
153
|
+
"B912",
|
|
154
|
+
|
|
155
|
+
# flake8-builtins
|
|
156
|
+
"A001",
|
|
157
|
+
"A002",
|
|
158
|
+
"A003",
|
|
159
|
+
"A004",
|
|
160
|
+
"A005",
|
|
161
|
+
# explicitly opting out of A006, since lambda argument shadowing seems a bit too strict..
|
|
162
|
+
|
|
163
|
+
# flake8-commas
|
|
164
|
+
#"COM812", trailing comma - should be handled by the magic comma ruff formatter or whatever
|
|
165
|
+
"COM818",
|
|
166
|
+
"COM819",
|
|
167
|
+
|
|
168
|
+
# flake8-comprehensions
|
|
169
|
+
"C400",
|
|
170
|
+
"C401",
|
|
171
|
+
"C402",
|
|
172
|
+
"C403",
|
|
173
|
+
"C404",
|
|
174
|
+
"C405",
|
|
175
|
+
"C406",
|
|
176
|
+
# specifically opting out of C408, since doing dict(x=1, y=2) is a common pattern that i like to use sometimes
|
|
177
|
+
"C409",
|
|
178
|
+
"C410",
|
|
179
|
+
"C411",
|
|
180
|
+
"C413",
|
|
181
|
+
"C414",
|
|
182
|
+
"C415",
|
|
183
|
+
"C416",
|
|
184
|
+
"C417",
|
|
185
|
+
"C418", # owo
|
|
186
|
+
"C419",
|
|
187
|
+
"C420",
|
|
188
|
+
|
|
189
|
+
# flake8-datetimez
|
|
190
|
+
"DTZ001",
|
|
191
|
+
"DTZ002",
|
|
192
|
+
"DTZ003",
|
|
193
|
+
"DTZ004",
|
|
194
|
+
"DTZ005",
|
|
195
|
+
"DTZ006",
|
|
196
|
+
"DTZ007",
|
|
197
|
+
"DTZ011",
|
|
198
|
+
"DTZ012",
|
|
199
|
+
"DTZ901",
|
|
200
|
+
|
|
201
|
+
# flake8-debugger
|
|
202
|
+
"T100",
|
|
203
|
+
|
|
204
|
+
# flake8-django
|
|
205
|
+
"DJ", # selecting entire category, since i dont know much about django and any help would be nice!
|
|
206
|
+
|
|
207
|
+
# flake8-errmsg
|
|
208
|
+
"EM101", # Note for self: instead of removing those rules try to actually follow the `msg = ...; raise E(msg)` pattern.
|
|
209
|
+
"EM102",
|
|
210
|
+
"EM103",
|
|
211
|
+
|
|
212
|
+
# flake8-executable
|
|
213
|
+
"EXE001",
|
|
214
|
+
"EXE002",
|
|
215
|
+
"EXE003",
|
|
216
|
+
"EXE004",
|
|
217
|
+
"EXE005",
|
|
218
|
+
|
|
219
|
+
# flake8-fixme
|
|
220
|
+
"FIX001", # 'FIXME'
|
|
221
|
+
"FIX002", # 'TODO'
|
|
222
|
+
"FIX004", # 'HACK'
|
|
223
|
+
|
|
224
|
+
# flake8-gettext
|
|
225
|
+
"INT001",
|
|
226
|
+
"INT002",
|
|
227
|
+
"INT003",
|
|
228
|
+
|
|
229
|
+
# flake8-implicit-str-concat
|
|
230
|
+
"ISC001",
|
|
231
|
+
"ISC002",
|
|
232
|
+
"ISC003",
|
|
233
|
+
"ISC004",
|
|
234
|
+
|
|
235
|
+
# flake8-import-conventions
|
|
236
|
+
"ICN001",
|
|
237
|
+
"ICN002",
|
|
238
|
+
"ICN003",
|
|
239
|
+
|
|
240
|
+
# flake8-logging & flake8-logging-format
|
|
241
|
+
"LOG", # Just get all the logging rules, though i'm not so sure about using the default camelCase logging module...
|
|
242
|
+
"G",
|
|
243
|
+
|
|
244
|
+
# flake8-no-pep420 (forbid namespace packages)
|
|
245
|
+
"INP001",
|
|
246
|
+
|
|
247
|
+
# flake8-pie
|
|
248
|
+
"PIE790",
|
|
249
|
+
"PIE794",
|
|
250
|
+
"PIE796",
|
|
251
|
+
"PIE800",
|
|
252
|
+
"PIE804",
|
|
253
|
+
"PIE807",
|
|
254
|
+
"PIE808",
|
|
255
|
+
"PIE810",
|
|
256
|
+
|
|
257
|
+
# flake8-pyi
|
|
258
|
+
"PYI", # we'll see if this holds, because if i open up somebody else's .pyi and ruff starts yelling at me, i'm tossing this shit the hell out
|
|
259
|
+
|
|
260
|
+
# flake8-pytest-style
|
|
261
|
+
"PT",
|
|
262
|
+
|
|
263
|
+
# flake8-return
|
|
264
|
+
"RET",
|
|
265
|
+
|
|
266
|
+
# flake8-simplify
|
|
267
|
+
"SIM",
|
|
268
|
+
|
|
269
|
+
# flake8-slots
|
|
270
|
+
"SLOT",
|
|
271
|
+
|
|
272
|
+
# flake8-type-checking
|
|
273
|
+
"TC",
|
|
274
|
+
|
|
275
|
+
# flake8-unused-arguments
|
|
276
|
+
"ARG005", # opt in only for lambdas
|
|
277
|
+
|
|
278
|
+
# flake8-use-pathlib
|
|
279
|
+
"PTH",
|
|
280
|
+
|
|
281
|
+
# flynt
|
|
282
|
+
"FLY002",
|
|
283
|
+
|
|
284
|
+
# isort
|
|
285
|
+
"I001",
|
|
286
|
+
|
|
287
|
+
# numpy
|
|
288
|
+
"NPY",
|
|
289
|
+
|
|
290
|
+
# pandas
|
|
291
|
+
"PD",
|
|
292
|
+
|
|
293
|
+
# pep8-naming
|
|
294
|
+
"N804", # method first argument should be `self`
|
|
295
|
+
"N805", # classmethod first argument should be `cls`
|
|
296
|
+
"N811", # constant imported as non-constant-looking name
|
|
297
|
+
"N818", # 'Error' suffix on exception typedefs
|
|
298
|
+
|
|
299
|
+
# perflint
|
|
300
|
+
"PERF101",
|
|
301
|
+
"PERF102",
|
|
302
|
+
|
|
303
|
+
"PERF401",
|
|
304
|
+
"PERF402",
|
|
305
|
+
"PERF403",
|
|
306
|
+
|
|
307
|
+
# pycodestyle (error)
|
|
308
|
+
"E101", # mixed spaces and tabs
|
|
309
|
+
"E711", # none comparison using `==`, should be using `is`
|
|
310
|
+
"E712", # comparison `== True` or `== False`
|
|
311
|
+
"E713", # 'not x in y' -> 'x not in y'
|
|
312
|
+
"E714", # 'not x is y' -> 'x is not y'
|
|
313
|
+
"E722", # bare 'except:'
|
|
314
|
+
"E731", # do not assign a lambda expression, use a def
|
|
315
|
+
"E902", # IOError (ruff itself cannot read a file)
|
|
316
|
+
|
|
317
|
+
# pycodestyle (warning)
|
|
318
|
+
"W605", # invalid escape sequence
|
|
319
|
+
|
|
320
|
+
# pydoclint
|
|
321
|
+
"DOC",
|
|
322
|
+
|
|
323
|
+
# pydocstyle
|
|
324
|
+
# "D1", # 'thing X should be documented'
|
|
325
|
+
# "D417", # 'function/method param X should be documented within the already documented function.'
|
|
326
|
+
|
|
327
|
+
"D200", # docstring could be on a single line
|
|
328
|
+
"D201", # 'unwanted blank between def and """', specifically opting out of 'blank between def's """ and later code', since this blurs the text together.
|
|
329
|
+
"D207", # docstring under-indented
|
|
330
|
+
"D208", # docstring over-indented
|
|
331
|
+
"D204", # 'missing blank between class's """ and class body'
|
|
332
|
+
"D209", # multiline docstring should have their ending quotes on a new line
|
|
333
|
+
"D210", # unwanted whitespace around docstring text. """ This. """ should be """This."""
|
|
334
|
+
"D211", # 'unwanted blank between class and """'
|
|
335
|
+
"D212", # multiline docstring's first line should be on the same line as """
|
|
336
|
+
"D214", # docstring section over-indented
|
|
337
|
+
"D300", # docstring str should be using " quotes (as opposed to ' quotes)
|
|
338
|
+
"D301", # use r""" if any backslashes in docstring
|
|
339
|
+
"D400", # docstring should end in a period
|
|
340
|
+
"D403", # docstring first word should be capitalized
|
|
341
|
+
"D405", # docstring section name not properly capitalized (args -> Args)
|
|
342
|
+
"D412", # unwanted newline between docstring section and the section's content
|
|
343
|
+
"D416", # missing section names' ":", (Args -> Args:)
|
|
344
|
+
"D418", # overloads should not be documented separately
|
|
345
|
+
"D419", # docstring content missing entirely. ("""""")
|
|
346
|
+
"D420", # docstring sections in correct order
|
|
347
|
+
|
|
348
|
+
# pyflakes
|
|
349
|
+
"F401", # unused import
|
|
350
|
+
"F50", # various helpful %-format rules
|
|
351
|
+
"F52", # various helpful str.format() rules
|
|
352
|
+
"F541", # f-string without any placeholders
|
|
353
|
+
"F60", # in dictionary literal: key repeated
|
|
354
|
+
"F622", # `a, *b, c, *d, e = f` is invalid syntax (notice the two '*'s)
|
|
355
|
+
"F631", # `assert (...,)` where tuple has at least one element is always true
|
|
356
|
+
"F634", # same as above but for `if (...,)`
|
|
357
|
+
"F632", # use == when comparing to literals that arent None, True, False, etc.
|
|
358
|
+
"F707", # order of exception handlers in try-except is wrong (most general handler should be last)
|
|
359
|
+
"F811", # redefinition of unused name from line X
|
|
360
|
+
"F822", # undefined name in __all__
|
|
361
|
+
"F842", # unused annotation (`foo: int`)
|
|
362
|
+
"F901", # raise NotImplemented should be raise NotImplementedError
|
|
363
|
+
|
|
364
|
+
# pygrep-hooks
|
|
365
|
+
# "PGH003", # blanket `type: ignore` (a specific `type: ignore[whatever]` should be provided)
|
|
366
|
+
# "PGH004", # blanket `# noqa` (a specific `noqa: whatever` should be provided)
|
|
367
|
+
"PGH005", # blanket mock attribute access, where it should be a function call (e.g. `mock.assert_called` should be `mock.assert_called()`)
|
|
368
|
+
|
|
369
|
+
# pylint
|
|
370
|
+
"PLC0105", # typevar variance should be reflected in the name (T_co, U_contra)
|
|
371
|
+
"PLC0131", # typevar cannot be covariant and contravariant at the same time
|
|
372
|
+
"PLC0132", # typevar's bound name should be the same as declared name (`T = TypeVar("T")`, not `T = TypeVar("U")`)
|
|
373
|
+
"PLC0205", # __slots__ should be a tuple, not `__slots__ = "asdf"` (instead: `__slots__ = ("asdf",)`)
|
|
374
|
+
"PLC0206", # iteration over dict where it could be `.items()`
|
|
375
|
+
"PLC0207", # in str.split()[int], a maxsplit should be used, since it gets getitemed anyway
|
|
376
|
+
"PLC0208", # iteration over set literal, where tuple literal is faster to iterate over.
|
|
377
|
+
"PLC1802", # do not use `if len(x):`, use `if x:` instead
|
|
378
|
+
"PLC1901", # a variable of type `str` should not be `== ""`, use `not x` instead
|
|
379
|
+
|
|
380
|
+
"PLE010", # do not use `yield` or `return` in `__init__`
|
|
381
|
+
"PLE0115", # use of `nonlocal` and `global` at the same time on the same identifier
|
|
382
|
+
"PLE0116", # `continue` in `finally`
|
|
383
|
+
"PLE0117", # `nonlocal` used, but the nonlocal scope doesnt define that variable
|
|
384
|
+
"PLE0118", # `global` keyword should be used at the top of the scope
|
|
385
|
+
"PLE0237", # assignment outside of defined `__slots__`
|
|
386
|
+
"PLE0241", # duplicate base in class definition
|
|
387
|
+
"PLE030", # unexpected signature or return types of a magic methods (e.g. `def __len__(self, wtf): ...`)
|
|
388
|
+
"PLE060", # __all__ invalid
|
|
389
|
+
"PLE0643", # literal expression likely to raise IndexError (e.g. `[1, 2, 3][3]`)
|
|
390
|
+
"PLE0704", # misplaced bare `raise`
|
|
391
|
+
"PLE1141", # unpacking dict to pairs without calling .items()
|
|
392
|
+
"PLE12", # logging: too many or too few args
|
|
393
|
+
"PLE13", # invalid string formatting (e.g. '%z' % 'a' - invalid format specifier 'z'), and others
|
|
394
|
+
"PLE1519", # on methods use `@singledispatchmethod` instead of `@singledispatch`
|
|
395
|
+
"PLE1520", # same as above but in reverse for non-methods
|
|
396
|
+
"PLE2502", # check for unicode RTL in source code (could slip backdoors in, if not careful, since would look like a comment)
|
|
397
|
+
"PLE251", # actual control characters in source code, use escaped versions
|
|
398
|
+
"PLE4703", # set modified during iteration will cause a RuntimeError
|
|
399
|
+
|
|
400
|
+
"PLR0124", # x==x, convert to True or math.isnan(x)
|
|
401
|
+
"PLR0133", # 1==1, convert to True
|
|
402
|
+
"PLR0202", # manual classmethod decorator
|
|
403
|
+
"PLR0203", # manual staticmethod decorator
|
|
404
|
+
"PLR0206", # @property cannot have parameters
|
|
405
|
+
"PLR0402", # use from imports when needed
|
|
406
|
+
"PLR1708", # don't raise `StopIteration` in a generator, use `return` instead
|
|
407
|
+
"PLR1712", # unnecessary tmp variable in swap, use `a, b = b, a`
|
|
408
|
+
"PLR1714", # repeated == operator on same object, use the `in` operator
|
|
409
|
+
"PLR1716", # use the chained comparison (`a < b < c`) instead of `a < b and b < c`
|
|
410
|
+
"PLR5501", # replace else: if with elif
|
|
411
|
+
"PLR6104", # prefer += operator over x=x+1, and so on for other supported inplace operators
|
|
412
|
+
"PLR6201", # prefer sets for membership testing of literals
|
|
413
|
+
#"PLR6301", # refactor methods that don't use the `self` argument into functions or staticmethods
|
|
414
|
+
|
|
415
|
+
"PLW0108", # `lambda: str()` -> `str`
|
|
416
|
+
"PLW0120", # useless else on a loop
|
|
417
|
+
"PLW0127", # avoid useless x=x
|
|
418
|
+
"PLW0128", # avoid mentioning same itentifier in assignment unpacking left side, because it overrides
|
|
419
|
+
"PLW0129", # `assert "str"` is always true for nonempty strs
|
|
420
|
+
"PLW0131", # walrus operator unneeded
|
|
421
|
+
"PLW0177", # == float('nan')
|
|
422
|
+
"PLW0244", # __slots__ should only mention additional slots in subclass
|
|
423
|
+
"PLW0245", # super needs parenthesis to work
|
|
424
|
+
"PLW0406", # module imports itself, which causes a circular import error
|
|
425
|
+
"PLW0602", # global used, but this var only read, not written to
|
|
426
|
+
"PLW0604", # global at module scope is redundant
|
|
427
|
+
"PLW0711", # `except A or B` should be `except (A, B)` in 99.9% of cases (is a brainfart and not intentional)
|
|
428
|
+
"PLW1501", # bad `mode` string for open()
|
|
429
|
+
"PLW1507", # correct way to copy os.environ
|
|
430
|
+
"PLW1508", # .get() for env should have a valid default type (env vars always store strings)
|
|
431
|
+
"PLW1509", # subproces.Popen has built-in methods to not have to use a preexec_fn
|
|
432
|
+
"PLW1510", # subprocess.run should be used with `check=...` like `zip` with `strict=...` to avoid silent failures unless explicitly requested
|
|
433
|
+
"PLW1514", # always specify encoding
|
|
434
|
+
"PLW2101", # threading.Lock should share the same instance across threads, otherwise it's useless
|
|
435
|
+
#"PLW2901", # `for i in x: for i in y:` doesnt let you access the first loop's i, this also applies to multiple with statements; NOTE: disabled becase it also broke shit like for expr in exprs: expr = expr.strip(); even though this just a simple mapping... and not overwriting with like expr = "shit"...
|
|
436
|
+
"PLW3301", # min(x, min(y, z)) -> min(x, y, z), though this is unsafe if any x, y or z are float('nan')
|
|
437
|
+
|
|
438
|
+
# pyupgrade
|
|
439
|
+
"UP",
|
|
440
|
+
|
|
441
|
+
# refurb
|
|
442
|
+
"FURB101", # open().read() -> use pathlib
|
|
443
|
+
"FURB103", # open().write() -> use pathlib
|
|
444
|
+
"FURB105", # print("") -> print() ; though might be annoying if a warning pops up every fucking time i use print so we'll see if this sticks
|
|
445
|
+
"FURB110", # `x if x else y` -> `x or y`
|
|
446
|
+
"FURB113", # repeated append() -> extend()
|
|
447
|
+
"FURB116", # bin(1337)[:2] -> f'{1337:b}'
|
|
448
|
+
"FURB122", # try to use writelines() instead of write() in a loop if possible
|
|
449
|
+
"FURB129", # iteration over a `f` already invokes .readlines() by default, no need to call it explicitly
|
|
450
|
+
"FURB131", # prefer `x.clear()` over `del x[:]` for lists and dicts
|
|
451
|
+
"FURB132", # prefer `set.discard` over `set.remove`, when you know about the element's whereabouts anyway
|
|
452
|
+
"FURB136", # don't do manual min or max via if expr
|
|
453
|
+
"FURB142", # don't use set.add or set.discard in a loop, set.update or set.difference_update, or -=, |= operators tho the operators require set type on rhs as well
|
|
454
|
+
"FURB145", # prefer `x = lst.copy()` over `x = lst[:]`
|
|
455
|
+
"FURB148", # unnecessary enumerate
|
|
456
|
+
"FURB152", # detect hardcoded versions of math constants, prefer math.pi, math.e, etc.
|
|
457
|
+
"FURB156", # simillar to above but for string module constants
|
|
458
|
+
"FURB154", # `global` supports multiple identifierrs joined by a comma
|
|
459
|
+
"FURB157", # `Decimal` should be used without unnecessary proxies like float() or string when not needed
|
|
460
|
+
"FURB161", # manual int.bit_count()
|
|
461
|
+
"FURB162", # datetime.fromisoformat handling of timezone "Z"
|
|
462
|
+
"FURB163", # redundant math.log() base, use log2, log10, etc.
|
|
463
|
+
"FURB166", # use int(..., base=0) to detect the "0x", "0b", "0o" prefixes automatically instead of slicing, etc.
|
|
464
|
+
"FURB168", # use `x is None` instead of `isinstance(x, type(None))`
|
|
465
|
+
"FURB169", # use `x is None` instead of `type(obj) == type(None)`
|
|
466
|
+
"FURB177", # use pathlib.Path.cwd() instead of pathlib.Path().resolve()
|
|
467
|
+
"FURB180", # use abc.ABC instead of abc.ABCMeta
|
|
468
|
+
"FURB188", # startswith+slice -> removeprefix(), same for ends/suffix
|
|
469
|
+
"FURB189", # use User* types when wanting to inherit from a builtin type, since the builtins don't consistently use their own dunder methods
|
|
470
|
+
"FURB192", # manual min/max with sorted()[0]
|
|
471
|
+
|
|
472
|
+
# ruff
|
|
473
|
+
"RUF001", # confusable unicode characters
|
|
474
|
+
"RUF002", # confusable unicode characters in docstrings
|
|
475
|
+
"RUF003", # confusable unicode characters in comments
|
|
476
|
+
"RUF005", # concatenate collections using *splat
|
|
477
|
+
"RUF006", # asyncio event loop stores tasks in a weakref, you have to store it somewhere
|
|
478
|
+
"RUF007", # manual itertools.pairwise() with zip() and slicing
|
|
479
|
+
"RUF008", # mutable defaults in @dataclass'es and simillar
|
|
480
|
+
"RUF009", # same as above but the mutable default is coming from a function
|
|
481
|
+
"RUF010", # use f-strings' {!r} instead of manual repr() calls
|
|
482
|
+
"RUF012", # mutable classvar default
|
|
483
|
+
"RUF013", # x: int = None -> x: int | None = None
|
|
484
|
+
"RUF015", # prefer next(x) over list(x)[0]
|
|
485
|
+
"RUF016", # invalid index type, like indexing into a list with a str
|
|
486
|
+
"RUF017", # use better list flattening strategy
|
|
487
|
+
"RUF018", # dont use the wlarus operator in assert
|
|
488
|
+
"RUF019", # instead of `x in dct and dct[x]`, use `dct.get(x)`
|
|
489
|
+
"RUF020", # union with Never is same as the other side of the union by itself
|
|
490
|
+
"RUF021", # in expr `x or y and z` parenthesise the `y and z` part since `and` binds tigher than `or`.
|
|
491
|
+
"RUF022", # unsorted __all__
|
|
492
|
+
"RUF023", # unsorted __slots__
|
|
493
|
+
"RUF028", # ineffective `# fmt: off` and simillar comments
|
|
494
|
+
"RUF030", # assert with second argument being call to print should not be used
|
|
495
|
+
"RUF032", # decimal called from float literal
|
|
496
|
+
"RUF033", # use dataclasses.InitVar instead of init parameters' default value which dont work like that
|
|
497
|
+
"RUF034", # `x if y else x` -> `x`
|
|
498
|
+
"RUF036", # `None` should be at the end of a type union
|
|
499
|
+
"RUF037", # deque should not take empty iterable as first argument, as that's the default
|
|
500
|
+
"RUF038", # `Literal[True, False]` -> `bool`
|
|
501
|
+
"RUF039", # use r-str for regex calls
|
|
502
|
+
"RUF040", # assert second argument must a str
|
|
503
|
+
"RUF041", # unnecessary nested `Literal`
|
|
504
|
+
"RUF043", # pytest.raises(match=) shenanings
|
|
505
|
+
"RUF045", # implicit classvar in dataclass
|
|
506
|
+
"RUF046", # unnecessary cast to int
|
|
507
|
+
"RUF047", # empty `else` clause
|
|
508
|
+
"RUF048", # don't int-parse parts of version.split('.'), because it might not contain text like "post-1" which is not a valid integer
|
|
509
|
+
"RUF049", # don't use @dataclass on an enum.Enum class
|
|
510
|
+
"RUF050", # empty `if` clause
|
|
511
|
+
"RUF051", # `if key in dict: del dict[key]` -> `dict.pop(key, None)`
|
|
512
|
+
"RUF053", # type vars cause problems with Generic
|
|
513
|
+
"RUF054", # formfeed characters shouldn't be used
|
|
514
|
+
"RUF055", # regex could be replaced with simpler function
|
|
515
|
+
"RUF056", # falsey dict.get() fallback (the default of "None" is already falsey)
|
|
516
|
+
"RUF057", # unnecessary round (value is already an integer)
|
|
517
|
+
"RUF058", # starmap(f, zip(*...)) should be replaced with normal map
|
|
518
|
+
"RUF059", # unused unpacked variable
|
|
519
|
+
"RUF060", # `x in ()` or any empty collection
|
|
520
|
+
"RUF061", # legacy form pytest.raises
|
|
521
|
+
"RUF063", # correctly get annotations
|
|
522
|
+
"RUF064", # non-octal chmod permissions
|
|
523
|
+
"RUF065", # dont eagerly convert to str in logging
|
|
524
|
+
"RUF066", # @property without return
|
|
525
|
+
"RUF067", # __init__.py should only feature imports, docs and __all__
|
|
526
|
+
"RUF068", # duplicate entry in __all__
|
|
527
|
+
"RUF069", # float math should not use == or !=, but rather math.isclose
|
|
528
|
+
"RUF070", # unnecessary assign before yield
|
|
529
|
+
"RUF071", # os.path.commonprefix compares by char, os.path.commonpath compares by path part
|
|
530
|
+
"RUF072", # useless `finally` clause
|
|
531
|
+
"RUF074", # incorrect decorator order
|
|
532
|
+
"RUF075", # fallible @contextmanager
|
|
533
|
+
"RUF100", # unused noqa
|
|
534
|
+
"RUF101", # redirected noqa, use redirect target
|
|
535
|
+
"RUF102", # invalid noqa
|
|
536
|
+
"RUF103", # invalid suppression comment
|
|
537
|
+
"RUF104", # unmatched ruff: disable (add ruff: enable)
|
|
538
|
+
"RUF200", # pyproject.toml schema
|
|
539
|
+
|
|
540
|
+
# try
|
|
541
|
+
"TRY002", # create your own exception class
|
|
542
|
+
"TRY003", # exception messages should ideally be within the class __init__, not at class init site
|
|
543
|
+
"TRY004", # use TypeError for failed isinstance(x, T)
|
|
544
|
+
"TRY201", # use no-argument `raise` if possible
|
|
545
|
+
"TRY203", # useless try-except
|
|
546
|
+
"TRY300", # consider moving to else block
|
|
547
|
+
"TRY301", # raise within try shouldn't be used if possible
|
|
548
|
+
"TRY400", # logging.error->logging.exception when in except handler
|
|
549
|
+
"TRY401", # do not attach exception instance to logging.exception as it already has it
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
ignore = [
|
|
553
|
+
"D105", # document magic methods, like __str__, this is just stupid. Why add a "Return a string representation of X"??? this is entirely obvious...
|
|
554
|
+
"D107", # document every public __init__. This is too much.
|
|
555
|
+
"TC003", # not moving to type checking block if im going to use it a second
|
|
556
|
+
]
|
|
557
|
+
|
|
558
|
+
# ignore = [
|
|
559
|
+
# # "COM812", # missing-trailing-comma | So ruff fmt shuts up
|
|
560
|
+
# # "COM812", # single-line-implicit-string-concatenation | So ruff fmt shuts up
|
|
561
|
+
|
|
562
|
+
# "ANN401", # any-type | Dynamically typed expressions (typing.Any) are disallowed
|
|
563
|
+
# "DTZ001", # call-datetime-without-tzinfo | The use of datetime.datetime() without tzinfo argument is not allowed
|
|
564
|
+
# "DTZ005", # call-datetime-now-without-tzinfo | The use of datetime.datetime.now() without tzinfo argument is not allowed
|
|
565
|
+
# "RUF012", # mutable-class-default | Class utils no longer work so it's on the ignore list
|
|
566
|
+
# "ARG001", # unused-function-argument | Messed with voiding arguments so it's disabled
|
|
567
|
+
# "ARG002", # unused-method-argument | Messed with voiding arguments so it's disabled
|
|
568
|
+
# "ARG003", # unused-class-method-argument | Messed with voiding arguments so it's disabled
|
|
569
|
+
# "ARG005", # unused-lambd-argument | Messed with voiding arguments so it's disabled
|
|
570
|
+
# "ARG004", # unused-static-method-argument | Messed with voiding arguments so it's disabled
|
|
571
|
+
# # "PLR2004", # magic-value-comparison | Magic values bruh shut up
|
|
572
|
+
# "PLR0913", # too-many-arguments | Too many function arguments
|
|
573
|
+
# "RET505", # superfluous-else-return | This rule just messes up readability, bruh
|
|
574
|
+
# "TRY003", # raise-vanilla-args | Exception("asdf") -> msg = 'asdf'; Exception(msg) bruuuuuhhhhh
|
|
575
|
+
# "PTH109", # os-getcwd | os.getcwd() is fine
|
|
576
|
+
# "PYI041", # redundant-numeric-union | No
|
|
577
|
+
# "PIE796", # non-unique-enums | But then enums are dumb?
|
|
578
|
+
# "PIE790", # unnecessary-pass | Shut up
|
|
579
|
+
# "PIE800", # unnecessary-spread | no u
|
|
580
|
+
# "B008", # function-call-in-default-argument | This has its uses
|
|
581
|
+
# "B006", # Do not use mutable data structures for argument defaults # No, fuck off
|
|
582
|
+
# "RUF005", # collection-literal-concatenation | Shut up x2
|
|
583
|
+
# "A001", # builtin-variable-shadowing | id
|
|
584
|
+
# "A002", # builtin-argument-shadowing | type
|
|
585
|
+
# "A003", # builtin-attribute-shadowing | type
|
|
586
|
+
# "A005", # Module tcrutils/types.py is shadowing python's builtin...
|
|
587
|
+
# "TRY301", # raise-within-try | no i will not
|
|
588
|
+
# "RET503", # implicit-return | like wtf?
|
|
589
|
+
# "PIE804", # unnecessary-dict-kwargs | sometimes useful
|
|
590
|
+
# "RUF013", # implicit-optional | that's dumb
|
|
591
|
+
# "SIM105", # suppressible-exception | obnoxious
|
|
592
|
+
# "SIM108", # if-else-block-instead-of-if-exp | makes stuff less readable
|
|
593
|
+
# "UP032", # f-string | sometimes .format() is useful
|
|
594
|
+
# "UP031", # Use format specifiers instead of percent format | sometimes % is useful
|
|
595
|
+
# "SIM117", # multiple-with-statements | more readable the old way
|
|
596
|
+
# "SIM102", # collapsible-if | nah
|
|
597
|
+
# # "TCH001", # typing-only-first-party-import | move to type checking my ass
|
|
598
|
+
# # "TCH002",
|
|
599
|
+
# # "TCH003",
|
|
600
|
+
|
|
601
|
+
# "A004", # Overriding built-ins, from rich import print
|
|
602
|
+
# "RUF029", # async fn without awaits
|
|
603
|
+
# "I001", # I have a better import sorter
|
|
604
|
+
# "B026", # Star-arg unpacking after a keyword argument is strongly discouraged
|
|
605
|
+
# "FURB142", # Use of `set.add()` in a for loop
|
|
606
|
+
# ]
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__: str = "0.1.0"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
BACKTICK = "`"
|
|
4
|
+
BACKTICKS = 3 * BACKTICK
|
|
5
|
+
NEWLINE = "\n"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _default_cut_at(text: str, max_length: int) -> str:
|
|
9
|
+
"""Cut text at max_length, if text is longer than max_length.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
text: The text to cut.
|
|
13
|
+
max_length: The maximum length of the output str.
|
|
14
|
+
Returns:
|
|
15
|
+
str: The text cut at max_length.
|
|
16
|
+
"""
|
|
17
|
+
return text[:max_length]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def codeblock(
|
|
21
|
+
text: str,
|
|
22
|
+
*,
|
|
23
|
+
langcode: str = "",
|
|
24
|
+
max_length: int = 1984,
|
|
25
|
+
fn_cut_at: Callable[[str, int], str] = _default_cut_at,
|
|
26
|
+
smart_empty: bool = True,
|
|
27
|
+
convert_three_backticks_to_apostrophes: bool = False,
|
|
28
|
+
) -> str:
|
|
29
|
+
r"""Pack text into a Discord codeblock. This will cut it to a max_length with a default for Discord, if you dont want this, set max_length to your desired length or -1 means text's length.
|
|
30
|
+
|
|
31
|
+
Note: May return str with len > max_length if max_length is ridicously low such that not even `'''\nlangcode'''` fits
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
text: The text to put in the codeblock body.
|
|
35
|
+
langcode: The language code to use in discord codeblock, for example "py".
|
|
36
|
+
max_length: The maximum length of the output str.
|
|
37
|
+
fn_cut_at: A Callable[[str, int], str] which's returned string is of or less than the passed in int in length.
|
|
38
|
+
smart_empty: If true, if `text` is '' (an empty string) it will be changed to ' ' (a space). This prevents the codeblock to think langcode is the body of the codeblock if no text is supplied.
|
|
39
|
+
convert_three_backticks_to_apostrophes: If true, any backticks in the text will be converted to apostrophes. This prevents the codeblock from being broken by backticks in the text.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: A str of the form ```langcode\ntext``` with a length of or less than max_length.
|
|
43
|
+
"""
|
|
44
|
+
if smart_empty and not text:
|
|
45
|
+
text = " "
|
|
46
|
+
|
|
47
|
+
if convert_three_backticks_to_apostrophes:
|
|
48
|
+
text = text.replace(BACKTICKS, "'''")
|
|
49
|
+
|
|
50
|
+
length_of_parts = len(BACKTICKS) + len(langcode) + len(NEWLINE) + len(BACKTICKS)
|
|
51
|
+
|
|
52
|
+
return "".join((
|
|
53
|
+
BACKTICKS,
|
|
54
|
+
langcode,
|
|
55
|
+
NEWLINE,
|
|
56
|
+
fn_cut_at(
|
|
57
|
+
text,
|
|
58
|
+
(max_length - length_of_parts if max_length != -1 else len(text)),
|
|
59
|
+
),
|
|
60
|
+
BACKTICKS,
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def codeblocks(
|
|
65
|
+
text0: str,
|
|
66
|
+
*texts: str,
|
|
67
|
+
langcodes: tuple[str, ...] | None = None,
|
|
68
|
+
max_length: int = 1984,
|
|
69
|
+
fn_cut_at: Callable[[str, int], str] = _default_cut_at,
|
|
70
|
+
smart_empty: bool = True,
|
|
71
|
+
convert_three_backticks_to_apostrophes: bool = False,
|
|
72
|
+
) -> str:
|
|
73
|
+
r"""Mutliple-`codeblock()`. For more info read codeblock() docstring.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
text0: The first text to put in the codeblock body.
|
|
77
|
+
*texts: The rest of the texts to put in the codeblock body.
|
|
78
|
+
langcodes: The language codes to use in discord codeblock, for example "py". If None, all langcodes will be empty strings. If provided, must be of same length as the total amount of texts passed in including text0.
|
|
79
|
+
max_length: The maximum length of the output str.
|
|
80
|
+
fn_cut_at: A Callable[[str, int], str] which's returned string is of or less than the passed in int in length.
|
|
81
|
+
smart_empty: If true, if `text` is '' (an empty string) it will be changed to ' ' (a space). This prevents the codeblock to think langcode is the body of the codeblock if no text is supplied.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
str: A str of the form ```langcode\ntext``` with a length of or less than max_length.
|
|
85
|
+
"""
|
|
86
|
+
texts = text0, *texts
|
|
87
|
+
|
|
88
|
+
if langcodes is None:
|
|
89
|
+
langcodes: tuple[str, ...] = ("",) * len(texts)
|
|
90
|
+
|
|
91
|
+
out = ""
|
|
92
|
+
|
|
93
|
+
for text, langcode in zip(texts, langcodes, strict=True):
|
|
94
|
+
out += codeblock(
|
|
95
|
+
text,
|
|
96
|
+
langcode=langcode,
|
|
97
|
+
max_length=max_length - len(out),
|
|
98
|
+
fn_cut_at=fn_cut_at,
|
|
99
|
+
smart_empty=smart_empty,
|
|
100
|
+
convert_three_backticks_to_apostrophes=convert_three_backticks_to_apostrophes,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def codeblocks_from_exception(
|
|
107
|
+
e: BaseException,
|
|
108
|
+
*,
|
|
109
|
+
max_length: int = 1800,
|
|
110
|
+
) -> str:
|
|
111
|
+
return codeblocks(
|
|
112
|
+
extract_error(e),
|
|
113
|
+
extract_traceback(e),
|
|
114
|
+
langcodes=(
|
|
115
|
+
"txt",
|
|
116
|
+
"py",
|
|
117
|
+
),
|
|
118
|
+
max_length=max_length,
|
|
119
|
+
smart_empty=True,
|
|
120
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .codeblock import BACKTICKS, NEWLINE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def uncodeblock(text: str) -> str:
|
|
5
|
+
"""*Try to* remove any markdown/discord codeblocks along with their langcodes if any are found.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
text: The text to remove markdown/discord codeblocks from.
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
str: The text with any markdown/discord codeblocks removed.
|
|
12
|
+
"""
|
|
13
|
+
if text[-3:] == BACKTICKS and text[:3] == BACKTICKS:
|
|
14
|
+
code_start = 3
|
|
15
|
+
code_end = -3
|
|
16
|
+
if NEWLINE in text[3:]: # Check if there is a language code specified
|
|
17
|
+
code_start = text.index(NEWLINE) + 1
|
|
18
|
+
return text[code_start:code_end].strip()
|
|
19
|
+
return text
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from nya_codeblock import codeblock
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_codeblock_simple():
|
|
5
|
+
result = codeblock("Hello, World!")
|
|
6
|
+
assert result == "```\nHello, World!```"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_codeblock_with_langcode():
|
|
10
|
+
result = codeblock("print('Hello, World!')", langcode="python")
|
|
11
|
+
assert result == "```python\nprint('Hello, World!')```"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_codeblock_max_length():
|
|
15
|
+
result = codeblock("This is a long text that should be cut.", max_length=24)
|
|
16
|
+
assert result == "```\nThis is a long te```"
|
|
17
|
+
assert len(result) <= 24
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_codeblock_smart_empty():
|
|
21
|
+
with_smart_empty = codeblock("", smart_empty=True)
|
|
22
|
+
assert with_smart_empty == "```\n ```"
|
|
23
|
+
|
|
24
|
+
without_smart_empty = codeblock("", smart_empty=False)
|
|
25
|
+
assert without_smart_empty == "```\n```"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.11"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "colorama"
|
|
7
|
+
version = "0.4.6"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
10
|
+
wheels = [
|
|
11
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[[package]]
|
|
15
|
+
name = "iniconfig"
|
|
16
|
+
version = "2.3.0"
|
|
17
|
+
source = { registry = "https://pypi.org/simple" }
|
|
18
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
|
19
|
+
wheels = [
|
|
20
|
+
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[[package]]
|
|
24
|
+
name = "nya-codeblock"
|
|
25
|
+
source = { editable = "." }
|
|
26
|
+
|
|
27
|
+
[package.dev-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
{ name = "pytest" },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[package.metadata]
|
|
33
|
+
|
|
34
|
+
[package.metadata.requires-dev]
|
|
35
|
+
dev = [{ name = "pytest", specifier = ">=9.1.1" }]
|
|
36
|
+
|
|
37
|
+
[[package]]
|
|
38
|
+
name = "packaging"
|
|
39
|
+
version = "26.2"
|
|
40
|
+
source = { registry = "https://pypi.org/simple" }
|
|
41
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
|
|
42
|
+
wheels = [
|
|
43
|
+
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[[package]]
|
|
47
|
+
name = "pluggy"
|
|
48
|
+
version = "1.6.0"
|
|
49
|
+
source = { registry = "https://pypi.org/simple" }
|
|
50
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
51
|
+
wheels = [
|
|
52
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[[package]]
|
|
56
|
+
name = "pygments"
|
|
57
|
+
version = "2.20.0"
|
|
58
|
+
source = { registry = "https://pypi.org/simple" }
|
|
59
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
|
60
|
+
wheels = [
|
|
61
|
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[[package]]
|
|
65
|
+
name = "pytest"
|
|
66
|
+
version = "9.1.1"
|
|
67
|
+
source = { registry = "https://pypi.org/simple" }
|
|
68
|
+
dependencies = [
|
|
69
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
70
|
+
{ name = "iniconfig" },
|
|
71
|
+
{ name = "packaging" },
|
|
72
|
+
{ name = "pluggy" },
|
|
73
|
+
{ name = "pygments" },
|
|
74
|
+
]
|
|
75
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
|
|
76
|
+
wheels = [
|
|
77
|
+
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
|
|
78
|
+
]
|