Skip to content

Extend Ansible markup #72

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[flake8]
extend-ignore = E203
count = true
max-complexity = 10
max-complexity = 11
# black's max-line-length is 89, but it doesn't touch long string literals.
max-line-length = 100
statistics = true
3 changes: 3 additions & 0 deletions changelogs/fragments/72-extend-ansible-markup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
major_changes:
- "Extend plugin reference ``P(...)`` to allow referencing a role's entrypoint (https://github.com/ansible-community/antsibull-docs-parser/pull/72)."
- "Extend environment variable ``E(...)`` to allow specifying a value (https://github.com/ansible-community/antsibull-docs-parser/pull/72)."
4 changes: 4 additions & 0 deletions src/antsibull_docs_parser/dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class ModulePart(NamedTuple):

class PluginPart(NamedTuple):
plugin: PluginIdentifier
entrypoint: str | None = (
None # can be present if plugin.type == "role"; default value for backwards compatibility
)
source: str | None = None
type: t.Literal[PartType.PLUGIN] = PartType.PLUGIN

Expand Down Expand Up @@ -114,6 +117,7 @@ class OptionValuePart(NamedTuple):

class EnvVariablePart(NamedTuple):
name: str
value: str | None = None # default value for backwards compatibility
source: str | None = None
type: t.Literal[PartType.ENV_VARIABLE] = PartType.ENV_VARIABLE

Expand Down
36 changes: 29 additions & 7 deletions src/antsibull_docs_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
_ARRAY_STUB_RE = re.compile(r"\[([^\]]*)\]")
_FQCN_TYPE_PREFIX_RE = re.compile(r"^([^.]+\.[^.]+\.[^#]+)#([^:]+):(.*)$")
_FQCN = re.compile(r"^[A-Za-z0-9_]+\.[A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)+$")
_ENTRYPOINT = re.compile(r"^[A-Za-z0-9_]+$")
_PLUGIN_TYPE = re.compile(r"^[a-z_]+$")
_WHITESPACE = re.compile(r"([\s]+)")
_DANGEROUS_WS = re.compile(r"[\t\n\r]")
Expand All @@ -33,6 +34,10 @@ def _is_fqcn(text: str) -> bool:
return _FQCN.match(text) is not None


def _is_entrypoint(text: str) -> bool:
return _ENTRYPOINT.match(text) is not None


def _is_plugin_type(text: str) -> bool:
# We do not want to hard-code a list of valid plugin types that might be
# inaccurate, so we simply check whether this is a valid kind of Python
Expand Down Expand Up @@ -376,6 +381,8 @@ def _parse_option_like(
if sep:
entrypoint = part1
text = part2
if not _is_entrypoint(entrypoint):
raise ValueError(f"Entrypoint {_repr(entrypoint)} is not valid")
if entrypoint is None:
raise ValueError("Role reference is missing entrypoint")
if ":" in text or "#" in text:
Expand Down Expand Up @@ -406,12 +413,23 @@ def parse(
if "#" not in name:
raise ValueError(f"Parameter {_repr(name)} is not of the form FQCN#type")
fqcn, ptype = name.split("#", 1)
entrypoint = None
part1, sep, part2 = ptype.partition(":")
if sep:
ptype = part1
entrypoint = part2
if not _is_entrypoint(entrypoint):
raise ValueError(f"Entrypoint {_repr(entrypoint)} is not valid")
if not _is_fqcn(fqcn):
raise ValueError(f"Plugin name {_repr(fqcn)} is not a FQCN")
if not _is_plugin_type(ptype):
raise ValueError(f"Plugin type {_repr(ptype)} is not valid")
if entrypoint is not None and ptype != "role":
raise ValueError("Only role references can have entrypoints")
return dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn=fqcn, type=ptype), source=source
plugin=dom.PluginIdentifier(fqcn=fqcn, type=ptype),
entrypoint=entrypoint,
source=source,
)


