Skip to content
Merged
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `ewokstasks` directive displays task tags.
- `ewokstasks` directive: `include-tags` and `exclude-tags` options.
- Filter tasks on tags in HTML pages.

## [2.2.1] - 2026-04-21

Expand Down
20 changes: 20 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ The [Examples page](./examples) contains real examples with Python code, RST syn
:ignore-import-error:
```

- `:include-tags:` and `:exclude-tags:` (comma-separated tags)

Generates documentation only for tasks with at least one of the included tags
(when given) and none of the excluded tags.

_Example_:

```
.. ewokstasks:: ewoksxrpd.tasks.*
:include-tags: integration, xrpd
:exclude-tags: deprecated
```

#### Tag filter

The tags of a task are shown in its documentation. In HTML pages with tagged tasks,
readers can filter the tasks by clicking on tags: a first click only shows tasks with
that tag, a second click hides tasks with that tag and a third click resets it.
The filter is kept in the `include_tags` and `exclude_tags` URL query parameters.


```{toctree}
:hidden:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ package-dir = { "" = "src" }
where = ["src"]

[tool.setuptools.package-data]
ewokssphinx = ["*.css"]
ewokssphinx = ["static/*"]

[tool.coverage.run]
omit = ['*/tests/*']
Expand Down
18 changes: 11 additions & 7 deletions src/ewokssphinx/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from pathlib import Path

import sphinx
from sphinx.application import Sphinx
from sphinx.util.osutil import copyfile
from sphinx.config import Config

from .ewoks_task_directive import EwoksTaskDirective

_STATIC_DIR = Path(__file__).parent / "static"


def setup(app: Sphinx):
app.add_directive("ewokstasks", EwoksTaskDirective)
Expand All @@ -27,8 +30,12 @@ def setup(app: Sphinx):
description="Ignore Ewoks task discovery errors. ",
)

if sphinx.version_info >= (9, 1):
app.add_static_dir(_STATIC_DIR)
Comment thread
woutdenolf marked this conversation as resolved.
else:
app.connect("config-inited", _add_static_path)
app.add_css_file("ewokssphinx.css")
app.connect("build-finished", _copy_css)
app.add_js_file("ewokssphinx.js")

return {
"version": "1.0",
Expand All @@ -37,8 +44,5 @@ def setup(app: Sphinx):
}


def _copy_css(app, exception):
if app.builder.name != "html" or exception:
return
static_dir = Path(app.builder.outdir) / "_static"
copyfile(Path(__file__).parent / "ewokssphinx.css", static_dir / "ewokssphinx.css")
def _add_static_path(app: Sphinx, config: Config) -> None:
config.html_static_path.append(str(_STATIC_DIR))
14 changes: 14 additions & 0 deletions src/ewokssphinx/ewoks_task_directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .ewoks_task_utils import cached_tasks
from .ewoks_task_utils import discover_tasks
from .ewoks_task_utils import filter_tasks_by_tags
from .sphinx_task_utils import additional_model_nodes
from .sphinx_task_utils import task_nodes

Expand All @@ -20,6 +21,8 @@ class EwoksTaskDirective(SphinxDirective):
option_spec = {
"task-type": _task_type_option,
"ignore-import-error": directives.flag,
"include-tags": directives.unchanged,
"exclude-tags": directives.unchanged,
}

def run(self):
Expand All @@ -41,9 +44,20 @@ def run(self):
module_pattern,
task_type,
)
tasks = filter_tasks_by_tags(
tasks,
include_tags=_parse_tags(self.options.get("include-tags")),
exclude_tags=_parse_tags(self.options.get("exclude-tags")),
)

task_sections = task_nodes(self, tasks)
additional_model_section = additional_model_nodes(self, tasks)
if additional_model_section is not None:
return [*task_sections, additional_model_section]
return task_sections


