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 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,4 @@
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
@@ -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">![PyPI - Python Version](https://img.shields.io/pypi/v/pynudger?style=for-the-badge&label=release&labelColor=grey&color=blue)
50
+ </a>
51
+ <a href="https://pypi.org/project/pynudger">![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fopen-nudge%2Fpynudger%2Fmain%2Fpyproject.toml&style=for-the-badge&label=python&labelColor=grey&color=blue)
52
+ </a>
53
+ <a href="https://opensource.org/licenses/Apache-2.0">![License](https://img.shields.io/badge/License-Apache_2.0-blue?style=for-the-badge)
54
+ </a>
55
+ <a>![Coverage Hardcoded](https://img.shields.io/badge/coverage-100%25-green?style=for-the-badge)
56
+ </a>
57
+ <a href="https://scorecard.dev/viewer/?uri=github.com/open-nudge/pynudger">![OSSF-Scorecard Score](https://img.shields.io/ossf-scorecard/github.com/open-nudge/pynudger?style=for-the-badge&label=OSSF)
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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-backend (2.4.5)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+
3
+ [gui_scripts]
4
+