docsig 0.47.0__tar.gz → 0.49.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docsig
3
- Version: 0.47.0
3
+ Version: 0.49.0
4
4
  Summary: Check signature params for proper documentation
5
5
  Home-page: https://pypi.org/project/docsig/
6
6
  License: MIT
@@ -21,8 +21,7 @@ Requires-Dist: Pygments (>=2.13.0,<3.0.0)
21
21
  Requires-Dist: Sphinx (>=7.0.0,<8.0.0)
22
22
  Requires-Dist: arcon (>=0.4.0)
23
23
  Requires-Dist: astroid (>=3.0.1,<4.0.0)
24
- Requires-Dist: object-colors (>=2.1.0,<3.0.0)
25
- Requires-Dist: typing-extensions (>=4.8.0,<5.0.0)
24
+ Requires-Dist: click (>=8.1.7,<9.0.0)
26
25
  Project-URL: Documentation, https://docsig.readthedocs.io/en/latest
27
26
  Project-URL: Repository, https://github.com/jshwi/docsig
28
27
  Description-Content-Type: text/x-rst
@@ -139,7 +138,7 @@ Commandline
139
138
  -s STR, --string STR string to parse instead of files
140
139
  -d LIST, --disable LIST comma separated list of rules to disable
141
140
  -t LIST, --target LIST comma separated list of rules to target
142
- -e EXCLUDE, --exclude EXCLUDE regular expression of files or dirs to exclude
141
+ -e PATTERN, --exclude PATTERN regular expression of files or dirs to exclude
143
142
  from checks
144
143
 
145
144
  Options can also be configured with the pyproject.toml file
@@ -182,7 +181,7 @@ API
182
181
  ... :param param3: About param3.
183
182
  ... '''
184
183
  ... """
185
- >>> docsig(string=string)
184
+ >>> docsig(string=string, summary=True, no_ansi=True)
186
185
  0
187
186
 
188
187
  .. code-block:: python
@@ -196,18 +195,9 @@ API
196
195
  ... :param param3: About param3.
197
196
  ... '''
198
197
  ... """
199
- >>> docsig(string=string)
200
- 2
201
- -
202
- def function(✓param1, ✓param2, ✖None) -> ✓None:
203
- """
204
- :param param1: ✓
205
- :param param2: ✓
206
- :param param3: ✖
207
- """
208
- <BLANKLINE>
209
- E102: includes parameters that do not exist (params-do-not-exist)
210
- <BLANKLINE>
198
+ >>> docsig(string=string, summary=True, no_ansi=True)
199
+ 2 in function
200
+ E102: includes parameters that do not exist (params-do-not-exist)
211
201
  1
212
202
 
213
203
  A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/docsig.html#docsig-messages>`__
@@ -233,7 +223,7 @@ It can be added to your .pre-commit-config.yaml as follows:
233
223
 
234
224
  repos:
235
225
  - repo: https://github.com/jshwi/docsig
236
- rev: v0.47.0
226
+ rev: v0.49.0
237
227
  hooks:
238
228
  - id: docsig
239
229
  args:
@@ -110,7 +110,7 @@ Commandline
110
110
  -s STR, --string STR string to parse instead of files
111
111
  -d LIST, --disable LIST comma separated list of rules to disable
112
112
  -t LIST, --target LIST comma separated list of rules to target
113
- -e EXCLUDE, --exclude EXCLUDE regular expression of files or dirs to exclude
113
+ -e PATTERN, --exclude PATTERN regular expression of files or dirs to exclude
114
114
  from checks
115
115
 
116
116
  Options can also be configured with the pyproject.toml file
@@ -153,7 +153,7 @@ API
153
153
  ... :param param3: About param3.
154
154
  ... '''
155
155
  ... """
156
- >>> docsig(string=string)
156
+ >>> docsig(string=string, summary=True, no_ansi=True)
157
157
  0
158
158
 
159
159
  .. code-block:: python
@@ -167,18 +167,9 @@ API
167
167
  ... :param param3: About param3.
