Skip to content
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No tests for starlark changes

Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def assumed_system_requirements(
spec = Label("//bazel/rules/rules_score/trlc/config:score_requirements_model"),
lobster_config = Label("//bazel/rules/rules_score/lobster/config:assumed_system_requirement"),
ref_package = "",
package = "",
image_srcs = [],
**kwargs):
"""Define Assumed System Requirements following S-CORE process guidelines.
Expand Down Expand Up @@ -87,6 +88,7 @@ def assumed_system_requirements(
lobster_config = lobster_config,
spec = spec,
ref_package = ref_package,
package = package,
image_srcs = image_srcs,
**kwargs
)
Expand Down
2 changes: 2 additions & 0 deletions bazel/rules/rules_score/private/assumptions_of_use.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def assumptions_of_use(
srcs,
deps = [],
ref_package = None,
package = "",
lobster_config = Label("//bazel/rules/rules_score/lobster/config:aou_config"),
**kwargs):
"""Define Assumptions of Use following S-CORE process guidelines.
Expand Down Expand Up @@ -92,6 +93,7 @@ def assumptions_of_use(
req_kind = "aou",
lobster_config = lobster_config,
ref_package = ref_package or "",
package = package,
**kwargs
)
trlc_requirements_test(
Expand Down
2 changes: 2 additions & 0 deletions bazel/rules/rules_score/private/component_requirements.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def component_requirements(
spec = Label("//bazel/rules/rules_score/trlc/config:score_requirements_model"),
lobster_config = Label("//bazel/rules/rules_score/lobster/config:component_requirement"),
ref_package = "",
package = "",
image_srcs = [],
**kwargs):
"""Define component requirements following S-CORE process guidelines.
Expand Down Expand Up @@ -86,6 +87,7 @@ def component_requirements(
lobster_config = lobster_config,
spec = spec,
ref_package = ref_package,
package = package,
image_srcs = image_srcs,
**kwargs
)
Expand Down
2 changes: 2 additions & 0 deletions bazel/rules/rules_score/private/feature_requirements.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def feature_requirements(
spec = Label("//bazel/rules/rules_score/trlc/config:score_requirements_model"),
lobster_config = Label("//bazel/rules/rules_score/lobster/config:feature_requirement"),
ref_package = "",
package = "",
image_srcs = [],
**kwargs):
"""Define feature requirements following S-CORE process guidelines.
Expand Down Expand Up @@ -86,6 +87,7 @@ def feature_requirements(
lobster_config = lobster_config,
spec = spec,
ref_package = ref_package,
package = package,
image_srcs = image_srcs,
**kwargs
)
Expand Down
5 changes: 5 additions & 0 deletions bazel/rules/rules_score/private/requirements.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ def score_requirements_rule(
deps = [],
spec = [],
ref_package = "",
package = "",
**kwargs):
"""Macro wrapper around _score_requirements_rule with RST support.

Expand All @@ -215,6 +216,9 @@ def score_requirements_rule(
ref_package: TRLC package prefix used for derived_from cross-references
when converting RST sources (e.g. "AssumedSystemRequirements" for
feature requirements that derive from ASR).
package: TRLC package name to use for any generated .trlc file(s) when
converting RST sources. Defaults to the input file's stem (e.g.
"index.rst" -> "Index") if left empty.

Returns:
List of resolved labels corresponding to srcs (after any .rst-to-.trlc
Expand All @@ -233,6 +237,7 @@ def score_requirements_rule(
name = gen_name,
srcs = [src],
ref_package = ref_package,
package = package,
)
trlc_srcs.append(":" + gen_name)
resolved_srcs.append(":" + gen_name)
Expand Down
5 changes: 4 additions & 1 deletion bazel/rules/rules_score/private/rst_to_trlc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ def _rst_to_trlc_impl(ctx):
"""Convert each .rst source file to a .trlc file via the Python converter."""
outs = []
for src in ctx.files.srcs:
out = ctx.actions.declare_file(src.basename[:-4] + ".trlc", sibling = src)
# `src` may come from a different repository (E.g.: a cross-repo RST label)
# Nest under the target's own name so two rst_to_trlc targets in the
# same package converting same-named files don't clash on the output path.
out = ctx.actions.declare_file(ctx.label.name + "/" + src.basename[:-4] + ".trlc")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

srcs = ["a/x.rst", "b/x.rst"] in one target still collides on /x.trlc

outs.append(out)

args = ctx.actions.args()
Expand Down
24 changes: 22 additions & 2 deletions bazel/rules/rules_score/src/rst_to_trlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
_RE_MARKUP = re.compile(r"\*\*?(.*?)\*\*?")
_RE_DIRECTIVE = re.compile(r"^\.\.\s+([\w]+)::\s*(.*)")
_RE_FIELD = re.compile(r"^\s+:([\w]+):\s*(.*)") # noqa: E501
_RE_NEEDS_FILTER = re.compile(r"(\s*\[[^\[\]]*\])+\s*$")

_TRLC_HEADER = """\
/********************************************************************************
Expand Down Expand Up @@ -136,8 +137,19 @@ def _escape(text: str) -> str:


def _collect_refs(fields: dict[str, str]) -> list[str]:
"""Extract all cross-reference IDs from relationship fields."""
return [r.strip() for k in _REF_FIELDS if k in fields for r in fields[k].split(",") if r.strip()]
"""Extract all cross-reference IDs from relationship fields.

Strips one or more trailing sphinx-needs filter expressions (E.g.:
[version==1] or [version==1][status==valid]), which are not
valid in TRLC reference syntax.
"""
return [
_RE_NEEDS_FILTER.sub("", r.strip()).strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 150 splits before line 147 strips.

Split on , happens before filter strip. req_001[version==1, status==x] → ['req_001[version==1', 'status==x]']. sphinx-needs filters with commas are legal ([tags in ["a","b"]]). Strip filters then split, or split only on top-level commas.

for k in _REF_FIELDS
if k in fields
for r in fields[k].split(",")
if r.strip()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if r.strip() guards the pre-substitution value.

{"satisfies": "[version==1]"} → [''] → renders derived_from = [Pkg.@1]. Filter after the sub, not before

]


def parse_directives(content: str) -> list[dict[str, Any]]:
Expand All @@ -154,6 +166,14 @@ def parse_directives(content: str) -> list[dict[str, Any]]:
directive, title = m.group(1), m.group(2).strip()
i += 1

# RST directive arguments may wrap onto continuation lines (docutils
# allows this); consume any such lines here before the field list,
# otherwise they get mis-parsed as body text and the real :id: (and
# other fields) on the following lines are missed entirely.
while i < len(lines) and lines[i].strip() and not _RE_FIELD.match(lines[i]):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation loop has no indentation guard:
.. comp_req:: A
.. comp_req:: B
🆔 comp_req__b

while i < len(lines) and lines[i].strip() and lines[i][0].isspace() and not _RE_FIELD.match(lines[i]):

title += " " + lines[i].strip()
i += 1

fields, i = _collect_fields(lines, i)
raw_body, i = _collect_body(lines, i)
body = _RE_MARKUP.sub(r"\1", raw_body).strip()
Expand Down
50 changes: 50 additions & 0 deletions bazel/rules/rules_score/test/rst_to_trlc_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,37 @@ def test_strips_rst_bold_markup_from_body(self):
self.assertNotIn("**", result[0]["body"])
self.assertIn("robust", result[0]["body"])

def test_multi_line_title_is_joined_and_fields_still_parsed(self):
"""docutils allows a directive's argument to wrap onto continuation
lines with no blank-line separator; the real field list must still
be found on the lines that follow."""
rst = _rst(
".. comp_req:: Logging During the C++ Static Storage",
" construction and destruction.",
" :id: comp_req__test__001",
" :safety: QM",
"",
" Body.",
)
result = parse_directives(rst)
self.assertEqual(len(result), 1)
self.assertEqual(
result[0]["title"],
"Logging During the C++ Static Storage construction and destruction.",
)
self.assertEqual(result[0]["fields"]["id"], "comp_req__test__001")

def test_single_line_title_unaffected_by_continuation_handling(self):
rst = _rst(
".. feat_req:: Single Line Title",
" :id: feat_req__test__001",
" :safety: QM",
"",
" Body.",
)
result = parse_directives(rst)
self.assertEqual(result[0]["title"], "Single Line Title")


# ---------------------------------------------------------------------------
# render_trlc – TRLC output for each S-CORE type
Expand Down Expand Up @@ -545,6 +576,25 @@ def test_mitigates_is_not_a_ref_field(self):
def test_returns_empty_when_no_ref_fields(self):
self.assertEqual(_collect_refs({"safety": "QM", "reqtype": "Functional"}), [])

def test_strips_trailing_needs_filter(self):
"""sphinx-needs filter suffixes are not valid TRLC reference syntax."""
self.assertEqual(
_collect_refs({"satisfies": "req_001[version==1]"}),
["req_001"],
)

def test_strips_multiple_trailing_needs_filters(self):
self.assertEqual(
_collect_refs({"satisfies": "req_001[version==1][status==valid]"}),
["req_001"],
)

def test_strips_needs_filter_from_each_comma_separated_ref(self):
self.assertEqual(
_collect_refs({"satisfies": "req_001[version==1], req_002[status==valid]"}),
["req_001", "req_002"],
)


# ---------------------------------------------------------------------------
# _collect_body – blank-line handling consistency
Expand Down
Loading