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
14 changes: 12 additions & 2 deletions src/specify_cli/workflows/overlays/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import typer
import yaml
from rich.markup import escape as _escape_markup

from ..._console import console, err_console
from ...extensions import normalize_priority
Expand Down Expand Up @@ -412,14 +413,23 @@ def workflow_resolve(project_root: Path, workflow_id: str) -> dict[str, Any] | N
priority = (
"n/a" if layer.tier == "base" else str(normalize_priority(layer.priority))
)
# ``\[`` keeps the literal bracket: unescaped, Rich parses ``[base]`` /
# ``[project-overlay]`` as a style tag and swallows the tier label whole.
console.print(
f" \u2022 [{layer.tier}] {layer.source} "
f" \u2022 \\[{_escape_markup(layer.tier)}] "
f"{_escape_markup(layer.source)} "
f"(priority={priority})"
)

console.print("Step attribution:")
for composed in attribution:
console.print(f" \u2022 {composed.step_id}: {composed.source}")
# Step IDs come from base-workflow / overlay YAML, which only bans ``:``
# \u2014 brackets pass validation, so they reach Rich as markup. A balanced
# ``[stuff]`` is swallowed; an unbalanced ``[/red]`` raises MarkupError.
console.print(
f" \u2022 {_escape_markup(composed.step_id)}: "
f"{_escape_markup(composed.source)}"
)

return {
"workflow_id": workflow_id,
Expand Down
80 changes: 80 additions & 0 deletions tests/workflows/test_overlay_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,86 @@ def test_workflow_resolve(self, project_dir, monkeypatch):
assert payload["layers"][-1]["tier"] == "base"
assert payload["layers"][-1]["priority"] is None

def test_workflow_resolve_prints_tier_labels(self, project_dir, monkeypatch):
"""Layer tiers render literally; an unescaped ``[base]`` is eaten as markup."""
monkeypatch.setattr("specify_cli._require_specify_project", lambda: project_dir)
_write_workflow(
project_dir,
"wf",
{
"schema_version": "1.0",
"workflow": {"id": "wf", "name": "WF", "version": "1.0.0"},
"steps": [{"id": "a", "type": "command", "command": "echo"}],
},
)
_write_overlay(
project_dir,
"wf",
"ov1",
{
"id": "ov1",
"extends": "wf",
"priority": 10,
"edits": [
{
"operation": "insert_after",
"anchor": "a",
"step": {"id": "new", "type": "command", "command": "echo"},
}
],
},
)

result = runner.invoke(app, ["workflow", "resolve", "wf"])
assert result.exit_code == 0, result.output
assert "[base]" in result.output
assert "[project-overlay]" in result.output

@pytest.mark.parametrize(
"step_id",
[
# Balanced tag: silently swallowed, so the step vanishes from output.
"new[stuff]",
# Unbalanced closer: raises MarkupError -> traceback and exit 1.
"new[/red]",
],
)
def test_workflow_resolve_escapes_rich_markup_in_step_id(
self, project_dir, monkeypatch, step_id
):
"""Step IDs are unvalidated for brackets, so they must be escaped."""
monkeypatch.setattr("specify_cli._require_specify_project", lambda: project_dir)
_write_workflow(
project_dir,
"wf",
{
"schema_version": "1.0",
"workflow": {"id": "wf", "name": "WF", "version": "1.0.0"},
"steps": [{"id": "a", "type": "command", "command": "echo"}],
},
)
_write_overlay(
project_dir,
"wf",
"ov1",
{
"id": "ov1",
"extends": "wf",
"priority": 10,
"edits": [
{
"operation": "insert_after",
"anchor": "a",
"step": {"id": step_id, "type": "command", "command": "echo"},
}
],
},
)

result = runner.invoke(app, ["workflow", "resolve", "wf"])
assert result.exit_code == 0, result.output
assert step_id in result.output

def test_workflow_resolve_equal_priority_layers_sort_by_source(self, project_dir, monkeypatch):
"""Equal-priority overlays are listed alphabetically by source."""
monkeypatch.setattr("specify_cli._require_specify_project", lambda: project_dir)
Expand Down