Expand All @@ -426,13 +444,17 @@ def parse(
source: str | None,
whitespace: Whitespace,
) -> dom.AnyPart:
name = _process_whitespace(
parameters[0],
whitespace=whitespace,
code_environment=True,
no_newlines=True,
)
name, sep, value_ = name.partition("=")
value = value_ if sep else None
return dom.EnvVariablePart(
name=_process_whitespace(
parameters[0],
whitespace=whitespace,
code_environment=True,
no_newlines=True,
),
name=name,
value=value,
source=source,
)

Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_dom.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ def process_return_value(self, part: dom.ReturnValuePart) -> None:
],
[
dom.TextPart(text="foo "),
dom.EnvVariablePart(name="a),b"),
dom.EnvVariablePart(name="a),b", value=None),
dom.TextPart(text=" "),
dom.PluginPart(plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="bam")),
dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="bam"), entrypoint=None
),
dom.TextPart(text=" baz "),
dom.OptionValuePart(value=" b,na)\\m, "),
dom.TextPart(text=" "),
Expand Down
56 changes: 51 additions & 5 deletions tests/unit/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,19 @@ def test__process_whitespace(
),
# semantic markup:
(
"foo E(a\\),b) P(foo.bar.baz#bam) baz V( b\\,\\na\\)\\\\m\\, ) O(foo) ",
"foo E(a\\),b) E(foo=bar=baz) P(foo.bar.baz#bam) baz V( b\\,\\na\\)\\\\m\\, ) O(foo) ",
Context(),
{},
[
[
dom.TextPart(text="foo "),
dom.EnvVariablePart(name="a),b"),
dom.EnvVariablePart(name="a),b", value=None),
dom.TextPart(text=" "),
dom.EnvVariablePart(name="foo", value="bar=baz"),
dom.TextPart(text=" "),
dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="bam")
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="bam"),
entrypoint=None,
),
dom.TextPart(text=" baz "),
dom.OptionValuePart(value=" b,na)\\m, "),
Expand All @@ -214,16 +217,21 @@ def test__process_whitespace(
],
),
(
"foo E(a\\),b) P(foo.bar.baz#bam) baz V( b\\,\\na\\)\\\\m\\, ) O(foo) ",
"foo E(a\\),b) E(foo=bar=baz) P(foo.bar.baz#bam) baz V( b\\,\\na\\)\\\\m\\, ) O(foo) ",
Context(),
dict(add_source=True),
[
[
dom.TextPart(text="foo ", source="foo "),
dom.EnvVariablePart(name="a),b", source="E(a\\),b)"),
dom.EnvVariablePart(name="a),b", value=None, source="E(a\\),b)"),
dom.TextPart(text=" ", source=" "),
dom.EnvVariablePart(
name="foo", value="bar=baz", source="E(foo=bar=baz)"
),
dom.TextPart(text=" ", source=" "),
dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="bam"),
entrypoint=None,
source="P(foo.bar.baz#bam)",
),
dom.TextPart(text=" baz ", source=" baz "),
Expand All @@ -243,6 +251,26 @@ def test__process_whitespace(
],
],
),
(
"P(foo.bar.baz#role) P(foo.bar.baz#role:entrypoint)",
Context(),
dict(add_source=True),
[
[
dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="role"),
entrypoint=None,
source="P(foo.bar.baz#role)",
),
dom.TextPart(text=" ", source=" "),
dom.PluginPart(
plugin=dom.PluginIdentifier(fqcn="foo.bar.baz", type="role"),
entrypoint="entrypoint",
source="P(foo.bar.baz#role:entrypoint)",
),
],
],
),
# semantic markup option name:
(
"O(foo)",
Expand Down Expand Up @@ -843,6 +871,18 @@ def test_parse(
dict(errors="exception", helpful_errors=False),
'While parsing P() at index 1: Plugin type "b m" is not valid',
),
(
"P(foo.bar.baz#module:e p)",
Context(),
dict(errors="exception", helpful_errors=False),
'While parsing P() at index 1: Entrypoint "e p" is not valid',
),
(
"P(foo.bar.baz#module:entrypoint)",
Context(),
dict(errors="exception", helpful_errors=False),
"While parsing P() at index 1: Only role references can have entrypoints",
),
# bad option name/return value (throw error):
(
"O(f o.b r.b z#bam:foobar)",
Expand All @@ -868,6 +908,12 @@ def test_parse(
dict(errors="exception", helpful_errors=False),
"While parsing O() at index 1: Role reference is missing entrypoint",
),
(
"O(foo.bar.baz#role:e p:bam)",
Context(),
dict(errors="exception", helpful_errors=False),
'While parsing O() at index 1: Entrypoint "e p" is not valid',
),
]


Expand Down