pynudger 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynudger/__init__.py +14 -0
- pynudger/__main__.py +13 -0
- pynudger/_check.py +358 -0
- pynudger/_cli.py +57 -0
- pynudger/_loader.py +89 -0
- pynudger/_rule.py +169 -0
- pynudger/py.typed +0 -0
- pynudger/py.typed.license +4 -0
- pynudger-0.1.0.dist-info/METADATA +265 -0
- pynudger-0.1.0.dist-info/RECORD +12 -0
- pynudger-0.1.0.dist-info/WHEEL +4 -0
- pynudger-0.1.0.dist-info/entry_points.txt +4 -0
pynudger/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Pynudger is an official opennudge linter."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from importlib.metadata import version
|
|
11
|
+
|
|
12
|
+
__version__ = version("pynudger")
|
|
13
|
+
|
|
14
|
+
del version
|
pynudger/__main__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Pynudger entrypoint."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
if __name__ == "__main__": # pragma: no cover
|
|
11
|
+
from pynudger import _cli
|
|
12
|
+
|
|
13
|
+
_cli.main()
|
pynudger/_check.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Base checks used for multiple rules."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import abc
|
|
11
|
+
import re
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import lintkit
|
|
15
|
+
|
|
16
|
+
# Shared functionality
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _Length(lintkit.check.Check, abc.ABC):
|
|
20
|
+
"""Calculate length of the value."""
|
|
21
|
+
|
|
22
|
+
@abc.abstractmethod
|
|
23
|
+
def _variable(self) -> str:
|
|
24
|
+
"""Name of the check.
|
|
25
|
+
|
|
26
|
+
Note:
|
|
27
|
+
It is used to display appropriate message.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Appropriate name of the length subclass.
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def _words(self, value: str) -> list[str]:
|
|
37
|
+
"""Divide string into words as defined by concrete class.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
List of words
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
45
|
+
def check(self, value: lintkit.Value[str]) -> bool:
|
|
46
|
+
"""Verify if the length of the value exceeds the limit.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
value:
|
|
50
|
+
Value which is unpacked (ast.AST node or path changed
|
|
51
|
+
to string)
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
True if the length exceeds the limit, False otherwise.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
words = self._words(value)
|
|
58
|
+
to_exclude: list[str] = self.config.get( # pyright: ignore[reportAttributeAccessIssue]
|
|
59
|
+
f"{self._variable()}_excludes", []
|
|
60
|
+
)
|
|
61
|
+
for word in to_exclude:
|
|
62
|
+
if word.lower() in words: # pragma: no cover
|
|
63
|
+
words.remove(word) # pyright: ignore[reportUnknownArgumentType]
|
|
64
|
+
|
|
65
|
+
return len(words) > self._length()
|
|
66
|
+
|
|
67
|
+
def _length(self) -> int:
|
|
68
|
+
"""Get length limit from config or return default.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Length limit
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
length = self.config.get(f"{self._variable()}_length") # pyright: ignore[reportAttributeAccessIssue]
|
|
75
|
+
if length is None:
|
|
76
|
+
return 3
|
|
77
|
+
return length # pragma: no cover
|
|
78
|
+
|
|
79
|
+
def message(self, value: lintkit.Value[str]) -> str:
|
|
80
|
+
"""Display error message in case of rule violation.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
value:
|
|
84
|
+
Value which violated the rule.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Message describing rule violation.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
return (
|
|
91
|
+
f"'{value}' has too many words (maximum: {self._length()}). "
|
|
92
|
+
"Consider using modules and submodules instead."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class _Regex(lintkit.check.Regex, abc.ABC):
|
|
97
|
+
"""Shared regex functionality."""
|
|
98
|
+
|
|
99
|
+
def regex_flags(self) -> int:
|
|
100
|
+
"""Make the matching case insensitive.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Flag indicating both upper and lowercase match.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
return re.IGNORECASE
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _What(abc.ABC):
|
|
110
|
+
"""What is being avoided."""
|
|
111
|
+
|
|
112
|
+
@abc.abstractmethod
|
|
113
|
+
def _what(self) -> str:
|
|
114
|
+
"""What is being avoided.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
String describing what is being avoided.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
raise NotImplementedError
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _SetGet(_Regex, _What, abc.ABC):
|
|
124
|
+
"""Shared functionality of setters and getters."""
|
|
125
|
+
|
|
126
|
+
def message(self, value: lintkit.Value[str]) -> str:
|
|
127
|
+
"""Display error message in case of rule violation.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
value:
|
|
131
|
+
Value which violated the rule.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Message describing rule violation.
|
|
135
|
+
|
|
136
|
+
"""
|
|
137
|
+
goal = re.sub(
|
|
138
|
+
self.regex(),
|
|
139
|
+
"",
|
|
140
|
+
value.__wrapped__, # pyright: ignore[reportUnknownArgumentType]
|
|
141
|
+
flags=self.regex_flags(),
|
|
142
|
+
)
|
|
143
|
+
if not goal:
|
|
144
|
+
return f"Avoid using {self._what()}."
|
|
145
|
+
return (
|
|
146
|
+
f"Avoid using {self._what()}. Instead of '{value}' "
|
|
147
|
+
f"define '{goal}' as a property."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class _UtilHelperCommon(_Regex, _What, abc.ABC):
|
|
152
|
+
"""Shared functionality of utils, helpers and commons."""
|
|
153
|
+
|
|
154
|
+
def message(self, _: lintkit.Value[str]) -> str:
|
|
155
|
+
"""Display error message in case of rule violation.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
_:
|
|
159
|
+
Unused
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Message describing rule violation.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
return (
|
|
166
|
+
f"Avoid defining '{self._what()}'. Use semantically "
|
|
167
|
+
"meaningful names instead."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Concrete checks
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Set(_SetGet):
|
|
175
|
+
"""Match setter and its variations."""
|
|
176
|
+
|
|
177
|
+
def regex(self) -> str:
|
|
178
|
+
"""Regex matching setters.
|
|
179
|
+
|
|
180
|
+
Note:
|
|
181
|
+
It might raise false positives.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Regex to match
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
return r"^_?set(?:s|ters?)?_?"
|
|
188
|
+
|
|
189
|
+
def _what(self) -> str:
|
|
190
|
+
"""What is being avoided.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Always "setters" string.
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
return "setters"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Get(_SetGet):
|
|
200
|
+
"""Match getter and its variations."""
|
|
201
|
+
|
|
202
|
+
def regex(self) -> str:
|
|
203
|
+
"""Regex matching getters.
|
|
204
|
+
|
|
205
|
+
Note:
|
|
206
|
+
It might raise false positives.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Regex to match
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
return r"^_?get(?:s|ters?)?_?"
|
|
213
|
+
|
|
214
|
+
def _what(self) -> str:
|
|
215
|
+
"""What is being avoided.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Always "getters" string.
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
return "getters"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class Util(_UtilHelperCommon):
|
|
225
|
+
"""Match utilities and its variations."""
|
|
226
|
+
|
|
227
|
+
def regex(self) -> str:
|
|
228
|
+
"""Regex matching utils.
|
|
229
|
+
|
|
230
|
+
Note:
|
|
231
|
+
It might raise false positives.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Regex to match
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
return r"_?util(s|ities)?"
|
|
238
|
+
|
|
239
|
+
def _what(self) -> str:
|
|
240
|
+
"""What is being avoided.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Always "utils" string.
|
|
244
|
+
|
|
245
|
+
"""
|
|
246
|
+
return "utils"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Helper(_UtilHelperCommon):
|
|
250
|
+
"""Match helpers and its variations."""
|
|
251
|
+
|
|
252
|
+
def regex(self) -> str:
|
|
253
|
+
"""Regex matching helpers.
|
|
254
|
+
|
|
255
|
+
Note:
|
|
256
|
+
It might raise false positives.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Regex to match
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
return r"_?help(ers?)?"
|
|
263
|
+
|
|
264
|
+
def _what(self) -> str:
|
|
265
|
+
"""What is being avoided.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Always "helpers" string.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
return "helpers"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class Common(_UtilHelperCommon):
|
|
275
|
+
"""Match common and its variations."""
|
|
276
|
+
|
|
277
|
+
def regex(self) -> str:
|
|
278
|
+
"""Regex matching common(s).
|
|
279
|
+
|
|
280
|
+
Note:
|
|
281
|
+
It might raise false positives.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Regex to match
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
return r"_?common(s)?"
|
|
288
|
+
|
|
289
|
+
def _what(self) -> str:
|
|
290
|
+
"""What is being avoided.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
Always "commons" string.
|
|
294
|
+
|
|
295
|
+
"""
|
|
296
|
+
return "commons"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class Pascal(_Length):
|
|
300
|
+
"""Calculate length of name based on pascal casing."""
|
|
301
|
+
|
|
302
|
+
def _variable(self) -> typing.Literal["pascal"]:
|
|
303
|
+
"""Name of the check.
|
|
304
|
+
|
|
305
|
+
Note:
|
|
306
|
+
It is used to display appropriate message.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Always "pascal" string.
|
|
310
|
+
|
|
311
|
+
"""
|
|
312
|
+
return "pascal"
|
|
313
|
+
|
|
314
|
+
def _words(self, value: str) -> list[str]:
|
|
315
|
+
"""Divide string into words as defined by pascal casing.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
List of words
|
|
319
|
+
|
|
320
|
+
"""
|
|
321
|
+
return re.sub(
|
|
322
|
+
"([A-Z][a-z]+)",
|
|
323
|
+
r" \1",
|
|
324
|
+
re.sub(
|
|
325
|
+
"([A-Z]+)",
|
|
326
|
+
r" \1",
|
|
327
|
+
value.__wrapped__, # pyright: ignore[reportUnknownArgumentType, reportAttributeAccessIssue]
|
|
328
|
+
),
|
|
329
|
+
).split()
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class Snake(_Length):
|
|
333
|
+
"""Calculate length of name based on snake casing."""
|
|
334
|
+
|
|
335
|
+
def _variable(self) -> typing.Literal["snake"]:
|
|
336
|
+
"""Name of the check.
|
|
337
|
+
|
|
338
|
+
Note:
|
|
339
|
+
It is used to display appropriate message.
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
Always "snake" string.
|
|
343
|
+
|
|
344
|
+
"""
|
|
345
|
+
return "snake"
|
|
346
|
+
|
|
347
|
+
def _words(self, value: str) -> list[str]:
|
|
348
|
+
"""Divide string into words as defined by snake casing.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
List of words
|
|
352
|
+
|
|
353
|
+
"""
|
|
354
|
+
if value.startswith("__") and value.endswith("__"):
|
|
355
|
+
return value[2:-2].split("_")
|
|
356
|
+
if value.startswith("_"):
|
|
357
|
+
return value[1:].split("_")
|
|
358
|
+
return value.split("_")
|
pynudger/_cli.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Pynudger CLI entrypoint."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import pathlib
|
|
11
|
+
import typing
|
|
12
|
+
|
|
13
|
+
from importlib.metadata import version
|
|
14
|
+
|
|
15
|
+
import lintkit
|
|
16
|
+
import loadfig
|
|
17
|
+
|
|
18
|
+
if typing.TYPE_CHECKING:
|
|
19
|
+
from collections.abc import Iterable
|
|
20
|
+
|
|
21
|
+
NAME = "pynudger"
|
|
22
|
+
|
|
23
|
+
lintkit.settings.name = NAME.upper()
|
|
24
|
+
|
|
25
|
+
# Import all rules to register them (side-effect)
|
|
26
|
+
from pynudger import ( # noqa: E402
|
|
27
|
+
_rule, # noqa: F401 # pyright: ignore[reportUnusedImport]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main(
|
|
32
|
+
args: list[str] | None = None,
|
|
33
|
+
include_codes: Iterable[int] | None = None,
|
|
34
|
+
exclude_codes: Iterable[int] | None = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Run the CLI."""
|
|
37
|
+
config = loadfig.config(NAME.lower())
|
|
38
|
+
|
|
39
|
+
lintkit.registry.inject("config", config)
|
|
40
|
+
|
|
41
|
+
if include_codes is None: # pragma: no cover
|
|
42
|
+
include_codes = config.get("include_codes")
|
|
43
|
+
if exclude_codes is None: # pragma: no cover
|
|
44
|
+
exclude_codes = config.get("exclude_codes")
|
|
45
|
+
|
|
46
|
+
lintkit.cli.main(
|
|
47
|
+
version(NAME),
|
|
48
|
+
files_default=pathlib.Path.cwd().rglob("*.py"),
|
|
49
|
+
files_help=(
|
|
50
|
+
"Files to lint with pynudger (default: all Python files in cwd)"
|
|
51
|
+
),
|
|
52
|
+
include_codes=include_codes,
|
|
53
|
+
exclude_codes=exclude_codes,
|
|
54
|
+
end_mode=config.get("end_mode", "all"),
|
|
55
|
+
args=args,
|
|
56
|
+
description="pynudger - opennudge Python linter",
|
|
57
|
+
)
|
pynudger/_loader.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Pynudger loaders."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import abc
|
|
11
|
+
import ast
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
import lintkit
|
|
15
|
+
|
|
16
|
+
if typing.TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
|
|
19
|
+
# Shared functionality
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _Definition(lintkit.loader.Python, abc.ABC):
|
|
23
|
+
"""Base class for loading class and function definitions."""
|
|
24
|
+
|
|
25
|
+
@abc.abstractmethod
|
|
26
|
+
def ast_class(self) -> type[ast.ClassDef | ast.FunctionDef]:
|
|
27
|
+
"""Return the AST class to look for.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
AST class to look for
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
def values(self) -> Iterable[lintkit.Value[str]]:
|
|
36
|
+
"""Yield all definitions of the specified AST class.
|
|
37
|
+
|
|
38
|
+
Yields:
|
|
39
|
+
All definitions of the specified AST class
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
data: dict[type[ast.AST], list[ast.AST]] = self.getitem("nodes_map")
|
|
43
|
+
for node in data[self.ast_class()]:
|
|
44
|
+
yield lintkit.Value.from_python(
|
|
45
|
+
node.name, # pyright: ignore[reportUnknownArgumentType, reportAttributeAccessIssue]
|
|
46
|
+
node,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Concrete loaders
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Class(_Definition):
|
|
54
|
+
"""Loader for function definitions."""
|
|
55
|
+
|
|
56
|
+
def ast_class(self) -> type[ast.ClassDef]:
|
|
57
|
+
"""Return the AST class to look for.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Always ast.ClassDef
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
return ast.ClassDef
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Function(_Definition):
|
|
67
|
+
"""Loader for function definitions."""
|
|
68
|
+
|
|
69
|
+
def ast_class(self) -> type[ast.FunctionDef]:
|
|
70
|
+
"""Return the AST class to look for.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Always ast.FunctionDef
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
return ast.FunctionDef
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Path(lintkit.loader.File):
|
|
80
|
+
"""Loader for file paths."""
|
|
81
|
+
|
|
82
|
+
def values(self) -> Iterable[lintkit.Value[str]]:
|
|
83
|
+
"""Yield the file path as a value.
|
|
84
|
+
|
|
85
|
+
Yields:
|
|
86
|
+
The file path as a value
|
|
87
|
+
"""
|
|
88
|
+
# COE: lintkit framework assures self.file is not None at this point
|
|
89
|
+
yield lintkit.Value(str(self.file.stem)) # pyright: ignore[reportOptionalMemberAccess]
|
pynudger/_rule.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
2
|
+
# SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
3
|
+
#
|
|
4
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
|
|
6
|
+
"""Pynudger rules."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import lintkit
|
|
11
|
+
|
|
12
|
+
from pynudger._check import Common, Get, Helper, Pascal, Set, Snake, Util
|
|
13
|
+
from pynudger._loader import Class, Function, Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Setters rules
|
|
17
|
+
class SetClass(Set, Class, lintkit.rule.Node, code=0):
|
|
18
|
+
"""Rule checking class names for setters."""
|
|
19
|
+
|
|
20
|
+
def description(self) -> str:
|
|
21
|
+
"""Return rule description."""
|
|
22
|
+
return "Avoid using setters in class names. Use properties instead."
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SetFunction(Set, Function, lintkit.rule.Node, code=1):
|
|
26
|
+
"""Rule checking function names for setters."""
|
|
27
|
+
|
|
28
|
+
def description(self) -> str:
|
|
29
|
+
"""Return rule description."""
|
|
30
|
+
return "Avoid using setters in function names. Use properties instead."
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SetPath(Set, Path, lintkit.rule.Node, code=2):
|
|
34
|
+
"""Rule checking paths for setters."""
|
|
35
|
+
|
|
36
|
+
def description(self) -> str:
|
|
37
|
+
"""Return rule description."""
|
|
38
|
+
return "Avoid using setters in file names. Define file name without it."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Getters rules
|
|
42
|
+
class GetClass(Get, Class, lintkit.rule.Node, code=3):
|
|
43
|
+
"""Rule checking class names for getters."""
|
|
44
|
+
|
|
45
|
+
def description(self) -> str:
|
|
46
|
+
"""Return rule description."""
|
|
47
|
+
return "Avoid using getters in class names. Use properties instead."
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GetFunction(Get, Function, lintkit.rule.Node, code=4):
|
|
51
|
+
"""Rule checking function names for getters."""
|
|
52
|
+
|
|
53
|
+
def description(self) -> str:
|
|
54
|
+
"""Return rule description."""
|
|
55
|
+
return "Avoid using getters in function names. Use properties instead."
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GetPath(Get, Path, lintkit.rule.Node, code=5):
|
|
59
|
+
"""Rule checking paths for getters."""
|
|
60
|
+
|
|
61
|
+
def description(self) -> str:
|
|
62
|
+
"""Return rule description."""
|
|
63
|
+
return "Avoid using getters in file names. Define file name without it."
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Util rules
|
|
67
|
+
class UtilClass(Util, Class, lintkit.rule.Node, code=6):
|
|
68
|
+
"""Rule checking class names for utils."""
|
|
69
|
+
|
|
70
|
+
def description(self) -> str:
|
|
71
|
+
"""Return rule description."""
|
|
72
|
+
return "Avoid using utils in class names. Name the class appropriately."
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class UtilFunction(Util, Function, lintkit.rule.Node, code=7):
|
|
76
|
+
"""Rule checking function names for utils."""
|
|
77
|
+
|
|
78
|
+
def description(self) -> str:
|
|
79
|
+
"""Return rule description."""
|
|
80
|
+
return "Avoid using utils in function names. Name the function appropriately."
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class UtilPath(Util, Path, lintkit.rule.Node, code=8):
|
|
84
|
+
"""Rule checking paths for utils."""
|
|
85
|
+
|
|
86
|
+
def description(self) -> str:
|
|
87
|
+
"""Return rule description."""
|
|
88
|
+
return "Avoid defining utils modules. Move functionality to appropriate modules."
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Helper rules
|
|
92
|
+
class HelperClass(Helper, Class, lintkit.rule.Node, code=9):
|
|
93
|
+
"""Rule checking class names for helpers."""
|
|
94
|
+
|
|
95
|
+
def description(self) -> str:
|
|
96
|
+
"""Return rule description."""
|
|
97
|
+
return (
|
|
98
|
+
"Avoid using helpers in class names. Name the class appropriately."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class HelperFunction(Helper, Function, lintkit.rule.Node, code=10):
|
|
103
|
+
"""Rule checking function names for helpers."""
|
|
104
|
+
|
|
105
|
+
def description(self) -> str:
|
|
106
|
+
"""Return rule description."""
|
|
107
|
+
return "Avoid using helpers in function names. Name the function appropriately."
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class HelperPath(Helper, Path, lintkit.rule.Node, code=11):
|
|
111
|
+
"""Rule checking paths for utils."""
|
|
112
|
+
|
|
113
|
+
def description(self) -> str:
|
|
114
|
+
"""Return rule description."""
|
|
115
|
+
return "Avoid defining utils modules. Move functionality to appropriate modules."
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Common rules
|
|
119
|
+
class CommonClass(Common, Class, lintkit.rule.Node, code=12):
|
|
120
|
+
"""Rule checking class names for helpers."""
|
|
121
|
+
|
|
122
|
+
def description(self) -> str:
|
|
123
|
+
"""Return rule description."""
|
|
124
|
+
return (
|
|
125
|
+
"Avoid using common in class names. Name the class appropriately."
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class CommonFunction(Common, Function, lintkit.rule.Node, code=13):
|
|
130
|
+
"""Rule checking function names for common."""
|
|
131
|
+
|
|
132
|
+
def description(self) -> str:
|
|
133
|
+
"""Return rule description."""
|
|
134
|
+
return "Avoid using common in function names. Name the function appropriately."
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CommonPath(Common, Path, lintkit.rule.Node, code=14):
|
|
138
|
+
"""Rule checking paths for commons."""
|
|
139
|
+
|
|
140
|
+
def description(self) -> str:
|
|
141
|
+
"""Return rule description."""
|
|
142
|
+
return "Avoid defining common modules. Move functionality to appropriate modules."
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Length rules
|
|
146
|
+
class LengthClass(Pascal, Class, lintkit.rule.Node, code=15):
|
|
147
|
+
"""Rule for checking class's name length (word-wise)."""
|
|
148
|
+
|
|
149
|
+
def description(self) -> str:
|
|
150
|
+
"""Return rule description."""
|
|
151
|
+
return "Avoid long class names. Specify intent by nesting modules/packages."
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class LengthFunction(Snake, Function, lintkit.rule.Node, code=16):
|
|
155
|
+
"""Rule for checking function's name length (word-wise)."""
|
|
156
|
+
|
|
157
|
+
def description(self) -> str:
|
|
158
|
+
"""Return rule description."""
|
|
159
|
+
return "Avoid long function names. Specify intent by nesting modules/packages."
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class LengthPath(Snake, Path, lintkit.rule.Node, code=17):
|
|
163
|
+
"""Rule for checking path name length (word-wise)."""
|
|
164
|
+
|
|
165
|
+
def description(self) -> str:
|
|
166
|
+
"""Return rule description."""
|
|
167
|
+
return (
|
|
168
|
+
"Avoid long path names. Specify intent by nesting modules/packages."
|
|
169
|
+
)
|
pynudger/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pynudger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: opennudge Python linter (naming conventions and other automated checks)
|
|
5
|
+
Author: open-nudge
|
|
6
|
+
Maintainer: open-nudge
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Project-URL: Changelog, https://github.com/open-nudge/pynudger/blob/master/CHANGELOG.md
|
|
19
|
+
Project-URL: Documentation, https://open-nudge.github.io/pynudger
|
|
20
|
+
Project-URL: Homepage, https://open-nudge.github.io/pynudger
|
|
21
|
+
Project-URL: Issues, https://github.com/open-nudge/pynudger/issues
|
|
22
|
+
Project-URL: Repository, https://github.com/open-nudge/pynudger
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Requires-Dist: lintkit[rich]>=0.2.1
|
|
25
|
+
Requires-Dist: loadfig>=0.1.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
<!--
|
|
29
|
+
SPDX-FileCopyrightText: © 2025 open-nudge <https://github.com/open-nudge>
|
|
30
|
+
SPDX-FileContributor: szymonmaszke <github@maszke.co>
|
|
31
|
+
|
|
32
|
+
SPDX-License-Identifier: Apache-2.0
|
|
33
|
+
-->
|
|
34
|
+
|
|
35
|
+
# pynudger
|
|
36
|
+
|
|
37
|
+
<!-- mkdocs remove start -->
|
|
38
|
+
|
|
39
|
+
<!-- vale off -->
|
|
40
|
+
|
|
41
|
+
<!-- pyml disable-num-lines 30 line-length-->
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<em>opennudge Python linter (naming conventions and other automated checks)</em>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<div align="center">
|
|
48
|
+
|
|
49
|
+
<a href="https://pypi.org/project/pynudger">
|
|
50
|
+
</a>
|
|
51
|
+
<a href="https://pypi.org/project/pynudger">
|
|
52
|
+
</a>
|
|
53
|
+
<a href="https://opensource.org/licenses/Apache-2.0">
|
|
54
|
+
</a>
|
|
55
|
+
<a>
|
|
56
|
+
</a>
|
|
57
|
+
<a href="https://scorecard.dev/viewer/?uri=github.com/open-nudge/pynudger">
|
|
58
|
+
</a>
|
|
59
|
+
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
✨ <a href="#features">Features</a>
|
|
64
|
+
🚀 <a href="#quick-start">Quick start</a>
|
|
65
|
+
📚 <a href="https://open-nudge.github.io/pynudger">Documentation</a>
|
|
66
|
+
🤝 <a href="#contribute">Contribute</a>
|
|
67
|
+
👍 <a href="https://github.com/open-nudge/pynudger/blob/main/ADOPTERS.md">Adopters</a>
|
|
68
|
+
📜 <a href="#legal">Legal</a>
|
|
69
|
+
</p>
|
|
70
|
+
<!-- vale on -->
|
|
71
|
+
|
|
72
|
+
______________________________________________________________________
|
|
73
|
+
|
|
74
|
+
<!-- mkdocs remove end -->
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
__pynudger__ is an opinionated linter for Python projects, focused on
|
|
79
|
+
naming conventions and making your code "more Pythonic".
|
|
80
|
+
|
|
81
|
+
- __Length rules__: Too long class/function names are flagged.
|
|
82
|
+
- __Setters/getters__: Discourages usage of setters/getters,
|
|
83
|
+
encourages properties instead.
|
|
84
|
+
- __No helpers/utils/commons__: Incentivizes more descriptive
|
|
85
|
+
and semantically coherent names for functionalities.
|
|
86
|
+
|
|
87
|
+
## Table of contents
|
|
88
|
+
|
|
89
|
+
- [Quick start](#quick-start)
|
|
90
|
+
- [Installation](#installation)
|
|
91
|
+
- [Usage](#usage)
|
|
92
|
+
- [Advanced](#advanced)
|
|
93
|
+
- [Configuration](#configuration)
|
|
94
|
+
- [Run as a pre-commit hook](#run-as-a-pre-commit-hook)
|
|
95
|
+
- [Disable in code](#disable-in-code)
|
|
96
|
+
- [Rules](#rules)
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
|
|
100
|
+
### Installation
|
|
101
|
+
|
|
102
|
+
> [!TIP]
|
|
103
|
+
> You can use your favorite package manager like
|
|
104
|
+
> [`uv`](https://github.com/astral-sh/uv),
|
|
105
|
+
> [`hatch`](https://github.com/pypa/hatch)
|
|
106
|
+
> or [`pdm`](https://github.com/pdm-project/pdm)
|
|
107
|
+
> instead of `pip`.
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
> pip install pynudger
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Usage
|
|
114
|
+
|
|
115
|
+
To check against the rules run the following from the command line:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
> pynudger check
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
You can pass additional arguments to `pynudger check`, like files
|
|
122
|
+
to check (by default all Python files in the current directory):
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
> pynudger check path/to/file.py another_file.py
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Advanced
|
|
129
|
+
|
|
130
|
+
### Configuration
|
|
131
|
+
|
|
132
|
+
You can configure pynudger in `pyproject.toml` (or `.pynudger.toml`
|
|
133
|
+
in the root of your project, just remove the `[tool.pynudger]` section),
|
|
134
|
+
for example:
|
|
135
|
+
|
|
136
|
+
```toml
|
|
137
|
+
[tool.pynudger]
|
|
138
|
+
# include rules by their code
|
|
139
|
+
include_codes = [1, 2, 5] # default: all rules included
|
|
140
|
+
# exclude rules by their code (takes precedence over include)
|
|
141
|
+
exclude_codes = [4, 5, 6] # default: no rules excluded
|
|
142
|
+
# whether to exit after first error or all errors
|
|
143
|
+
end_mode = "first" # default: "all"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> [!TIP]
|
|
147
|
+
> Rule-specific configuration can be found in the section below.
|
|
148
|
+
|
|
149
|
+
### Run as a pre-commit hook
|
|
150
|
+
|
|
151
|
+
`pynudger` can be used as a pre-commit hook, to add as a plugin:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
repos:
|
|
155
|
+
- repo: "https://github.com/open-nudge/pynudger"
|
|
156
|
+
rev: ... # select the tag or revision you want, or run `pre-commit autoupdate`
|
|
157
|
+
hooks:
|
|
158
|
+
- id: "pynudger"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Disable in code
|
|
162
|
+
|
|
163
|
+
You can disable `pynudger` on a line-by-line basis
|
|
164
|
+
(you have to specify exact code), e.g.:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
def set_my_too_long_function_name(): # noqa: PYNUDGER0, PYNUDGER16
|
|
168
|
+
pass
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
or a line span:
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# noqa-start: PYNUDGER0, PYNUDGER16
|
|
175
|
+
def set_my_too_long_function_name():
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
def set_another_long_function():
|
|
179
|
+
pass
|
|
180
|
+
# noqa-end: PYNUDGER0, PYNUDGER16
|
|
181
|
+
|
|
182
|
+
def set_will_error_out_this_time():
|
|
183
|
+
pass
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
It is also possible to disable all checks in a file
|
|
187
|
+
by placing the following somewhere in the file (preferably at the top):
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# noqa-file: PYNUDGER0, PYNUDGER16
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
> [!NOTE]
|
|
194
|
+
> If you are running `pynudger` with
|
|
195
|
+
> [`ruff`](https://github.com/astral-sh/ruff) you should add
|
|
196
|
+
> `lint.external = ["PYNUDGER"]` to `[tool.ruff]` section in
|
|
197
|
+
> `pyproject.toml` to avoid removing `# noqa: PYNUDGER` comments.
|
|
198
|
+
|
|
199
|
+
### Rules
|
|
200
|
+
|
|
201
|
+
> [!TIP]
|
|
202
|
+
> Run `pynudger rules` to see the list of available rules.
|
|
203
|
+
|
|
204
|
+
`pynudger` provides the following rules:
|
|
205
|
+
|
|
206
|
+
<!-- pyml disable-num-lines 25 line-length-->
|
|
207
|
+
|
|
208
|
+
| Name | Description |
|
|
209
|
+
| ------------ | ------------------------------------------------------------------------- |
|
|
210
|
+
| `PYNUDGER0` | Avoid using setters in class names. Use properties instead. |
|
|
211
|
+
| `PYNUDGER1` | Avoid using setters in function names. Use properties instead. |
|
|
212
|
+
| `PYNUDGER2` | Avoid using setters in file names. Define file name without it. |
|
|
213
|
+
| `PYNUDGER3` | Avoid using getters in class names. Use properties instead. |
|
|
214
|
+
| `PYNUDGER4` | Avoid using getters in function names. Use properties instead. |
|
|
215
|
+
| `PYNUDGER5` | Avoid using getters in file names. Define file name without it. |
|
|
216
|
+
| `PYNUDGER6` | Avoid using utils in class names. Name the class appropriately. |
|
|
217
|
+
| `PYNUDGER7` | Avoid using utils in function names. Name the function appropriately. |
|
|
218
|
+
| `PYNUDGER8` | Avoid defining utils modules. Move functionality to appropriate modules. |
|
|
219
|
+
| `PYNUDGER9` | Avoid using helpers in class names. Name the class appropriately. |
|
|
220
|
+
| `PYNUDGER10` | Avoid using helpers in function names. Name the function appropriately. |
|
|
221
|
+
| `PYNUDGER11` | Avoid defining utils modules. Move functionality to appropriate modules. |
|
|
222
|
+
| `PYNUDGER12` | Avoid using common in class names. Name the class appropriately. |
|
|
223
|
+
| `PYNUDGER13` | Avoid using common in function names. Name the function appropriately. |
|
|
224
|
+
| `PYNUDGER14` | Avoid defining common modules. Move functionality to appropriate modules. |
|
|
225
|
+
| `PYNUDGER15` | Avoid long class names. Specify intent by nesting modules/packages. |
|
|
226
|
+
| `PYNUDGER16` | Avoid long function names. Specify intent by nesting modules/packages. |
|
|
227
|
+
| `PYNUDGER17` | Avoid long path names. Specify intent by nesting modules/packages. |
|
|
228
|
+
|
|
229
|
+
with the following configurable options (in `pyproject.toml`
|
|
230
|
+
or `.pynudger.toml`):
|
|
231
|
+
|
|
232
|
+
<!-- pyml disable-num-lines 10 line-length-->
|
|
233
|
+
|
|
234
|
+
| Option | Description | Affected rules | Default |
|
|
235
|
+
| ----------------- | ----------------------------------------------------- | ---------------------- | ------- |
|
|
236
|
+
| `pascal_length` | Maximum allowed length of PascalCase names | PYNUDGER15 | 3 |
|
|
237
|
+
| `snake_length` | Maximum allowed length of snake_case names | PYNUDGER16, PYNUDGER17 | 3 |
|
|
238
|
+
| `pascal_excludes` | List of words to exclude from PascalCase length check | PYNUDGER15 | [] |
|
|
239
|
+
| `snake_excludes` | List of words to exclude from snake_case length check | PYNUDGER16, PYNUDGER17 | [] |
|
|
240
|
+
|
|
241
|
+
## Contribute
|
|
242
|
+
|
|
243
|
+
<!-- md-dead-link-check: off -->
|
|
244
|
+
|
|
245
|
+
<!-- mkdocs remove start -->
|
|
246
|
+
|
|
247
|
+
We welcome your contributions! Start here:
|
|
248
|
+
|
|
249
|
+
- [Code of Conduct](/CODE_OF_CONDUCT.md)
|
|
250
|
+
- [Contributing Guide](/CONTRIBUTING.md)
|
|
251
|
+
- [Roadmap](/ROADMAP.md)
|
|
252
|
+
- [Changelog](/CHANGELOG.md)
|
|
253
|
+
- [Report security vulnerabilities](/SECURITY.md)
|
|
254
|
+
- [Open an Issue](https://github.com/open-nudge/pynudger/issues)
|
|
255
|
+
|
|
256
|
+
## Legal
|
|
257
|
+
|
|
258
|
+
- This project is licensed under the _Apache 2.0 License_ - see
|
|
259
|
+
the [LICENSE](/LICENSE.md) file for details.
|
|
260
|
+
- This project is copyrighted by _open-nudge_ - the
|
|
261
|
+
appropriate copyright notice is included in each file.
|
|
262
|
+
|
|
263
|
+
<!-- mkdocs remove end -->
|
|
264
|
+
|
|
265
|
+
<!-- md-dead-link-check: on -->
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pynudger-0.1.0.dist-info/METADATA,sha256=6RN0mUgMUfsQmOuhU9_XFAHBjD2wHwA3s5urmOqve_8,9516
|
|
2
|
+
pynudger-0.1.0.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
|
3
|
+
pynudger-0.1.0.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
|
4
|
+
pynudger/__init__.py,sha256=0bxelJimZJm0sOqrC0SOGnnRO67Da2Q__fQtSLfcDEk,346
|
|
5
|
+
pynudger/__main__.py,sha256=kWeE4-k-Szhsb3lPs9ukvgf8_hlF6h5Q5gLsk1o_CJI,332
|
|
6
|
+
pynudger/_check.py,sha256=_wqyeKghpncLlUahoNbLWZkrQIYIgs0bWzCrQdAX5dc,8003
|
|
7
|
+
pynudger/_cli.py,sha256=-tcEgzKXhpJ0O7ojCeO82PWw0bX33N9PnMbt0_aXyOg,1507
|
|
8
|
+
pynudger/_loader.py,sha256=-vN2Ixte5jKEATRgpHIfiCszMxUWjGPKhqOmexbSTl0,2182
|
|
9
|
+
pynudger/_rule.py,sha256=CKuRe472tumdJU7kZ32pX4KWHXsBVdZ--INJIVzCZuE,5392
|
|
10
|
+
pynudger/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
pynudger/py.typed.license,sha256=N3aJoYmMm73hltVnRTMxUjsx8VeY0ltH2YNf7w4C4xQ,166
|
|
12
|
+
pynudger-0.1.0.dist-info/RECORD,,
|