nwdocstringchecking 2.0.1__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.
- nwdocstringchecking-2.0.1.dist-info/METADATA +149 -0
- nwdocstringchecking-2.0.1.dist-info/RECORD +8 -0
- nwdocstringchecking-2.0.1.dist-info/WHEEL +5 -0
- nwdocstringchecking-2.0.1.dist-info/entry_points.txt +2 -0
- nwdocstringchecking-2.0.1.dist-info/top_level.txt +3 -0
- nwdocstringchecking.py +86 -0
- nwdocstringcheckingcli.py +358 -0
- setupinfo.py +14 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nwdocstringchecking
|
|
3
|
+
Version: 2.0.1
|
|
4
|
+
Summary: A library designed to identify which methods in a Python file are missing docstrings.
|
|
5
|
+
Home-page: https://github.com/numbworks/nwdocstringchecking
|
|
6
|
+
Author: numbworks
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Dynamic: author
|
|
11
|
+
Dynamic: description
|
|
12
|
+
Dynamic: description-content-type
|
|
13
|
+
Dynamic: home-page
|
|
14
|
+
Dynamic: license
|
|
15
|
+
Dynamic: requires-python
|
|
16
|
+
Dynamic: summary
|
|
17
|
+
|
|
18
|
+
# nwdocstringchecking
|
|
19
|
+
|
|
20
|
+
## Introduction
|
|
21
|
+
|
|
22
|
+
This packages consists of the following modules:
|
|
23
|
+
|
|
24
|
+
- `nwdocstringchecking` is a library designed to identify which methods in a Python file are missing docstrings.
|
|
25
|
+
- `nwdocstringcheckingcli` is a command-line application built on the top of `nwdocstringchecking`.
|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
I'll use Debian as operating system, but the concept is similar for Windows or Mac.
|
|
30
|
+
|
|
31
|
+
Procedure:
|
|
32
|
+
|
|
33
|
+
1. Install `pip` and `venv`:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
sudo apt install python3-pip python3.12-venv
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Create a virtual environment:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
python3 -m venv /tmp/nwdocstringchecking
|
|
43
|
+
source /tmp/nwdocstringchecking/bin/activate
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Download and install the package:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pip install nwdocstringchecking
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
4. Enter in the interactive Python environment:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
python3
|
|
56
|
+
```
|
|
57
|
+
```
|
|
58
|
+
Python 3.12.3 (main, Aug 31 2026, 10:18:26) [GCC 13.3.0] on linux
|
|
59
|
+
Type "help", "copyright", "credits" or "license" for more information.
|
|
60
|
+
>>>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
5. Try out if the library works:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from nwdocstringchecking import DocStringChecker
|
|
67
|
+
|
|
68
|
+
docstring_checker : DocStringChecker = DocStringChecker()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
6. Exit from the interactive Python environment:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
exit()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
7. Try out if the CLI application works:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
nwdocstringcheckingcli
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
8. Deactivate and delete the virtual environment:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
deactivate
|
|
87
|
+
rm -rf /tmp/nwdocstringchecking
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
9. Done!
|
|
91
|
+
|
|
92
|
+
## Examples
|
|
93
|
+
|
|
94
|
+
Run it against a `file_path`:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
nwdocstringcheckingcli --file_path src/nwdocstringchecking.py
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
*******************************************
|
|
102
|
+
'##::: ##:'##:::::'##:'########:::'######::
|
|
103
|
+
###:: ##: ##:'##: ##: ##.... ##:'##... ##:
|
|
104
|
+
####: ##: ##: ##: ##: ##:::: ##: ##:::..::
|
|
105
|
+
## ## ##: ##: ##: ##: ##:::: ##:. ######::
|
|
106
|
+
##. ####: ##: ##: ##: ##:::: ##::..... ##:
|
|
107
|
+
##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:
|
|
108
|
+
##::. ##:. ###. ###:: ########::. ######::
|
|
109
|
+
..::::..:::...::...:::........::::......:::
|
|
110
|
+
************************Version: 2.0.1*****
|
|
111
|
+
|
|
112
|
+
file_path: 'src/nwdocstringchecking.py'
|
|
113
|
+
exclude: '[]'
|
|
114
|
+
|
|
115
|
+
_MessageCollectionValidator.provided_file_path_doesnt_exist
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Run it against a `file_path` with `exclude`:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
nwdocstringcheckingcli --file_path src/nwdocstringchecking.py --exclude Message --exclude Something
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
*******************************************
|
|
126
|
+
'##::: ##:'##:::::'##:'########:::'######::
|
|
127
|
+
###:: ##: ##:'##: ##: ##.... ##:'##... ##:
|
|
128
|
+
####: ##: ##: ##: ##: ##:::: ##: ##:::..::
|
|
129
|
+
## ## ##: ##: ##: ##: ##:::: ##:. ######::
|
|
130
|
+
##. ####: ##: ##: ##: ##:::: ##::..... ##:
|
|
131
|
+
##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:
|
|
132
|
+
##::. ##:. ###. ###:: ########::. ######::
|
|
133
|
+
..::::..:::...::...:::........::::......:::
|
|
134
|
+
************************Version: 2.0.1*****
|
|
135
|
+
|
|
136
|
+
file_path: 'src/nwdocstringchecking.py'
|
|
137
|
+
exclude: '['Message', 'Something']'
|
|
138
|
+
|
|
139
|
+
All methods have docstrings.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Links
|
|
143
|
+
|
|
144
|
+
- Source: https://github.com/numbworks/nwdocstringchecking
|
|
145
|
+
- License: https://github.com/numbworks/nwdocstringchecking/blob/master/LICENSE
|
|
146
|
+
|
|
147
|
+
## Total Downloads (PyPi)
|
|
148
|
+
|
|
149
|
+
[](https://pepy.tech/projects/nwdocstringchecking)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
nwdocstringchecking.py,sha256=en2BhIbWK4newXig-kPjVFePGjd0n_4gZweoaxlfFq8,2613
|
|
2
|
+
nwdocstringcheckingcli.py,sha256=6Xxr9Fb8ArsbWy9MztGw1NQdeWDwDOxUNR7wQWST06s,11857
|
|
3
|
+
setupinfo.py,sha256=bxSRJLmhxTbDpQ5NSPPbudbV__EoxGw8yTsvyOJ9kBg,557
|
|
4
|
+
nwdocstringchecking-2.0.1.dist-info/METADATA,sha256=TnfCZ6b3-v4wrGcgVkrwXYuxwUJ0Ng5GWrA0NGy3tF0,3611
|
|
5
|
+
nwdocstringchecking-2.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
nwdocstringchecking-2.0.1.dist-info/entry_points.txt,sha256=zVRHVw0fuAi4ScBzKUt82iMFltcpWJCDNcP8E5Q58Co,71
|
|
7
|
+
nwdocstringchecking-2.0.1.dist-info/top_level.txt,sha256=CMJgYLAyf5OdUH_PfZuM5ywuBhEdv6N6DhVyg_hMVJs,53
|
|
8
|
+
nwdocstringchecking-2.0.1.dist-info/RECORD,,
|
nwdocstringchecking.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'''
|
|
2
|
+
A library designed to identify which methods in a Python file are missing docstrings.
|
|
3
|
+
|
|
4
|
+
Alias: nwds
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
# GLOBAL MODULES
|
|
8
|
+
import ast
|
|
9
|
+
import os
|
|
10
|
+
from ast import Module
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
# LOCAL MODULES
|
|
14
|
+
# CONSTANTS
|
|
15
|
+
# STATIC CLASSES
|
|
16
|
+
class _MessageCollectionValidator():
|
|
17
|
+
|
|
18
|
+
'''Collects all the messages used for logging and for the exceptions used by Validator.'''
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def provided_file_path_doesnt_exist(file_path : str) -> str:
|
|
22
|
+
return f"The provided 'file_path' doesn't exist: '{file_path}'."
|
|
23
|
+
class _MessageCollection(
|
|
24
|
+
_MessageCollectionValidator):
|
|
25
|
+
|
|
26
|
+
'''Collects all the messages used for logging and for the exceptions.'''
|
|
27
|
+
class Validator():
|
|
28
|
+
|
|
29
|
+
'''Collects all validation methods.'''
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def validate_file_path(file_path : str) -> None:
|
|
33
|
+
|
|
34
|
+
'''Returns file_path or raises Exception.'''
|
|
35
|
+
|
|
36
|
+
if not os.path.isfile(file_path):
|
|
37
|
+
raise Exception(_MessageCollection.provided_file_path_doesnt_exist(file_path))
|
|
38
|
+
|
|
39
|
+
# CLASSES
|
|
40
|
+
class DocStringChecker():
|
|
41
|
+
|
|
42
|
+
'''Collects all the logic related to docstrings management.'''
|
|
43
|
+
|
|
44
|
+
def __load_source(self, file_path : str) -> str:
|
|
45
|
+
|
|
46
|
+
'''Loads source from file_path.'''
|
|
47
|
+
|
|
48
|
+
source : str = ""
|
|
49
|
+
|
|
50
|
+
with open(file_path, "r", encoding='utf-8') as file:
|
|
51
|
+
source = file.read()
|
|
52
|
+
|
|
53
|
+
return source
|
|
54
|
+
def __get_missing_docstrings(self, source : str, exclude : list[str]) -> list[str]:
|
|
55
|
+
|
|
56
|
+
'''Returns all the method names missing docstrings by excluding specified substrings.'''
|
|
57
|
+
|
|
58
|
+
tree : Module = ast.parse(source = source)
|
|
59
|
+
|
|
60
|
+
method_names : list[str] = []
|
|
61
|
+
|
|
62
|
+
for node in ast.walk(tree):
|
|
63
|
+
if isinstance(node, ast.ClassDef):
|
|
64
|
+
for item in node.body:
|
|
65
|
+
if isinstance(item, ast.FunctionDef):
|
|
66
|
+
if ast.get_docstring(item) is None:
|
|
67
|
+
method_name = f"{node.name}.{item.name}"
|
|
68
|
+
if not any(substring in method_name for substring in exclude):
|
|
69
|
+
method_names.append(method_name)
|
|
70
|
+
|
|
71
|
+
return method_names
|
|
72
|
+
|
|
73
|
+
def run(self, file_path : str, exclude : list[str] = []) -> list[str]:
|
|
74
|
+
|
|
75
|
+
'''Runs the docstring check.'''
|
|
76
|
+
|
|
77
|
+
Validator.validate_file_path(file_path)
|
|
78
|
+
|
|
79
|
+
source : str = self.__load_source(file_path = cast(str, file_path))
|
|
80
|
+
missing : list[str] = self.__get_missing_docstrings(source = source, exclude = exclude)
|
|
81
|
+
|
|
82
|
+
return missing
|
|
83
|
+
|
|
84
|
+
# MAIN
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
pass
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
'''
|
|
2
|
+
A CLI application built on the top of nwdocstringchecking.
|
|
3
|
+
'''
|
|
4
|
+
|
|
5
|
+
# GLOBAL MODULES
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
from argparse import ArgumentParser, Namespace
|
|
9
|
+
from shutil import get_terminal_size
|
|
10
|
+
from subprocess import CompletedProcess
|
|
11
|
+
from typing import Any, Callable, Final, Optional
|
|
12
|
+
|
|
13
|
+
# LOCAL MODULES
|
|
14
|
+
from nwdocstringchecking import DocStringChecker, Validator
|
|
15
|
+
from setupinfo import CLI_DESCRIPTION, PROJECT_VERSION
|
|
16
|
+
|
|
17
|
+
# CONSTANTS
|
|
18
|
+
class CLISTRING:
|
|
19
|
+
|
|
20
|
+
'''Collects all the CLI-related strings.'''
|
|
21
|
+
|
|
22
|
+
OPTION_FILEPATH_FLAGS : Final[list[str]] = ["--file_path"]
|
|
23
|
+
OPTION_FILEPATH_DEST : Final[str] = "file_path"
|
|
24
|
+
OPTION_FILEPATH_REQUIRED : Final[bool] = True
|
|
25
|
+
OPTION_FILEPATH_HELP : Final[str] = "The path to the Python file to check docstrings for."
|
|
26
|
+
|
|
27
|
+
OPTION_EXCLUDE_FLAGS : Final[list[str]] = ["--exclude"]
|
|
28
|
+
OPTION_EXCLUDE_DEST : Final[str] = "exclude"
|
|
29
|
+
OPTION_EXCLUDE_REQUIRED : Final[bool] = False
|
|
30
|
+
OPTION_EXCLUDE_HELP : Final[str] = "One or multiple substrings to exclude from the output."
|
|
31
|
+
OPTION_EXCLUDE_DEFAULT : Final[list[str]] = []
|
|
32
|
+
OPTION_EXCLUDE_ACTION : Final[str] = "append"
|
|
33
|
+
|
|
34
|
+
# STATIC CLASSES
|
|
35
|
+
class _MessageCollectionAsciiBannerManager():
|
|
36
|
+
|
|
37
|
+
'''Collects all the messages used for logging and for the exceptions.'''
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def provided_version_empty_whitespace() -> str:
|
|
41
|
+
return "The provided 'version' is empty or whitespace."
|
|
42
|
+
class _MessageCollectionCLIManager():
|
|
43
|
+
|
|
44
|
+
'''Collects all the messages used for logging and for the exceptions.'''
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def all_methods_have_docstrings() -> str:
|
|
48
|
+
return "All methods have docstrings."
|
|
49
|
+
class _MessageCollection(
|
|
50
|
+
_MessageCollectionAsciiBannerManager,
|
|
51
|
+
_MessageCollectionCLIManager):
|
|
52
|
+
|
|
53
|
+
'''Collects all the messages used for logging and for the exceptions.'''
|
|
54
|
+
|
|
55
|
+
# CLASSES
|
|
56
|
+
class AsciiBannerManager:
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
Creates the ASCII banner for the provided library's version.
|
|
60
|
+
|
|
61
|
+
The figlet can be generated using
|
|
62
|
+
- 'http://www.network-science.de/ascii/' (font: "banner3-D", width: 120)
|
|
63
|
+
- 'https://www.askapache.com/online-tools/figlet-ascii/'.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __validate(self, version: str) -> None:
|
|
67
|
+
|
|
68
|
+
"""Validates the provided 'version'."""
|
|
69
|
+
|
|
70
|
+
if not version or not version.strip():
|
|
71
|
+
raise ValueError(_MessageCollection.provided_version_empty_whitespace())
|
|
72
|
+
def __create_figlet(self) -> tuple:
|
|
73
|
+
|
|
74
|
+
"""Returns a tuple containing the figlet and its width."""
|
|
75
|
+
|
|
76
|
+
lines : list[str] = [
|
|
77
|
+
"'##::: ##:'##:::::'##:'########:::'######::",
|
|
78
|
+
" ###:: ##: ##:'##: ##: ##.... ##:'##... ##:",
|
|
79
|
+
" ####: ##: ##: ##: ##: ##:::: ##: ##:::..::",
|
|
80
|
+
" ## ## ##: ##: ##: ##: ##:::: ##:. ######::",
|
|
81
|
+
" ##. ####: ##: ##: ##: ##:::: ##::..... ##:",
|
|
82
|
+
" ##:. ###: ##: ##: ##: ##:::: ##:'##::: ##:",
|
|
83
|
+
" ##::. ##:. ###. ###:: ########::. ######::",
|
|
84
|
+
"..::::..:::...::...:::........::::......:::"
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
return (os.linesep.join(lines), len(lines[0]))
|
|
88
|
+
def __create_frame(self, version: str, max_length: int) -> tuple:
|
|
89
|
+
|
|
90
|
+
"""Returns a tuple containing the frame of the figlet."""
|
|
91
|
+
|
|
92
|
+
version_token : str = f"Version: {version}"
|
|
93
|
+
|
|
94
|
+
margin_length : int = 5
|
|
95
|
+
total_length : int = max_length - len(version_token) - margin_length
|
|
96
|
+
|
|
97
|
+
top_line : str = "*" * max_length
|
|
98
|
+
bottom_line : str = f"{top_line[:total_length]}{version_token}{'*' * margin_length}"
|
|
99
|
+
|
|
100
|
+
return (top_line, bottom_line)
|
|
101
|
+
|
|
102
|
+
def create_standard(self, version : str) -> str:
|
|
103
|
+
|
|
104
|
+
"""Creates the standard ASCII banner."""
|
|
105
|
+
|
|
106
|
+
self.__validate(version)
|
|
107
|
+
|
|
108
|
+
figlet, max_length = self.__create_figlet()
|
|
109
|
+
top_line, bottom_line = self.__create_frame(version, max_length)
|
|
110
|
+
|
|
111
|
+
ascii_banner : str = os.linesep.join([
|
|
112
|
+
top_line,
|
|
113
|
+
figlet,
|
|
114
|
+
bottom_line,
|
|
115
|
+
""
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
return ascii_banner
|
|
119
|
+
def create_mini(self, version : str) -> str:
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
Creates the mini ASCII banner:
|
|
123
|
+
|
|
124
|
+
***************
|
|
125
|
+
* NWDS v1.0.0 *
|
|
126
|
+
***************
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
self.__validate(version)
|
|
130
|
+
|
|
131
|
+
assembly_name : str = "NWDS"
|
|
132
|
+
middle_line : str = f"* {assembly_name} v{version} *"
|
|
133
|
+
|
|
134
|
+
top_line : str = "*" * len(middle_line)
|
|
135
|
+
bottom_line : str = top_line
|
|
136
|
+
|
|
137
|
+
ascii_banner : str = os.linesep.join([
|
|
138
|
+
top_line,
|
|
139
|
+
middle_line,
|
|
140
|
+
bottom_line,
|
|
141
|
+
""
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
return ascii_banner
|
|
145
|
+
def create(self, version : str, terminal_width : int) -> str:
|
|
146
|
+
|
|
147
|
+
"""Creates either a standard or mini ASCII banner depending on the terminal width."""
|
|
148
|
+
|
|
149
|
+
_, max_length = self.__create_figlet()
|
|
150
|
+
|
|
151
|
+
if max_length <= terminal_width:
|
|
152
|
+
return self.create_standard(version)
|
|
153
|
+
else:
|
|
154
|
+
return self.create_mini(version)
|
|
155
|
+
class TerminalWindowManager:
|
|
156
|
+
|
|
157
|
+
'''Handles terminal window size.'''
|
|
158
|
+
|
|
159
|
+
__shutil_width_function : Callable[[], Optional[int]]
|
|
160
|
+
__stty_width_function : Callable[[], Optional[int]]
|
|
161
|
+
|
|
162
|
+
cutoff_width : Final[int] = 70
|
|
163
|
+
|
|
164
|
+
@staticmethod
|
|
165
|
+
def default_shutil_width_function() -> Optional[int]:
|
|
166
|
+
|
|
167
|
+
"""Get terminal width using shutil (multi-platform)."""
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
|
|
171
|
+
terminal_width : int = get_terminal_size().columns
|
|
172
|
+
|
|
173
|
+
return terminal_width
|
|
174
|
+
|
|
175
|
+
except:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def default_stty_width_function() -> Optional[int]:
|
|
180
|
+
|
|
181
|
+
"""Get terminal width using stty command (Linux)."""
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
|
|
185
|
+
process : CompletedProcess[str] = subprocess.run(
|
|
186
|
+
["/bin/sh", "-c", "stty size | cut -d' ' -f2"],
|
|
187
|
+
capture_output = True,
|
|
188
|
+
text = True,
|
|
189
|
+
check = False,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
stty_output : str = process.stdout.strip()
|
|
193
|
+
terminal_width : int = int(stty_output)
|
|
194
|
+
|
|
195
|
+
if terminal_width >= 0:
|
|
196
|
+
return terminal_width
|
|
197
|
+
|
|
198
|
+
return None
|
|
199
|
+
except:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
shutil_width_function : Optional[Callable[[], Optional[int]]] = None,
|
|
205
|
+
stty_width_function : Optional[Callable[[], Optional[int]]] = None,
|
|
206
|
+
) -> None:
|
|
207
|
+
|
|
208
|
+
if shutil_width_function is None:
|
|
209
|
+
shutil_width_function = self.default_shutil_width_function
|
|
210
|
+
|
|
211
|
+
if stty_width_function is None:
|
|
212
|
+
stty_width_function = self.default_stty_width_function
|
|
213
|
+
|
|
214
|
+
self.__shutil_width_function = shutil_width_function
|
|
215
|
+
self.__stty_width_function = stty_width_function
|
|
216
|
+
|
|
217
|
+
def get_or_cutoff(self) -> int:
|
|
218
|
+
|
|
219
|
+
terminal_width : Optional[int] = self.__shutil_width_function()
|
|
220
|
+
|
|
221
|
+
if terminal_width is None:
|
|
222
|
+
terminal_width = self.__stty_width_function()
|
|
223
|
+
|
|
224
|
+
if terminal_width is None:
|
|
225
|
+
terminal_width = self.cutoff_width
|
|
226
|
+
|
|
227
|
+
return terminal_width
|
|
228
|
+
class CLIValidator:
|
|
229
|
+
|
|
230
|
+
'''Handles CLI argument validation.'''
|
|
231
|
+
|
|
232
|
+
def validate_file_path(self, file_path: str) -> str:
|
|
233
|
+
|
|
234
|
+
'''Returns file_path or raises Exception.'''
|
|
235
|
+
|
|
236
|
+
Validator().validate_file_path(file_path)
|
|
237
|
+
|
|
238
|
+
return file_path
|
|
239
|
+
class APFactory():
|
|
240
|
+
|
|
241
|
+
'''Encapsulates all the logic related to the creation of a custom instance of argparse.ArgumentParser.'''
|
|
242
|
+
|
|
243
|
+
__cli_validator : CLIValidator
|
|
244
|
+
|
|
245
|
+
def __init__(self, cli_validator : CLIValidator = CLIValidator()) -> None:
|
|
246
|
+
self.__cli_validator = cli_validator
|
|
247
|
+
|
|
248
|
+
def create(self) -> ArgumentParser:
|
|
249
|
+
|
|
250
|
+
'''
|
|
251
|
+
Creates a custom instance of argparse.ArgumentParser.
|
|
252
|
+
|
|
253
|
+
The "prog" argument is not provided in order to make the "usage" statement dynamic:
|
|
254
|
+
|
|
255
|
+
usage: nwdocstringcheckingcli.py [-h] --file_path FILE_PATH [--exclude EXCLUDE]
|
|
256
|
+
'''
|
|
257
|
+
|
|
258
|
+
argument_parser : ArgumentParser = ArgumentParser(description = CLI_DESCRIPTION)
|
|
259
|
+
|
|
260
|
+
argument_parser.add_argument(
|
|
261
|
+
*CLISTRING.OPTION_FILEPATH_FLAGS,
|
|
262
|
+
dest = CLISTRING.OPTION_FILEPATH_DEST,
|
|
263
|
+
required = CLISTRING.OPTION_FILEPATH_REQUIRED,
|
|
264
|
+
help = CLISTRING.OPTION_FILEPATH_HELP,
|
|
265
|
+
type = self.__cli_validator.validate_file_path)
|
|
266
|
+
|
|
267
|
+
argument_parser.add_argument(
|
|
268
|
+
*CLISTRING.OPTION_EXCLUDE_FLAGS,
|
|
269
|
+
dest = CLISTRING.OPTION_EXCLUDE_DEST,
|
|
270
|
+
required = CLISTRING.OPTION_EXCLUDE_REQUIRED,
|
|
271
|
+
help = CLISTRING.OPTION_EXCLUDE_HELP,
|
|
272
|
+
default = CLISTRING.OPTION_EXCLUDE_DEFAULT,
|
|
273
|
+
action = CLISTRING.OPTION_EXCLUDE_ACTION)
|
|
274
|
+
|
|
275
|
+
return argument_parser
|
|
276
|
+
class CLIManager():
|
|
277
|
+
|
|
278
|
+
'''Collects all the logic related to the CLI management.'''
|
|
279
|
+
|
|
280
|
+
__ap_factory : APFactory
|
|
281
|
+
__ascii_banner_manager : AsciiBannerManager
|
|
282
|
+
__docstring_checker : DocStringChecker
|
|
283
|
+
__tw_manager : TerminalWindowManager
|
|
284
|
+
__logging_function : Callable[[str], None]
|
|
285
|
+
|
|
286
|
+
def __init__(
|
|
287
|
+
self,
|
|
288
|
+
ap_factory : APFactory = APFactory(),
|
|
289
|
+
ascii_banner_manager : AsciiBannerManager = AsciiBannerManager(),
|
|
290
|
+
docstring_checker : DocStringChecker = DocStringChecker(),
|
|
291
|
+
tw_manager : TerminalWindowManager = TerminalWindowManager(),
|
|
292
|
+
logging_function : Callable[[str], None] = lambda msg : print(msg)) -> None:
|
|
293
|
+
|
|
294
|
+
self.__ap_factory = ap_factory
|
|
295
|
+
self.__ascii_banner_manager = ascii_banner_manager
|
|
296
|
+
self.__docstring_checker = docstring_checker
|
|
297
|
+
self.__tw_manager = tw_manager
|
|
298
|
+
self.__logging_function = logging_function
|
|
299
|
+
|
|
300
|
+
def __log_ascii_banner(self) -> None:
|
|
301
|
+
|
|
302
|
+
"""Logs the ascii banner."""
|
|
303
|
+
|
|
304
|
+
terminal_width : int = self.__tw_manager.get_or_cutoff()
|
|
305
|
+
ascii_banner : str = self.__ascii_banner_manager.create(PROJECT_VERSION, terminal_width)
|
|
306
|
+
|
|
307
|
+
self.__logging_function("")
|
|
308
|
+
self.__logging_function(ascii_banner)
|
|
309
|
+
def __log_namespace(self, args : Namespace):
|
|
310
|
+
|
|
311
|
+
'''Logs the provided args.'''
|
|
312
|
+
|
|
313
|
+
for key, value in vars(args).items():
|
|
314
|
+
self.__logging_function(f"{key}: '{value}'")
|
|
315
|
+
|
|
316
|
+
self.__logging_function("")
|
|
317
|
+
def __log_docstrings(self, missing: list[str]) -> None:
|
|
318
|
+
|
|
319
|
+
'''Logs missing docstrings.'''
|
|
320
|
+
|
|
321
|
+
if missing:
|
|
322
|
+
for method in missing:
|
|
323
|
+
self.__logging_function(method)
|
|
324
|
+
else:
|
|
325
|
+
self.__logging_function(_MessageCollection.all_methods_have_docstrings())
|
|
326
|
+
|
|
327
|
+
def run_and_log(self) -> None:
|
|
328
|
+
|
|
329
|
+
'''
|
|
330
|
+
Extract the missing docstrings and log them.
|
|
331
|
+
|
|
332
|
+
The SystemExit exception occurs when a required option is not provided.
|
|
333
|
+
SystemExit doesn't inherit from Exception and has no message, therefore we need to handle it accordingly.
|
|
334
|
+
'''
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
|
|
338
|
+
self.__log_ascii_banner()
|
|
339
|
+
|
|
340
|
+
argument_parser : ArgumentParser = self.__ap_factory.create()
|
|
341
|
+
args : Namespace = argument_parser.parse_args()
|
|
342
|
+
|
|
343
|
+
self.__log_namespace(args)
|
|
344
|
+
|
|
345
|
+
missing : list[str] = self.__docstring_checker.run(file_path = args.file_path, exclude = args.exclude)
|
|
346
|
+
|
|
347
|
+
self.__log_docstrings(missing)
|
|
348
|
+
|
|
349
|
+
except (Exception, SystemExit) as e:
|
|
350
|
+
|
|
351
|
+
if not isinstance(e, SystemExit):
|
|
352
|
+
self.__logging_function(str(e))
|
|
353
|
+
|
|
354
|
+
# MAIN
|
|
355
|
+
def main(): CLIManager().run_and_log()
|
|
356
|
+
|
|
357
|
+
if __name__ == "__main__":
|
|
358
|
+
main()
|
setupinfo.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'''Contains project information.'''
|
|
2
|
+
|
|
3
|
+
# INFORMATION
|
|
4
|
+
PROJECT_VERSION : str = "2.0.1"
|
|
5
|
+
PROJECT_AUTHOR : str = "numbworks"
|
|
6
|
+
PROJECT_ALIAS : str = "nwds"
|
|
7
|
+
|
|
8
|
+
LIBRARY_NAME : str = "nwdocstringchecking"
|
|
9
|
+
LIBRARY_DESCRIPTION : str = "A library designed to identify which methods in a Python file are missing docstrings."
|
|
10
|
+
|
|
11
|
+
CLI_NAME : str = "nwdocstringcheckingcli"
|
|
12
|
+
CLI_DESCRIPTION : str = "A CLI application designed to identify which methods in a Python file are missing docstrings."
|
|
13
|
+
|
|
14
|
+
PROJECT_URL : str = f"https://github.com/{PROJECT_AUTHOR}/{LIBRARY_NAME}"
|