Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute parsing for epydoc parser #77

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions docstring_parser/epydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def parse(text: str) -> Docstring:
param_pattern = re.compile(
r"(param|keyword|type)(\s+[_A-z][_A-z0-9]*\??):"
)
attribute_pattern = re.compile(r"(ivar|cvar|var)(\s+[_A-z][_A-z0-9]*\??):")
raise_pattern = re.compile(r"(raise)(\s+[_A-z][_A-z0-9]*\??)?:")
return_pattern = re.compile(r"(return|rtype|yield|ytype):")
meta_pattern = re.compile(
Expand All @@ -70,11 +71,13 @@ def parse(text: str) -> Docstring:
continue

param_match = re.search(param_pattern, chunk)
attribute_match = re.search(attribute_pattern, chunk)
raise_match = re.search(raise_pattern, chunk)
return_match = re.search(return_pattern, chunk)
meta_match = re.search(meta_pattern, chunk)

match = param_match or raise_match or return_match or meta_match
match = param_match or attribute_match or raise_match or return_match \
or meta_match
if not match:
raise ParseError(f'Error parsing meta information near "{chunk}".')

Expand All @@ -83,6 +86,10 @@ def parse(text: str) -> Docstring:
base = "param"
key: str = match.group(1)
args = [match.group(2).strip()]
elif attribute_match:
base = "attribute"
key: str = match.group(1)
args = [match.group(2).strip()]
elif raise_match:
base = "raise"
key: str = match.group(1)
Expand All @@ -101,6 +108,9 @@ def parse(text: str) -> Docstring:
# way here:
if key in [
"param",
"ivar",
"cvar",
"var",
"keyword",
"type",
"return",
Expand All @@ -121,7 +131,7 @@ def parse(text: str) -> Docstring:
# Combine type_name, arg_name, and description information
params: T.Dict[str, T.Dict[str, T.Any]] = {}
for (base, key, args, desc) in stream:
if base not in ["param", "return"]:
if base not in {"param", "attribute", "return"}:
continue # nothing to do

(arg_name,) = args or ("return",)
Expand All @@ -138,7 +148,7 @@ def parse(text: str) -> Docstring:

is_done: T.Dict[str, bool] = {}
for (base, key, args, desc) in stream:
if base == "param" and not is_done.get(args[0], False):
if base in {"param", "attribute"} and not is_done.get(args[0], False):
(arg_name,) = args
info = params[arg_name]
type_name = info.get("type_name")
Expand Down
59 changes: 59 additions & 0 deletions docstring_parser/tests/test_epydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,65 @@ def test_params() -> None:
assert docstring.params[4].default == "'bye'"


def test_attributes() -> None:
"""Test parsing attributes."""
docstring = parse("Short description")
assert len(docstring.params) == 0

docstring = parse(
"""
Short description

@ivar name: description 1
@ivar priority: description 2
@type priority: int
@cvar sender: description 3
@type sender: str?
@var message: description 4, defaults to 'hello'
@type message: str?
@var multiline: long description 5,
defaults to 'bye'
@type multiline: str?
"""
)
assert len(docstring.params) == 5
assert docstring.params[0].arg_name == "name"
assert docstring.params[0].args[0] == "ivar"
assert docstring.params[0].type_name is None
assert docstring.params[0].description == "description 1"
assert docstring.params[0].default is None
assert not docstring.params[0].is_optional
assert docstring.params[1].arg_name == "priority"
assert docstring.params[1].args[0] == "ivar"
assert docstring.params[1].type_name == "int"
assert docstring.params[1].description == "description 2"
assert not docstring.params[1].is_optional
assert docstring.params[1].default is None
assert docstring.params[2].arg_name == "sender"
assert docstring.params[2].args[0] == "cvar"
assert docstring.params[2].type_name == "str"
assert docstring.params[2].description == "description 3"
assert docstring.params[2].is_optional
assert docstring.params[2].default is None
assert docstring.params[3].arg_name == "message"
assert docstring.params[3].args[0] == "var"
assert docstring.params[3].type_name == "str"
assert (
docstring.params[3].description == "description 4, defaults to 'hello'"
)
assert docstring.params[3].is_optional
assert docstring.params[3].default == "'hello'"
assert docstring.params[4].arg_name == "multiline"
assert docstring.params[4].type_name == "str"
assert docstring.params[4].args[0] == "var"
assert (
docstring.params[4].description
== "long description 5,\ndefaults to 'bye'"
)
assert docstring.params[4].is_optional
assert docstring.params[4].default == "'bye'"


def test_returns() -> None:
"""Test parsing returns."""
docstring = parse(
Expand Down