Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit abc9667

Browse files
authored
fix: format enum data into table format (#376)
* 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
1 parent b8fa93e commit abc9667

2 files changed

Lines changed: 150 additions & 16 deletions

File tree

docfx_yaml/extension.py

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,83 @@ def indent_code_left(lines, tab_space):
396396
return "\n".join(parts)
397397

398398

399-
def _parse_docstring_summary(summary):
399+
def _parse_enum_content(parts: Sequence[str]) -> Sequence[Mapping[str, str]]:
400+
"""Parses the given content for enums.
401+
402+
Args:
403+
parts: The content to parse, given in the form of a sequence of str,
404+
which have been split by newlines and are left-indented.
405+
406+
Returns:
407+
Sequence of mapping of enum entries for name and description.
408+
409+
Raises:
410+
ValueError: If the `Values` enum docstring is malformed.
411+
"""
412+
enum_content: MutableSequence[Mapping[str, str]] = []
413+
enum_name = ""
414+
enum_description = []
415+
for part in parts:
416+
if (
417+
(current_tab_space := len(part) - len(part.lstrip(" "))) > 0
418+
):
419+
enum_description.append(indent_code_left(part, current_tab_space))
420+
continue
421+
422+
# Add the new enum and start collecting new entry.
423+
if enum_name and enum_description:
424+
enum_content.append({
425+
"id": enum_name,
426+
"description": " ".join(enum_description),
427+
})
428+
429+
enum_description = []
430+
# Only collect the name, not the value.
431+
enum_name = part.split(" ")[0]
432+
433+
if not enum_name and not enum_description:
434+
raise ValueError(
435+
"The enum docstring is not formatted well. Check the"
436+
" docstring:\n\n{}".format("\n".join(parts))
437+
)
438+
439+
# Add the last entry.
440+
if not enum_name or not enum_description:
441+
raise ValueError(
442+
"The enum docstring is not formatted well. Check the"
443+
" docstring:\n\n{}".format("\n".join(parts))
444+
)
445+
446+
enum_content.append({
447+
"id": enum_name,
448+
"description": " ".join(enum_description),
449+
})
450+
451+
return enum_content
452+
453+
454+
def _parse_docstring_summary(
455+
summary: str,
456+
) -> tuple[str, Mapping[str, str], Mapping[str, str]]:
457+
"""
458+
Parses the docstring tokens found in the summary.
459+
460+
Looks for tokens such as codeblocks, attributes, notices and enums.
461+
462+
Args:
463+
summary: The content to parse docstring for.
464+
465+
Returns:
466+
A tuple of the following:
467+
* str: The content with parsed docstrings.
468+
* Mapping[str, str]: Attribute entries if found.
469+
* Mapping[str, str]: Enum entries if found.
470+
"""
400471
summary_parts = []
401472
attributes = []
402473
attribute_type_token = ":type:"
403474
enum_type_token = "Values:"
475+
enums = []
404476
keyword = name = description = var_type = ""
405477

406478
notice_open_tag = '<aside class="{notice_tag}">\n<b>{notice_name}:</b>'
@@ -500,14 +572,8 @@ def _parse_docstring_summary(summary):
500572
if tab_space == 0:
501573
raise ValueError("Content in the block should be indented."\
502574
f"Please check the docstring: \n{summary}")
503-
parts = "\n".join(
504-
[indent_code_left(part, tab_space) for part in parts]
505-
)
506-
summary_parts.append(
507-
"Enum values:\n\n```\n"
508-
f"{parts}"
509-
"\n```\n"
510-
)
575+
parts = [indent_code_left(part, tab_space) for part in parts]
576+
enums = _parse_enum_content(parts)
511577
continue
512578

513579
try:
@@ -573,7 +639,7 @@ def _parse_docstring_summary(summary):
573639
summary_parts.append(notice_close_tag)
574640

575641
# Requires 2 newline chars to properly show on cloud site.
576-
return "\n".join(summary_parts), attributes
642+
return "\n".join(summary_parts), attributes, enums
577643

578644

579645
# Given documentation docstring, parse them into summary_info.
@@ -1014,7 +1080,9 @@ def _update_friendly_package_name(path):
10141080
summary = reformat_summary(summary)
10151081
top_summary = _extract_docstring_info(summary_info, summary, name)
10161082
try:
1017-
datam['summary'], datam['attributes'] = _parse_docstring_summary(top_summary)
1083+
datam['summary'], datam['attributes'], datam['enum'] = (
1084+
_parse_docstring_summary(top_summary)
1085+
)
10181086
except ValueError:
10191087
debug_line = []
10201088
if path:

tests/test_unit.py

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ def test_resolves_references_in_summary(
201201
self.assertEqual(resolved_content, expected_content.split("\n"))
202202
self.assertCountEqual(xrefs_to_check, expected_xrefs)
203203

204+
204205
test_entries = [
205206
[
206207
"""Required.
@@ -750,9 +751,12 @@ def test_parses_docstring_summary(
750751
summary,
751752
expected_summary,
752753
):
753-
parsed_summary, attributes = extension._parse_docstring_summary(summary)
754+
parsed_summary, attributes, enums = (
755+
extension._parse_docstring_summary(summary)
756+
)
754757
self.assertEqual(parsed_summary, expected_summary)
755758
self.assertEqual(attributes, [])
759+
self.assertEqual(enums, [])
756760

757761

758762
test_entries = [
@@ -775,6 +779,15 @@ def test_parses_docstring_summary(
775779
"""
776780
.. warning::
777781
this is not a properly formatted warning.
782+
"""
783+
),
784+
ValueError,
785+
],
786+
[
787+
(\
788+
"""
789+
Values:
790+
BAD_FORMATTING (-1): this is not properly formatted enum.
778791
"""
779792
),
780793
ValueError,
@@ -808,6 +821,7 @@ def test_raises_error_parsing_malformed_docstring(
808821
"description": "simple description",
809822
"var_type": "str",
810823
}],
824+
[],
811825
],
812826
[
813827
# Tests for multiple attributes.
@@ -839,6 +853,7 @@ def test_raises_error_parsing_malformed_docstring(
839853
"description": "Table insert request.",
840854
"var_type": "google.cloud.bigquery_logging_v1.types.TableInsertRequest",
841855
}],
856+
[],
842857
],
843858
[
844859
# Tests only attributes in valid format are parsed.
@@ -865,20 +880,71 @@ def test_raises_error_parsing_malformed_docstring(
865880
"description": "proper description.",
866881
"var_type": "str",
867882
}],
883+
[],
884+
],
885+
[
886+
# Tests enums are parsed.
887+
(\
888+
"""
889+
Values:
890+
EMPLOYMENT_TYPE_UNSPECIFIED (0):
891+
The default value if the employment type isn't specified.
892+
FULL_TIME (1):
893+
The job requires working a number of hours that constitute full time
894+
employment, typically 40 or more hours per week.
895+
PART_TIME (2):
896+
The job entails working fewer hours than a full time job,
897+
typically less than 40 hours a week.
898+
"""
899+
),
900+
[],
901+
[
902+
{
903+
"id": "EMPLOYMENT_TYPE_UNSPECIFIED",
904+
"description": (
905+
"The default value if the employment type isn't"
906+
" specified."
907+
),
908+
},
909+
{
910+
"id": "FULL_TIME",
911+
"description": (
912+
"The job requires working a number of hours that"
913+
" constitute full time employment, typically 40 or"
914+
" more hours per week."
915+
),
916+
},
917+
{
918+
"id": "PART_TIME",
919+
"description": (
920+
"The job entails working fewer hours than a full"
921+
" time job, typically less than 40 hours a week."
922+
),
923+
},
924+
925+
],
868926
],
869927
]
870928
@parameterized.expand(test_entries)
871-
def test_parses_docstring_summary_for_attributes(
929+
def test_parses_docstring_summary_for_attributes_and_enums(
872930
self,
873931
summary,
874932
expected_attributes,
933+
expected_enums,
875934
):
876-
_, attributes = extension._parse_docstring_summary(summary)
935+
_, attributes, enums = extension._parse_docstring_summary(summary)
936+
877937
self.assertCountEqual(attributes, expected_attributes)
878-
for attributes, expected_attributes in zip(
938+
self.assertCountEqual(enums, expected_enums)
939+
940+
for attribute, expected_attribute in zip(
879941
attributes, expected_attributes
880942
):
881-
self.assertDictEqual(attributes, expected_attributes)
943+
self.assertDictEqual(attribute, expected_attribute)
944+
for enum, expected_enum in zip(
945+
enums, expected_enums
946+
):
947+
self.assertDictEqual(enum, expected_enum)
882948

883949

884950
def test_merges_markdown_and_package_toc(self):

0 commit comments

Comments
 (0)