Skip to content

Commit 8e0baca

Browse files
authored
Merge pull request #14 from d-chris/develop
Enhance PdocStr functionality
2 parents 3445854 + 807d403 commit 8e0baca

File tree

7 files changed

+159
-40
lines changed

7 files changed

+159
-40
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
hooks:
1212
- id: add-trailing-comma
1313
- repo: https://github.com/tox-dev/pyproject-fmt
14-
rev: 2.4.3
14+
rev: v2.4.3
1515
hooks:
1616
- id: pyproject-fmt
1717
- repo: https://github.com/tox-dev/tox-ini-fmt
@@ -27,7 +27,7 @@ repos:
2727
hooks:
2828
- id: black
2929
- repo: https://github.com/adamchainz/blacken-docs
30-
rev: "1.19.0"
30+
rev: "1.19.1"
3131
hooks:
3232
- id: blacken-docs
3333
files: pathlibutil/

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ optional `str` functions can be added to `<pdoc_attr>` with a dot
141141
- `indent` - format code with 2 spaces for indentation, see `autopep8.fix_code`
142142
- `upper` - converts to upper case
143143
- `lower` - converts to lower case
144+
- `nodoc` - removes shebang and docstring
144145

145146
Example:
146147

@@ -206,6 +207,20 @@ repos:
206207
files: docs/.*\.jinja2$
207208
```
208209
210+
Use `additional_dependencies` to add extra dependencies to the pre-commit environment. Example see below.
211+
212+
> This is necessary when a module or source code rendered into your template contains modules that are not part of the standard library.
213+
214+
```yaml
215+
repos:
216+
- repo: https://github.com/d-chris/jinja2_pdoc/
217+
rev: v1.1.0
218+
hooks:
219+
- id: jinja2pdoc
220+
files: docs/.*\.jinja2$
221+
additional_dependencies: [pathlibutil]
222+
```
223+
209224
## Dependencies
210225

211226
[![PyPI - autopep8](https://img.shields.io/pypi/v/autopep8?logo=pypi&logoColor=white&label=autopep8)](https://pypi.org/project/autopep8/)

docs/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@ def main() -> int:
104104

105105

106106
if __name__ == "__main__":
107-
SystemExit(main())
107+
raise SystemExit(main())

jinja2_pdoc/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ def jinja2pdoc(
102102

103103
root = Path(output) if output else cwd
104104

105-
env = Environment()
105+
env = Environment(
106+
keep_trailing_newline=True,
107+
)
106108

107109
def render_file(file):
108110
template = file.read_text(encoding)

jinja2_pdoc/wrapper.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ class PdocStr(str):
9696
inhertits from `str` with a `dedent` method
9797
"""
9898

99+
_regex_doc = re.compile(
100+
r"^(?:#!.*?)?(?P<doc>\"{3}|\'{3}).*?(?P=doc)\s*$",
101+
re.MULTILINE | re.DOTALL,
102+
)
103+
99104
def dedent(self) -> str:
100105
"""
101106
remove common whitespace from the left of every line in the string,
@@ -110,3 +115,30 @@ def indent(self) -> str:
110115
"""
111116
s = autopep8.fix_code(self.dedent(), options={"indent_size": 2})
112117
return self.__class__(s)
118+
119+
def nodoc(self) -> str:
120+
"""
121+
remove shebang and docstring and from the string
122+
"""
123+
s = self._regex_doc.sub("", self.dedent(), 1)
124+
125+
return self.__class__(s.strip("\n"))
126+
127+
def __getattribute__(self, name: str) -> Any:
128+
"""
129+
get all known attributes and cast `str` to `PdocStr`
130+
"""
131+
attr = super().__getattribute__(name)
132+
133+
if callable(attr):
134+
135+
def wrapper(*args, **kwargs):
136+
result = attr(*args, **kwargs)
137+
if isinstance(result, str):
138+
cls = object.__getattribute__(self, "__class__")
139+
return cls(result)
140+
return result
141+
142+
return wrapper
143+
144+
return attr

poetry.lock

Lines changed: 6 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/test_wrapper.py

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,133 @@
44

55

66
@pytest.fixture
7-
def doc() -> Module:
7+
def module() -> Module:
88
return Module.from_name("pathlib")
99

1010

1111
@pytest.fixture
12-
def open(doc: Module) -> Function:
13-
return doc.get("Path.open")
12+
def function(module: Module) -> Function:
13+
return module.get("Path.open")
1414

1515

1616
@pytest.fixture
17-
def funcstr() -> PdocStr:
17+
def pdocstr() -> PdocStr:
1818
return PdocStr("\n".join([" def dummy():", " pass"]))
1919

2020

21-
def test_module(doc: Module):
22-
assert isinstance(doc, Module)
21+
@pytest.fixture(params=["indent", "dedent", "nodoc", "lower", "upper"])
22+
def pdocstr_attr(request):
23+
return request.param
2324

2425

25-
def test_class(doc: Module):
26-
cls = doc.get("Path")
26+
@pytest.fixture(params=["source", "code", "docstring"])
27+
def function_prop(request):
28+
return request.param
2729

28-
assert cls.name == "Path"
29-
assert isinstance(cls, Function)
3030

31-
cls = doc.get("NotAClass")
32-
assert cls is None
31+
def test_module():
32+
m = Module.from_name("pathlib")
3333

34-
func = doc.get("Path.notafunction")
35-
assert func is None
34+
assert isinstance(m, Module)
3635

3736

38-
def test_func(open: Function):
39-
assert open.name == "open"
40-
assert isinstance(open, Function)
41-
assert hasattr(open, "code")
37+
def test_module_raises():
38+
with pytest.raises(RuntimeError):
39+
Module.from_name("not_a_module")
4240

4341

44-
def test_str(open: Function):
45-
sourcecode = open.code
42+
@pytest.mark.parametrize(
43+
"name, returntype",
44+
[
45+
("Path", Function),
46+
("Path.open", Function),
47+
("NotAClass", type(None)),
48+
("Path.notafunction", type(None)),
49+
],
50+
)
51+
def test_module_returntype(module: Module, name: str, returntype: type):
52+
obj = module.get(name)
53+
assert isinstance(obj, returntype)
4654

47-
assert isinstance(sourcecode, PdocStr)
48-
assert hasattr(sourcecode, "dedent")
4955

50-
assert isinstance(open.docstring, PdocStr)
56+
def test_pdocstr_attributes(pdocstr_attr: str, pdocstr: PdocStr):
5157

58+
assert hasattr(pdocstr, pdocstr_attr)
5259

53-
def test_module_raises():
54-
with pytest.raises(RuntimeError):
55-
Module.from_name("not_a_module")
5660

61+
def test_pdocstr_returntypes(pdocstr_attr, pdocstr: PdocStr):
62+
method = getattr(pdocstr, pdocstr_attr)
5763

58-
def test_dedent(funcstr: PdocStr):
59-
s = funcstr.dedent()
64+
assert isinstance(method(), PdocStr)
6065

61-
assert isinstance(s, PdocStr)
62-
assert s.startswith("def dummy():\n pass")
6366

67+
def test_pdocstr_callable(pdocstr_attr, pdocstr: PdocStr):
68+
method = getattr(pdocstr, pdocstr_attr)
69+
70+
assert callable(method)
71+
72+
73+
def test_pdocstr_nodoc():
74+
text = [
75+
"",
76+
'"""docstring"""',
77+
"",
78+
"def dummy():", # 3
79+
" pass",
80+
"",
81+
]
82+
83+
funcstr = PdocStr("\n".join(text))
84+
85+
assert funcstr.nodoc() == "\n".join(text[3:5])
86+
87+
88+
def test_pdocstr_shebang():
89+
text = [
90+
"#! python3",
91+
"",
92+
'"""docstring"""',
93+
"",
94+
"def dummy():", # 4
95+
" pass",
96+
"",
97+
]
98+
99+
funcstr = PdocStr("\n".join(text))
100+
101+
assert funcstr.nodoc() == "\n".join(text[4:6])
64102

65-
def test_autopep8(funcstr: PdocStr):
66-
s = funcstr.indent()
67103

68-
assert isinstance(s, PdocStr)
104+
def test_pdocstr_indent(pdocstr: PdocStr):
105+
s = pdocstr.indent()
106+
69107
assert s.startswith("def dummy():\n pass")
108+
109+
110+
def test_pdocstr_dedent(pdocstr: PdocStr):
111+
s = pdocstr.dedent()
112+
113+
assert s.startswith("def dummy():\n pass")
114+
115+
116+
def test_function_attributes(function_prop, function):
117+
assert hasattr(function, function_prop)
118+
119+
120+
def test_function_returntypes(function_prop, function):
121+
prop = getattr(function, function_prop)
122+
123+
assert isinstance(prop, PdocStr)
124+
125+
126+
def test_function_property(function_prop, function):
127+
prop = getattr(function, function_prop)
128+
129+
assert not callable(prop)
130+
131+
132+
def test_function_code(function):
133+
doc = function.docstring
134+
135+
assert doc, "testing function should have a docstring"
136+
assert doc not in function.code

0 commit comments

Comments
 (0)