Skip to content

Commit

Permalink
fix: format enum data into table format (#376)
Browse files Browse the repository at this point in the history
* fix: format enum data into table format

* test: update unit test

* fix: account for potentially empty insertion

* test: apply refactored unit test and add enum testing

* fix: update to use `enum` instead of `enums`

* fix: address review comments
  • Loading branch information
dandhlee authored May 6, 2024
1 parent b8fa93e commit abc9667
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 16 deletions.
90 changes: 79 additions & 11 deletions docfx_yaml/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,11 +396,83 @@ def indent_code_left(lines, tab_space):
return "\n".join(parts)


def _parse_docstring_summary(summary):
def _parse_enum_content(parts: Sequence[str]) -> Sequence[Mapping[str, str]]:
"""Parses the given content for enums.
Args:
parts: The content to parse, given in the form of a sequence of str,
which have been split by newlines and are left-indented.
Returns:
Sequence of mapping of enum entries for name and description.
Raises:
ValueError: If the `Values` enum docstring is malformed.
"""
enum_content: MutableSequence[Mapping[str, str]] = []
enum_name = ""
enum_description = []
for part in parts:
if (
(current_tab_space := len(part) - len(part.lstrip(" "))) > 0
):
enum_description.append(indent_code_left(part, current_tab_space))
continue

# Add the new enum and start collecting new entry.
if enum_name and enum_description:
enum_content.append({
"id": enum_name,
"description": " ".join(enum_description),
})

enum_description = []
# Only collect the name, not the value.
enum_name = part.split(" ")[0]

if not enum_name and not enum_description:
raise ValueError(
"The enum docstring is not formatted well. Check the"
" docstring:\n\n{}".format("\n".join(parts))
)

# Add the last entry.
if not enum_name or not enum_description:
raise ValueError(
"The enum docstring is not formatted well. Check the"
" docstring:\n\n{}".format("\n".join(parts))
)

enum_content.append({
"id": enum_name,
"description": " ".join(enum_description),
})

return enum_content


def _parse_docstring_summary(
summary: str,
) -> tuple[str, Mapping[str, str], Mapping[str, str]]:
"""
Parses the docstring tokens found in the summary.
Looks for tokens such as codeblocks, attributes, notices and enums.
Args:
summary: The content to parse docstring for.
Returns:
A tuple of the following:
* str: The content with parsed docstrings.
* Mapping[str, str]: Attribute entries if found.
* Mapping[str, str]: Enum entries if found.
"""
summary_parts = []
attributes = []
attribute_type_token = ":type:"
enum_type_token = "Values:"
enums = []
keyword = name = description = var_type = ""

notice_open_tag = '<aside class="{notice_tag}">\n<b>{notice_name}:</b>'
Expand Down Expand Up @@ -500,14 +572,8 @@ def _parse_docstring_summary(summary):
if tab_space == 0:
raise ValueError("Content in the block should be indented."\
f"Please check the docstring: \n{summary}")
parts = "\n".join(
[indent_code_left(part, tab_space) for part in parts]
)
summary_parts.append(
"Enum values:\n\n```\n"
f"{parts}"
"\n```\n"
)
parts = [indent_code_left(part, tab_space) for part in parts]
enums = _parse_enum_content(parts)
continue

try:
Expand Down Expand Up @@ -573,7 +639,7 @@ def _parse_docstring_summary(summary):
summary_parts.append(notice_close_tag)

# Requires 2 newline chars to properly show on cloud site.
return "\n".join(summary_parts), attributes
return "\n".join(summary_parts), attributes, enums


# Given documentation docstring, parse them into summary_info.
Expand Down Expand Up @@ -1014,7 +1080,9 @@ def _update_friendly_package_name(path):
summary = reformat_summary(summary)
top_summary = _extract_docstring_info(summary_info, summary, name)
try:
datam['summary'], datam['attributes'] = _parse_docstring_summary(top_summary)
datam['summary'], datam['attributes'], datam['enum'] = (
_parse_docstring_summary(top_summary)
)
except ValueError:
debug_line = []
if path:
Expand Down
76 changes: 71 additions & 5 deletions tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def test_resolves_references_in_summary(
self.assertEqual(resolved_content, expected_content.split("\n"))
self.assertCountEqual(xrefs_to_check, expected_xrefs)


test_entries = [
[
"""Required.
Expand Down Expand Up @@ -750,9 +751,12 @@ def test_parses_docstring_summary(
summary,
expected_summary,
):
parsed_summary, attributes = extension._parse_docstring_summary(summary)
parsed_summary, attributes, enums = (
extension._parse_docstring_summary(summary)
)
self.assertEqual(parsed_summary, expected_summary)
self.assertEqual(attributes, [])
self.assertEqual(enums, [])


test_entries = [
Expand All @@ -775,6 +779,15 @@ def test_parses_docstring_summary(
"""
.. warning::
this is not a properly formatted warning.
"""
),
ValueError,
],
[
(\
"""
Values:
BAD_FORMATTING (-1): this is not properly formatted enum.
"""
),
ValueError,
Expand Down Expand Up @@ -808,6 +821,7 @@ def test_raises_error_parsing_malformed_docstring(
"description": "simple description",
"var_type": "str",
}],
[],
],
[
# Tests for multiple attributes.
Expand Down Expand Up @@ -839,6 +853,7 @@ def test_raises_error_parsing_malformed_docstring(
"description": "Table insert request.",
"var_type": "google.cloud.bigquery_logging_v1.types.TableInsertRequest",
}],
[],
],
[
# Tests only attributes in valid format are parsed.
Expand All @@ -865,20 +880,71 @@ def test_raises_error_parsing_malformed_docstring(
"description": "proper description.",
"var_type": "str",
}],
[],
],
[
# Tests enums are parsed.
(\
"""
Values:
EMPLOYMENT_TYPE_UNSPECIFIED (0):
The default value if the employment type isn't specified.
FULL_TIME (1):
The job requires working a number of hours that constitute full time
employment, typically 40 or more hours per week.
PART_TIME (2):
The job entails working fewer hours than a full time job,
typically less than 40 hours a week.
"""
),
[],
[
{
"id": "EMPLOYMENT_TYPE_UNSPECIFIED",
"description": (
"The default value if the employment type isn't"
" specified."
),
},
{
"id": "FULL_TIME",
"description": (
"The job requires working a number of hours that"
" constitute full time employment, typically 40 or"
" more hours per week."
),
},
{
"id": "PART_TIME",
"description": (
"The job entails working fewer hours than a full"
" time job, typically less than 40 hours a week."
),
},

],
],
]
@parameterized.expand(test_entries)
def test_parses_docstring_summary_for_attributes(
def test_parses_docstring_summary_for_attributes_and_enums(
self,
summary,
expected_attributes,
expected_enums,
):
_, attributes = extension._parse_docstring_summary(summary)
_, attributes, enums = extension._parse_docstring_summary(summary)

self.assertCountEqual(attributes, expected_attributes)
for attributes, expected_attributes in zip(
self.assertCountEqual(enums, expected_enums)

for attribute, expected_attribute in zip(
attributes, expected_attributes
):
self.assertDictEqual(attributes, expected_attributes)
self.assertDictEqual(attribute, expected_attribute)
for enum, expected_enum in zip(
enums, expected_enums
):
self.assertDictEqual(enum, expected_enum)


def test_merges_markdown_and_package_toc(self):
Expand Down

0 comments on commit abc9667

Please sign in to comment.