diff --git a/docstring_parser/epydoc.py b/docstring_parser/epydoc.py index 1ebc4a7..2e42bf1 100644 --- a/docstring_parser/epydoc.py +++ b/docstring_parser/epydoc.py @@ -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( @@ -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}".') @@ -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) @@ -101,6 +108,9 @@ def parse(text: str) -> Docstring: # way here: if key in [ "param", + "ivar", + "cvar", + "var", "keyword", "type", "return", @@ -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",) @@ -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") diff --git a/docstring_parser/tests/test_epydoc.py b/docstring_parser/tests/test_epydoc.py index e23518a..0d8f6e2 100644 --- a/docstring_parser/tests/test_epydoc.py +++ b/docstring_parser/tests/test_epydoc.py @@ -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(