def _parse_tags(argument: str | None) -> list[str]:
if not argument:
return []
return [tag.strip() for tag in argument.split(",") if tag.strip()]
20 changes: 20 additions & 0 deletions src/ewokssphinx/ewoks_task_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Annotated
from typing import Any
from typing import Iterator
from typing import Sequence
from typing import Type
from typing import get_args
from typing import get_origin
Expand Down Expand Up @@ -94,6 +95,25 @@ def cached_tasks(
return module_pattern_dict[task_type]


def filter_tasks_by_tags(
tasks: Sequence[dict[str, Any]],
include_tags: Sequence[str] | None = None,
exclude_tags: Sequence[str] | None = None,
) -> list[dict[str, Any]]:
"""Keep tasks with at least one of `include_tags` (when given) and none of `exclude_tags`."""
include = set(include_tags or ())
exclude = set(exclude_tags or ())
filtered = []
for task in tasks:
tags = set(task.get("tags", ()))
if include and not (tags & include):
continue
if tags & exclude:
continue
filtered.append(task)
return filtered


def _get_task_name(identifier: str, task_type: str) -> str:
if task_type == "ppfmethod":
# ppfmethods are all named `run` so use the module name as task name.
Expand Down
30 changes: 0 additions & 30 deletions src/ewokssphinx/ewokssphinx.css

This file was deleted.

14 changes: 12 additions & 2 deletions src/ewokssphinx/sphinx_task_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def _task_section(directive: SphinxDirective, task: dict[str, Any]) -> nodes.sec
section = nodes.section(
"",
ids=[task["task_identifier"]],
classes=["ewokssphinx-task"],
classes=["ewokssphinx-task", *map(_model_class, task["submodels"])],
)

# Title
Expand Down Expand Up @@ -233,7 +233,11 @@ def additional_model_nodes(
model_sections = []
for task in tasks:
for model_name, model_fields in task["submodels"].items():
section = nodes.section("", ids=[model_name])
section = nodes.section(
"",
ids=[model_name],
classes=["ewokssphinx-model", _model_class(model_name)],
)
section.append(nodes.title("", model_name.split(".")[-1]))

section.append(_field_list(directive, {"fields": model_fields}))
Expand All @@ -247,4 +251,10 @@ def additional_model_nodes(
nodes.title("", "Additional models"),
*model_sections,
ids=["Additional models"],
classes=["ewokssphinx-models"],
)


def _model_class(model_name: str) -> str:
"""CSS class linking a model section to the task sections that use the model."""
return "ewokssphinx-model-" + nodes.make_id(model_name)
55 changes: 55 additions & 0 deletions src/ewokssphinx/static/ewokssphinx.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.ewokssphinx-task .ewokssphinx-field-list {
padding-left: 0.5em;
}

.ewokssphinx-task .ewokssphinx-default {
background-color: transparent;
border: none;
}

.ewokssphinx-task .ewokssphinx-required {
color: var(--sd-color-danger, red)
}

.ewokssphinx-task .ewokssphinx-examples {
font-style: italic;
}

.ewokssphinx-task .ewokssphinx-examples pre {
padding: 0.1rem;
border: none;
}

.ewokssphinx-task .ewokssphinx-examples li>div {
margin: 0;
}

.ewokssphinx-task .ewokssphinx-tag,
.ewokssphinx-tag-filter .ewokssphinx-tag {
cursor: pointer;
border-radius: 0.75em;
padding: 0.1em 0.6em;
}

.ewokssphinx-tag.ewokssphinx-tag-include {
background-color: var(--sd-color-success, green);
color: var(--sd-color-success-text, white);
}

.ewokssphinx-tag.ewokssphinx-tag-exclude {
background-color: var(--sd-color-danger, red);
color: var(--sd-color-danger-text, white);
text-decoration: line-through;
}

.ewokssphinx-tag-filter {
margin-bottom: 1em;
display: flex;
flex-wrap: wrap;
gap: 0.4em;
align-items: center;
}

.ewokssphinx-tag-filter-count {
font-style: italic;
}
Loading
Loading