168
168
  ... '''
169
169
  ... """
170
- >>> docsig(string=string)
171
- 2
172
- -
173
- def function(✓param1, ✓param2, ✖None) -> ✓None:
174
- """
175
- :param param1: ✓
176
- :param param2: ✓
177
- :param param3: ✖
178
- """
179
- <BLANKLINE>
180
- E102: includes parameters that do not exist (params-do-not-exist)
181
- <BLANKLINE>
170
+ >>> docsig(string=string, summary=True, no_ansi=True)
171
+ 2 in function
172
+ E102: includes parameters that do not exist (params-do-not-exist)
182
173
  1
183
174
 
184
175
  A full list of checks can be found `here <https://docsig.readthedocs.io/en/latest/docsig.html#docsig-messages>`__
@@ -204,7 +195,7 @@ It can be added to your .pre-commit-config.yaml as follows:
204
195
 
205
196
  repos:
206
197
  - repo: https://github.com/jshwi/docsig
207
- rev: v0.47.0
198
+ rev: v0.49.0
208
199
  hooks:
209
200
  - id: docsig
210
201
  args:
@@ -6,9 +6,9 @@ docsig._config
6
6
  from argparse import HelpFormatter as _HelpFormatter
7
7
  from pathlib import Path as _Path
8
8
 
9
+ import click as _click
9
10
  from arcon import ArgumentParser as _ArgumentParser
10
11
 
11
- from ._display import color as _color
12
12
  from ._version import __version__
13
13
 
14
14
 
@@ -18,7 +18,7 @@ class Parser(_ArgumentParser):
18
18
  def __init__(self) -> None:
19
19
  super().__init__(
20
20
  version=__version__,
21
- prog=_color.cyan.get(__package__),
21
+ prog=_click.style(__package__, fg="cyan"),
22
22
  formatter_class=lambda prog: _HelpFormatter(
23
23
  prog, max_help_position=45
24
24
  ),
@@ -149,5 +149,6 @@ class Parser(_ArgumentParser):
149
149
  self.add_argument(
150
150
  "-e",
151
151
  "--exclude",
152
+ metavar="PATTERN",
152
153
  help="regular expression of files or dirs to exclude from checks",
153
154
  )
@@ -7,6 +7,8 @@ from __future__ import annotations as _
7
7
 
8
8
  from pathlib import Path as _Path
9
9
 
10
+ import click as _click
11
+
10
12
  from . import _decorators
11
13
  from ._display import Display as _Display
12
14
  from ._display import Failure as _Failure
@@ -16,7 +18,7 @@ from ._message import Message as _Message
16
18
  from ._module import Function as _Function
17
19
  from ._module import Modules as _Modules
18
20
  from ._module import Parent as _Parent
19
- from ._report import generate_report as _generate_report
21
+ from ._report import Report as _Report
20
22
  from .messages import TEMPLATE as _TEMPLATE
21
23
  from .messages import E as _E
22
24
 
@@ -42,7 +44,7 @@ _DEFAULT_EXCLUDES = """\
42
44
 
43
45
  def _print_checks() -> None:
44
46
  for msg in _E.values():
45
- print(msg.fstring(_TEMPLATE))
47
+ _click.echo(msg.fstring(_TEMPLATE))
46
48
 
47
49
 
48
50
  def _run_check( # pylint: disable=too-many-arguments
@@ -73,13 +75,11 @@ def _run_check( # pylint: disable=too-many-arguments
73
75
  and not (child.isdunder and not check_dunders)
74
76
  and not (child.docstring.bare and ignore_no_params)
75
77
  ):
76
- report = _generate_report(
78
+ report = _Report(
77
79
  child, targets, child.disabled, check_property_returns
78
80
  )
79
81
  if report:
80
- failures.append(
81
- _Failure(child, _FuncStr(child, no_ansi), report)
82
- )
82
+ failures.append(_Failure(child, _FuncStr(child), report))
83
83
 
84
84
  if check_nested:
85
85
  for func in child:
@@ -59,7 +59,7 @@ def validate_args(func: _FuncType) -> _WrappedFuncType:
59
59
  for message in kwargs.get("targets") or []:
60
60
  if not message.isknown:
61
61
  stderr.append(
62
- f"unknown option to target '{message.description}'",
62
+ f"unknown option to target '{message.description}'"
63
63
  )
64
64
 
65
65
  if kwargs.get("check_class") and kwargs.get(
@@ -10,8 +10,6 @@ import tokenize as _tokenize
10
10
  import typing as _t
11
11
  from io import StringIO as _StringIO
12
12
 
13
- from typing_extensions import Self as _Self
14
-
15
13
  from ._message import Message as _Message
16
14
  from .messages import E as _E
17
15
 
@@ -96,7 +94,7 @@ class Directive:
96
94
  return self._kind == self._valid_kinds[1]
97
95
 
98
96
  @classmethod
99
- def parse(cls, comment: str, col: int) -> _Self | None:
97
+ def parse(cls, comment: str, col: int) -> Directive | None:
100
98
  """Parse string into directive object if possible.
101
99
 
102
100
  :param comment: Comment to parse.
@@ -8,7 +8,7 @@ from __future__ import annotations as _
8
8
  import typing as _t
9
9
  from collections import UserString as _UserString
10
10
 
11
- from object_colors import Color as _Color
11
+ import click as _click
12
12
  from pygments import highlight as _highlight
13
13
  from pygments.formatters.terminal256 import (
14
14
  Terminal256Formatter as _Terminal256Formatter,
@@ -23,39 +23,18 @@ from ._stub import ARG as _ARG
23
23
  from ._stub import KEY as _KEY
24
24
  from ._stub import Param as _Param
25
25
 
26
- color = _Color()
27
-
28
- color.populate_colors()
29
-
30
26
  TAB = " "
31
27
 
32
28
 
33
- class _ANSI:
34
- def __init__(self, no_ansi: bool = False) -> None:
35
- self._no_ansi = no_ansi
36
-
37
- def color(self, obj: _t.Any, color_obj: _Color) -> str:
38
- """Get string with selected color.
39
-
40
- :param obj: Any object, represented as ``__str__``.
41
- :param color_obj: Instantiated ``Color`` object.
42
- :return: Colored string or string as was supplied.
43
- """
44
- return str(obj) if self._no_ansi else color_obj.get(obj)
45
-
46
- def syntax(self, obj: _t.Any) -> str:
47
- """Get code representation with syntax highlighting.
29
+ def syntax(obj: _t.Any) -> str:
30
+ """Get code representation with syntax highlighting.
48
31
 
49
- :param obj: Any object, represented as ``__str__``.
50
- :return: Colored string or string as was supplied.
51
- """
52
- return (
53
- str(obj)
54
- if self._no_ansi
55
- else _highlight(
56
- obj, _PythonLexer(), _Terminal256Formatter(style="monokai")
57
- ).strip()
58
- )
32
+ :param obj: Any object, represented as ``__str__``.
33
+ :return: Colored string or string as was supplied.
34
+ """
35
+ return _highlight(
36
+ obj, _PythonLexer(), _Terminal256Formatter(style="monokai")
37
+ ).strip()
59
38
 
60
39
 
61
40
  class FuncStr(_UserString):
@@ -63,16 +42,14 @@ class FuncStr(_UserString):
63
42
 
64
43
  :param func: Represents a function with signature and docstring
65
44
  parameters.
66
- :param no_ansi: Disable ANSI output.
67
45
  """
68
46
 
69
47
  CHECK = "\u2713"
70
48
  CROSS = "\u2716"
71
49
  TRIPLE_QUOTES = '"""'
72
50
 
73
- def __init__(self, func: _Function, no_ansi: bool = False) -> None:
51
+ def __init__(self, func: _Function) -> None:
74
52
  super().__init__(func.name)
75
- self._ansi = _ANSI(no_ansi)
76
53
  self._parent_name = func.parent.name
77
54
  self._isinit = func.isinit
78
55
  self.data = ""
@@ -80,13 +57,13 @@ class FuncStr(_UserString):
80
57
  if self._isinit:
81
58
  self.data += TAB
82
59
 
83
- self.data += self._ansi.syntax(f"def {func.name}(")
60
+ self.data += syntax(f"def {func.name}(")
84
61
  if self._is_string:
85
- self._docstring = self._ansi.syntax(f"{TAB}{self.TRIPLE_QUOTES}")
62
+ self._docstring = syntax(f"{TAB}{self.TRIPLE_QUOTES}")
86
63
  else:
87
- self._docstring = f"{TAB}{self._ansi.color('...', color.red)}\n"
64
+ self._docstring = f"{TAB}{_click.style('...', fg='red')}\n"
88
65
 
89
- self._mark = self._ansi.color(self.CHECK, color.green)
66
+ self._mark = _click.style(self.CHECK, fg="green")
90
67
  for index in range(len(func)):
91
68
  arg = func.signature.args.get(index)
92
69
  doc = func.docstring.args.get(index)
@@ -119,9 +96,9 @@ class FuncStr(_UserString):
119
96
  :param failed: Boolean to test that check failed.
120
97
  """
121
98
  self._mark = (
122
- self._ansi.color(self.CROSS, color.red)
99
+ _click.style(self.CROSS, fg="red")
123
100
  if failed
124
- else self._ansi.color(self.CHECK, color.green)
101
+ else _click.style(self.CHECK, fg="green")
125
102
  )
126
103
 
127
104
  def add_param(
@@ -162,34 +139,27 @@ class FuncStr(_UserString):
162
139
  :param arg: Signature argument.
163
140
  """
164
141
  if arg is not None:
165
- self.data += "{}{}{}{}".format(
166
- self._ansi.syntax(") -> "),
167
- self._mark,
168
- arg,
169
- self._ansi.syntax(":"),
170
- )
142
+ self.data += f"{syntax(') -> ')}{self._mark}{arg}{syntax(':')}"
171
143
  else:
172
144
  self.data += "{}{}{}".format(
173
- self._ansi.syntax(")"),
174
- self._ansi.color("?", color.red),
175
- self._ansi.syntax(":"),
145
+ syntax(")"),
146
+ _click.style("?", fg="red"),
147
+ syntax(":"),
176
148
  )
177
149
 
178
150
  def add_comma(self) -> None:
179
151
  """Add comma between parenthesis."""
180
- self.data += self._ansi.syntax(", ")
152
+ self.data += syntax(", ")
181
153
 
182
154
  def close_docstring(self) -> None:
183
155
  """Close docstring."""
184
- self._cat_docstring(
185
- f"\n{TAB}{self._ansi.syntax(self.TRIPLE_QUOTES)}\n"
186
- )
156
+ self._cat_docstring(f"\n{TAB}{syntax(self.TRIPLE_QUOTES)}\n")
187
157
 
188
158
  def render(self) -> None:
189
159
  """Render final string by adding docstring to function."""
190
160
  if self._isinit:
191
161
  self.data = (
192
- self._ansi.syntax(f"class {self._parent_name}:")
162
+ syntax(f"class {self._parent_name}:")
193
163
  + f"\n{self._docstring}"
194
164
  + f"\n{self.data}\n"
195
165
  )
@@ -209,15 +179,7 @@ class Failures(_t.List[Failure]):
209
179
  """Sequence of failed functions."""
210
180
 
211
181
 
212
- class _DisplaySequence(_t.Dict[str, _t.List[Failures]]):
213
- def __getitem__(self, key: str) -> list[Failures]:
214
- if key not in super().__iter__():
215
- super().__setitem__(key, [])
216
-
217
- return super().__getitem__(key)
218
-
219
-
220
- class Display(_DisplaySequence):
182
+ class Display(_t.Dict[str, _t.List[Failures]]):
221
183
  """Collect and display report.
222
184
 
223
185
  :param no_ansi: Disable ANSI output.
@@ -225,7 +187,13 @@ class Display(_DisplaySequence):
225
187
 
226
188
  def __init__(self, no_ansi: bool = False) -> None:
227
189
  super().__init__()
228
- self._ansi = _ANSI(no_ansi)
190
+ self._ansi = not no_ansi
191
+
192
+ def __getitem__(self, key: str) -> list[Failures]:
193
+ if key not in super().__iter__():
194
+ super().__setitem__(key, [])
195
+
196
+ return super().__getitem__(key)
229
197
 
230
198
  def report(self) -> None:
231
199
  """Display report if any checks have failed."""
@@ -236,10 +204,12 @@ class Display(_DisplaySequence):
236
204
  if failure.func.parent.name:
237
205
  header += f" in {failure.func.parent.name}"
238
206
 
239
- print(self._ansi.color(header, color.magenta))
240
- print(len(header) * "-")
241
- print(failure.func_str)
242
- print(failure.report.get_report())
207
+ _click.echo(
208
+ _click.style(header, fg="magenta"), color=self._ansi
209
+ )
210
+ _click.echo(len(header) * "-", color=self._ansi)
211
+ _click.echo(failure.func_str, color=self._ansi)
212
+ _click.echo(failure.report.get_report(), color=self._ansi)
243
213
 
244
214
  def summarise(self) -> None:
245
215
  """Display report summary if any checks have failed."""
@@ -252,9 +222,10 @@ class Display(_DisplaySequence):
252
222
  function = f"{failure.func.parent.name}.{function}"
253
223
 
254
224
  header += f" in {function}"
255
- print(
256
- "{}\n\t{}".format(
257
- self._ansi.color(header, color.magenta),
258
- failure.report.get_report("\t").strip(),
259
- )
225
+ _click.echo(
226
+ "{}\n {}".format(
227
+ _click.style(header, fg="magenta"),
228
+ failure.report.get_report(" ").strip(),
229
+ ),
230
+ color=self._ansi,
260
231
  )
@@ -6,12 +6,12 @@ docsig._hooks
6
6
  import sys as _sys
7
7
  from os import environ as _e
8
8
 
9
- from ._display import color as _color
9
+ import click as _click
10
10
 
11
11
 
12
12
  def pretty_print_error() -> None:
13
13
  """Print user friendly commandline error if debug not enabled."""
14
14
  if _e.get("DOCSIG_DEBUG", None) != "1":
15
15
  _sys.excepthook = lambda x, y, _: print(
16
- f"{_color.red.bold.get(x.__name__)}: {y}"
16
+ f"{_click.style(x.__name__, fg='red', bold=True)}: {y}"
17
17
  )
@@ -0,0 +1,45 @@
1
+ """
2
+ docsig._main
3
+ ============
4
+
5
+ Contains package entry point.
6
+ """
7
+
8
+ from __future__ import annotations as _
9
+
10
+ from ._config import Parser as _Parser
11
+ from ._core import docsig as _docsig
12
+ from ._hooks import pretty_print_error as _pretty_print_error
13
+
14
+
15
+ def main() -> str | int:
16
+ """Main function for package.
17
+
18
+ Collect config and arguments for the commandline.
19
+
20
+ :return: Exit status for whether test failed or not.
21
+ """
22
+ p = _Parser()
23
+ _pretty_print_error()
24
+ return _docsig(
25
+ *p.args.path,
26
+ string=p.args.string,
27
+ list_checks=p.args.list_checks,
28
+ check_class=p.args.check_class,
29
+ check_class_constructor=p.args.check_class_constructor,
30
+ check_dunders=p.args.check_dunders,
31
+ check_protected_class_methods=p.args.check_protected_class_methods,
32
+ check_nested=p.args.check_nested,
33
+ check_overridden=p.args.check_overridden,
34
+ check_protected=p.args.check_protected,
35
+ check_property_returns=p.args.check_property_returns,
36
+ ignore_no_params=p.args.ignore_no_params,
37
+ ignore_args=p.args.ignore_args,
38
+ ignore_kwargs=p.args.ignore_kwargs,
39
+ no_ansi=p.args.no_ansi,
40
+ summary=p.args.summary,
41
+ verbose=p.args.verbose,
42
+ targets=p.args.target,
43
+ disable=p.args.disable,
44
+ exclude=p.args.exclude,
45
+ )
@@ -0,0 +1,196 @@
1
+ """
2
+ docsig._report
3
+ ==============
4
+ """
5
+
6
+ from __future__ import annotations as _
7
+
8
+ import typing as _t
9
+
10
+ from ._message import Message as _Message
11
+ from ._module import Function as _Function
12
+ from ._stub import RETURN as _RETURN
13
+ from ._stub import Param as _Param
14
+ from ._utils import almost_equal as _almost_equal
15
+ from .messages import TEMPLATE as _TEMPLATE
16
+ from .messages import E as _E
17
+
18
+ _MIN_MATCH = 0.8
19
+ _MAX_MATCH = 1.0
20
+
21
+
22
+ class Report(_t.List[str]):
23
+ """Compile and produce report.
24
+
25
+ :param func: Function object.
26
+ :param targets: List of errors to target.
27
+ :param disable: List of errors to disable.
28
+ :param check_property_returns: Run return checks on properties.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ func: _Function,
34
+ targets: list[_Message],
35
+ disable: list[_Message],
36
+ check_property_returns: bool,
37
+ ) -> None:
38
+ super().__init__()
39
+ self._disable = list(disable)
40
+ if targets:
41
+ errors = list(_E.all(1))
42
+ for target in targets:
43
+ errors.remove(target)
44
+
45
+ self._disable.extend(errors)
46
+
47
+ self._errors: list[_Message] = []
48
+ self._func = func
49
+ self._no_prop_return = func.isproperty and not check_property_returns
50
+ self._no_returns = func.isinit or self._no_prop_return
51
+ self._invalid_directive()
52
+ self._invalid_directive_options()
53
+ self._missing_class_docstring()
54
+ self._missing_func_docstring()
55
+ if func.docstring.string is not None:
56
+ self._return_not_typed()
57
+ self._exists()
58
+ self._missing()
59
+ self._duplicates()
60
+ self._extra_return()
61
+ self._missing_return()
62
+ self._property_return()
63
+ self._class_return()
64
+ for index in range(len(func)):
65
+ arg = func.signature.args.get(index)
66
+ doc = func.docstring.args.get(index)
67
+ self._description_syntax(doc)
68
+ self._indent_syntax(doc)
69
+ if arg != doc:
70
+ self._order(arg, doc)
71
+ self._incorrect(arg, doc)
72
+ self._misspelled(arg, doc)
73
+ self._not_equal(arg, doc)
74
+
75
+ self.sort()
76
+
77
+ def _add(self, value: _Message, hint: bool = False, **kwargs) -> None:
78
+ self._errors.append(value)
79
+ message = value.fstring(_TEMPLATE)
80
+ if kwargs:
81
+ message = message.format(**kwargs)
82
+
83
+ if hint:
84
+ message += f"\nhint: {value.hint}"
85
+
86
+ if value not in self._disable and message not in self:
87
+ super().append(message)
88
+
89
+ def _order(self, sig: _Param, doc: _Param) -> None:
90
+ if any(sig.name == i.name for i in self._func.docstring.args) or any(
91
+ doc.name == i.name for i in self._func.signature.args
92
+ ):
93
+ self._add(_E[101])
94
+
95
+ def _exists(self) -> None:
96
+ if len(self._func.docstring.args) > len(self._func.signature.args):
97
+ self._add(_E[102])
98
+
99
+ def _missing_func_docstring(self) -> None:
100
+ if not self._func.isinit and self._func.docstring.string is None:
101
+ self._add(_E[113])
102
+
103
+ def _missing_class_docstring(self) -> None:
104
+ if self._func.isinit and self._func.docstring.string is None:
105
+ self._add(_E[114])
106
+
107
+ def _missing(self) -> None:
108
+ if len(self._func.signature.args) > len(self._func.docstring.args):
109
+ self._add(_E[103])
110
+
111
+ def _duplicates(self) -> None:
112
+ if self._func.docstring.args.duplicated:
113
+ self._add(_E[106])
114
+
115
+ def _extra_return(self) -> None:
116
+ if (
117
+ self._func.docstring.returns
118
+ and self._func.signature.rettype == "None"
119
+ and not self._no_returns
120
+ ):
121
+ self._add(_E[104])
122
+
123
+ def _property_return(self) -> None:
124
+ if self._func.docstring.returns and self._no_prop_return:
125
+ self._add(_E[108], hint=True)
126
+
127
+ def _return_not_typed(self) -> None:
128
+ if self._func.signature.rettype is None and not self._no_returns:
129
+ self._add(_E[109])
130
+
131
+ def _missing_return(self) -> None:
132
+ hint = False
133
+ if (
134
+ self._func.signature.returns
135
+ and not self._func.docstring.returns
136
+ and not self._no_returns
137
+ ):
138
+ docstring = self._func.docstring.string
139
+ if docstring is not None and _RETURN in docstring:
140
+ hint = True
141
+
142
+ self._add(_E[105], hint=hint)
143
+
144
+ def _incorrect(self, sig: _Param, doc: _Param) -> None:
145
+ if sig.name is None and doc.name is None:
146
+ self._add(_E[107])
147
+
148
+ # final catch-all
149
+ def _not_equal(self, sig: _Param, doc: _Param) -> None:
150
+ if sig.name is not None and doc.name is not None and not self._errors:
151
+ self._add(_E[110])
152
+
153
+ def _class_return(self) -> None:
154
+ if self._func.docstring.returns and self._func.isinit:
155
+ self._add(_E[111], hint=True)
156
+
157
+ def _misspelled(self, sig: _Param, doc: _Param) -> None:
158
+ if (
159
+ sig.name is not None
160
+ and doc.name is not None
161
+ and not self._errors
162
+ and _almost_equal(sig.name, doc.name, _MIN_MATCH, _MAX_MATCH)
163
+ ):
164
+ self._add(_E[112])
165
+
166
+ def _description_syntax(self, doc: _Param) -> None:
167
+ if doc.description is not None and not doc.description.startswith(" "):
168
+ self._add(_E[115])
169
+
170
+ def _indent_syntax(self, doc: _Param) -> None:
171
+ if doc.indent > 0:
172
+ self._add(_E[116])
173
+
174
+ def _invalid_directive(self) -> None:
175
+ for directive in self._func.directives:
176
+ if not directive.isvalid:
177
+ err = _E[int(f"20{1 if directive.ismodule else 2}")]
178
+ self._add(err, directive=directive.kind)
179
+
180
+ def _invalid_directive_options(self) -> None:
181
+ for directive in self._func.directives:
182
+ if directive.rules.unknown:
183
+ err = _E[int(f"20{3 if directive.ismodule else 4}")]
184
+ for rule in directive.rules.unknown:
185
+ self._add(
186
+ err, directive=directive.kind, option=rule.description
187
+ )
188
+
189
+ def get_report(self, prefix: str = "") -> str:
190
+ """Get report compiled as a string.
191
+
192
+ :param prefix: Prefix report.
193
+ :return: Current report.
194
+ """
195
+ report = f"\n{prefix}".join(self)
196
+ return f"{report}\n"
@@ -20,16 +20,19 @@ RETURN = "return"
20
20
  ARG = "arg"
21
21
 
22
22
 
23
+ # noinspection PyTypeChecker
23
24
  class _GoogleDocstring(str):
24
25
  def __new__(cls, string: str) -> _GoogleDocstring:
25
26
  return super().__new__(cls, str(_s.GoogleDocstring(string)))
26
27
 
27
28
 
29
+ # noinspection PyTypeChecker
28
30
  class _NumpyDocstring(str):
29
31
  def __new__(cls, string: str) -> _NumpyDocstring:
30
32
  return super().__new__(cls, str(_s.NumpyDocstring(string)))
31
33
 
32
34
 
35
+ # noinspection PyTypeChecker
33
36
  class _DocFmt(str):
34
37
  def __new__(cls, string: str) -> _DocFmt:
35
38
  return super().__new__(
@@ -40,6 +43,7 @@ class _DocFmt(str):
40
43
  )
41
44
 
42
45
 
46
+ # noinspection PyTypeChecker
43
47
  class _RawDocstring(str):
44
48
  def __new__(cls, string: str) -> _RawDocstring:
45
49
  return super().__new__(
@@ -7,6 +7,8 @@ from __future__ import annotations as _
7
7
 
8
8
  from difflib import SequenceMatcher as _SequenceMatcher
9
9
 
10
+ import click as _click
11
+
10
12
 
11
13
  def almost_equal(str1: str, str2: str, mini: float, maxi: float) -> bool:
12
14
  """Show result for more than the minimum but less than the maximum.
@@ -28,4 +30,4 @@ def vprint(msg: str, verbose: bool = False) -> None:
28
30
  :param verbose: Whether verbose mode is enabled.
29
31
  """
30
32
  if verbose:
31
- print(msg)
33
+ _click.echo(msg)
@@ -8,4 +8,4 @@ Allows for access to the version internally without cyclic imports
8
8
  caused by accessing it through __init__.
9
9
  """
10
10
 
11
- __version__ = "0.47.0"
11
+ __version__ = "0.49.0"
@@ -19,6 +19,7 @@ line-length = 79
19
19
  [tool.constcheck]
20
20
  ignore_strings = [
21
21
  "disable",
22
+ "red",
22
23
  "store",
23
24
  "store_true",
24
25
  "targets",
@@ -72,16 +73,15 @@ maintainers = [
72
73
  name = "docsig"
73
74
  readme = "README.rst"
74
75
  repository = "https://github.com/jshwi/docsig"
75
- version = "0.47.0"
76
+ version = "0.49.0"
76
77
 
77
78
  [tool.poetry.dependencies]
78
79
  Pygments = "^2.13.0"
79
80
  Sphinx = "^7.0.0"
80
81
  arcon = ">=0.4.0"
81
82
  astroid = "^3.0.1"
82
- object-colors = "^2.1.0"
83
+ click = "^8.1.7"
83
84
  python = "^3.8"
84
- typing-extensions = "^4.8.0"
85
85
 
86
86
  [tool.poetry.dev-dependencies]
87
87
  bump2version = "^1.0.1"
@@ -1,47 +0,0 @@
1
- """
2
- docsig._main
3
- ============
4
-
5
- Contains package entry point.
6
- """
7
-
8
- from __future__ import annotations as _
9
-
10
- from ._config import Parser as _Parser
11
- from ._core import docsig as _docsig
12
- from ._hooks import pretty_print_error as _pretty_print_error
13
-
14
-
15
- def main() -> str | int:
16
- """Main function for package.
17
-
18
- Collect config and arguments for the commandline.
19
-
20
- :return: Exit status for whether test failed or not.
21
- """
22
- parser = _Parser()
23
- _pretty_print_error()
24
- return _docsig(
25
- *parser.args.path,
26
- string=parser.args.string,
27
- list_checks=parser.args.list_checks,
28
- check_class=parser.args.check_class,
29
- check_class_constructor=parser.args.check_class_constructor,
30
- check_dunders=parser.args.check_dunders,
31
- check_protected_class_methods=(
32
- parser.args.check_protected_class_methods
33
- ),
34
- check_nested=parser.args.check_nested,
35
- check_overridden=parser.args.check_overridden,
36
- check_protected=parser.args.check_protected,
37
- check_property_returns=parser.args.check_property_returns,
38
- ignore_no_params=parser.args.ignore_no_params,
39
- ignore_args=parser.args.ignore_args,
40
- ignore_kwargs=parser.args.ignore_kwargs,
41
- no_ansi=parser.args.no_ansi,
42
- summary=parser.args.summary,
43
- verbose=parser.args.verbose,
44
- targets=parser.args.target,
45
- disable=parser.args.disable,
46
- exclude=parser.args.exclude,
47
- )
@@ -1,275 +0,0 @@
1
- """
2
- docsig._report
3
- ==============
4
- """
5
-
6
- from __future__ import annotations as _
7
-
8
- import typing as _t
9
-
10
- from ._message import Message as _Message
11
- from ._module import Function as _Function
12
- from ._stub import RETURN as _RETURN
13
- from ._stub import Param as _Param
14
- from ._utils import almost_equal as _almost_equal
15
- from .messages import TEMPLATE as _TEMPLATE
16
- from .messages import E as _E
17
-
18
- _MIN_MATCH = 0.8
19
- _MAX_MATCH = 1.0
20
-
21
-
22
- class _MessageSequence(_t.List[str]):
23
- def __init__(
24
- self,
25
- targets: list[_Message],
26
- disable: list[_Message],
27
- ) -> None:
28
- super().__init__()
29
- self._disable = list(disable)
30
- if targets:
31
- errors = list(_E.all(1))
32
- for target in targets:
33
- errors.remove(target)
34
-
35
- self._disable.extend(errors)
36
-
37
- self._errors: list[_Message] = []
38
-
39
- def add(self, value: _Message, hint: bool = False, **kwargs) -> None:
40
- """Add an error to the container.
41
-
42
- :param value: Value to add.
43
- :param hint: Whether to print a hint or not.
44
- :param kwargs: Variable(s) if format string.
45
- """
46
- self._errors.append(value)
47
- message = value.fstring(_TEMPLATE)
48
- if kwargs:
49
- message = message.format(**kwargs)
50
-
51
- if hint:
52
- message += f"\nhint: {value.hint}"
53
-
54
- if value not in self._disable and message not in self:
55
- super().append(message)
56
-
57
-
58
- class Report(_MessageSequence):
59
- """Compile and produce report.
60
-
61
- :param func: Function object.
62
- :param targets: List of errors to target.
63
- :param disable: List of errors to disable.
64
- :param check_property_returns: Run return checks on properties.
65
- """
66
-
67
- def __init__(
68
- self,
69
- func: _Function,
70
- targets: list[_Message],
71
- disable: list[_Message],
72
- check_property_returns: bool,
73
- ) -> None:
74
- super().__init__(targets, disable)
75
- self._func = func
76
- self._no_prop_return = func.isproperty and not check_property_returns
77
- self._no_returns = func.isinit or self._no_prop_return
78
-
79
- def order(self, sig: _Param, doc: _Param) -> None:
80
- """Test for documented parameters and their order.
81
-
82
- :param sig: Signature argument.
83
- :param doc: Docstring argument.
84
- """
85
- if any(sig.name == i.name for i in self._func.docstring.args) or any(
86
- doc.name == i.name for i in self._func.signature.args
87
- ):
88
- self.add(_E[101])
89
-
90
- def exists(self) -> None:
91
- """Test that non-existing parameter is not documented."""
92
- if len(self._func.docstring.args) > len(self._func.signature.args):
93
- self.add(_E[102])
94
-
95
- def missing_func_docstring(self) -> None:
96
- """Test that docstring is not missing from func."""
97
- if not self._func.isinit and self._func.docstring.string is None:
98
- self.add(_E[113])
99
-
100
- def missing_class_docstring(self) -> None:
101
- """Test that docstring is not missing from class."""
102
- if self._func.isinit and self._func.docstring.string is None:
103
- self.add(_E[114])
104
-
105
- def missing(self) -> None:
106
- """Test that parameter is not missing from documentation."""
107
- if len(self._func.signature.args) > len(self._func.docstring.args):
108
- self.add(_E[103])
109
-
110
- def duplicates(self) -> None:
111
- """Test that there are no duplicate parameters in docstring."""
112
- if self._func.docstring.args.duplicated:
113
- self.add(_E[106])
114
-
115
- def extra_return(self) -> None:
116
- """Check that return is not documented when there is none."""
117
- if (
118
- self._func.docstring.returns
119
- and self._func.signature.rettype == "None"
120
- and not self._no_returns
121
- ):
122
- self.add(_E[104])
123
-
124
- def property_return(self) -> None:
125
- """Check that return is not documented for property."""
126
- if self._func.docstring.returns and self._no_prop_return:
127
- self.add(_E[108], hint=True)
128
-
129
- def return_not_typed(self) -> None:
130
- """Check that return is not documented when no type provided."""
131
- if self._func.signature.rettype is None and not self._no_returns:
132
- self.add(_E[109])
133
-
134
- def missing_return(self) -> None:
135
- """Check that return is documented when func returns value."""
136
- hint = False
137
- if (
138
- self._func.signature.returns
139
- and not self._func.docstring.returns
140
- and not self._no_returns
141
- ):
142
- docstring = self._func.docstring.string
143
- if docstring is not None and _RETURN in docstring:
144
- hint = True
145
-
146
- self.add(_E[105], hint=hint)
147
-
148
- def incorrect(self, sig: _Param, doc: _Param) -> None:
149
- """Test that proper syntax is used when documenting parameters.
150
-
151
- :param sig: Signature argument.
152
- :param doc: Docstring argument.
153
- """
154
- if sig.name is None and doc.name is None:
155
- self.add(_E[107])
156
-
157
- def not_equal(self, sig: _Param, doc: _Param) -> None:
158
- """Final catch-all.
159
-
160
- Only applies if no other errors, including disabled, have been
161
- triggered
162
-
163
- :param sig: Signature argument.
164
- :param doc: Docstring argument.
165
- """
166
- if sig.name is not None and doc.name is not None and not self._errors:
167
- self.add(_E[110])
168
-
169
- def class_return(self) -> None:
170
- """Check that return is not documented for __init__."""
171
- if self._func.docstring.returns and self._func.isinit:
172
- self.add(_E[111], hint=True)
173
-
174
- def misspelled(self, sig: _Param, doc: _Param) -> None:
175
- """Test whether there is a spelling error in documentation.
176
-
177
- To avoid false positives also check whether doc param is almost
178
- equal amongst its sibling params. If params are too similarly
179
- named then this error won't be raised.
180
-
181
- :param sig: Signature argument.
182
- :param doc: Docstring argument.
183
- """
184
- if (
185
- sig.name is not None
186
- and doc.name is not None
187
- and not self._errors
188
- and _almost_equal(sig.name, doc.name, _MIN_MATCH, _MAX_MATCH)
189
- ):
190
- self.add(_E[112])
191
-
192
- def description_syntax(self, doc: _Param) -> None:
193
- """Test whether docstring description has correct spacing.
194
-
195
- :param doc: Docstring argument.
196
- """
197
- if doc.description is not None and not doc.description.startswith(" "):
198
- self.add(_E[115])
199
-
200
- def indent_syntax(self, doc: _Param) -> None:
201
- """Test whether docstring description is indented correctly.
202
-
203
- :param doc: Docstring argument.
204
- """
205
- if doc.indent > 0:
206
- self.add(_E[116])
207
-
208
- def invalid_directive(self) -> None:
209
- """Report on any invalid directives belonging to this func."""
210
- for directive in self._func.directives:
211
- if not directive.isvalid:
212
- err = _E[int(f"20{1 if directive.ismodule else 2}")]
213
- self.add(err, directive=directive.kind)
214
-
215
- def invalid_directive_options(self) -> None:
216
- """Report on any invalid directive options belonging to this."""
217
- for directive in self._func.directives:
218
- if directive.rules.unknown:
219
- err = _E[int(f"20{3 if directive.ismodule else 4}")]
220
- for rule in directive.rules.unknown:
221
- self.add(
222
- err, directive=directive.kind, option=rule.description
223
- )
224
-
225
- def get_report(self, prefix: str = "") -> str:
226
- """Get report compiled as a string.
227
-
228
- :param prefix: Prefix report.
229
- :return: Current report.
230
- """
231
- report = f"\n{prefix}".join(self)
232
- return f"{report}\n"
233
-
234
-
235
- def generate_report(
236
- func: _Function,
237
- targets: list[_Message],
238
- disable: list[_Message],
239
- check_property_returns: bool,
240
- ) -> Report:
241
- """Generate report if function or method has failed.
242
-
243
- :param func: Function object.
244
- :param targets: List of errors to target.
245
- :param disable: List of errors to disable.
246
- :param check_property_returns: Run return checks on properties.
247
- :return: Compiled report.
248
- """
249
- report = Report(func, targets, disable, check_property_returns)
250
- report.invalid_directive()
251
- report.invalid_directive_options()
252
- report.missing_class_docstring()
253
- report.missing_func_docstring()
254
- if func.docstring.string is not None:
255
- report.return_not_typed()
256
- report.exists()
257
- report.missing()
258
- report.duplicates()
259
- report.extra_return()
260
- report.missing_return()
261
- report.property_return()
262
- report.class_return()
263
- for index in range(len(func)):
264
- arg = func.signature.args.get(index)
265
- doc = func.docstring.args.get(index)
266
- report.description_syntax(doc)
267
- report.indent_syntax(doc)
268
- if arg != doc:
269
- report.order(arg, doc)
270
- report.incorrect(arg, doc)
271
- report.misspelled(arg, doc)
272
- report.not_equal(arg, doc)
273
-
274
- report.sort()
275
- return report
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes