From eb03be8d60768d1073f1e84aca41d1e7838eb83f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 17 Jun 2026 14:33:10 -0400 Subject: [PATCH] Generate Markdown directive reference from directives.yml Enrich directives.yml into the source of truth for directive docs: per-param type/context/default/description (via shared _parameter_sets), embeddable, requires, category, renders; add missing instance_*_link and visualization entries. Add scripts/markdown_directives_doc.py to render directives.md and cross-check the yml against markdown_parse.py (VALID_ARGUMENTS, EMBED_CAPABLE_DIRECTIVES, SHARED_ARGUMENTS) + requirements.yml. Guard via test_markdown_directives_doc.py and `make client-gen-markdown-directives`, mirroring config-rebuild. directives.md is generated, so prettier-ignore it. Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 3 + client/.prettierignore | 2 + client/src/components/Markdown/directives.md | 415 ++++++++++++++++ client/src/components/Markdown/directives.ts | 15 + client/src/components/Markdown/directives.yml | 443 ++++++++++++++++-- scripts/markdown_directives_doc.py | 320 +++++++++++++ test/unit/app/test_markdown_directives_doc.py | 30 ++ 7 files changed, 1177 insertions(+), 51 deletions(-) create mode 100644 client/src/components/Markdown/directives.md create mode 100644 scripts/markdown_directives_doc.py create mode 100644 test/unit/app/test_markdown_directives_doc.py diff --git a/Makefile b/Makefile index 218fd972a51..a486bde8271 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,9 @@ config-rebuild: ## Rebuild all sample YAML, RST files, and type stubs from confi config-lint: ## lint galaxy YAML configuration file $(CONFIG_MANAGE) lint galaxy +client-gen-markdown-directives: ## Regenerate the Galaxy Markdown directive reference from directives.yml + $(IN_VENV) python scripts/markdown_directives_doc.py + release-ensure-upstream: ## Ensure upstream branch for release commands setup ifeq (shell git remote -v | grep $(RELEASE_UPSTREAM), ) git remote add $(RELEASE_UPSTREAM) git@github.com:galaxyproject/galaxy.git diff --git a/client/.prettierignore b/client/.prettierignore index 70ab8556d27..251a71a7d39 100644 --- a/client/.prettierignore +++ b/client/.prettierignore @@ -13,3 +13,5 @@ src/libs *.json *.yml *.yaml +# Generated from directives.yml by scripts/markdown_directives_doc.py +src/components/Markdown/directives.md diff --git a/client/src/components/Markdown/directives.md b/client/src/components/Markdown/directives.md new file mode 100644 index 00000000000..39a7eb5899b --- /dev/null +++ b/client/src/components/Markdown/directives.md @@ -0,0 +1,415 @@ +# Galaxy Markdown Directive Reference + +Generated from `directives.yml` by `scripts/markdown_directives_doc.py` (`make client-gen-markdown-directives`). Do not edit by hand. + +## Syntax + +**Block** — works for every directive; required in workflow report templates: + +```` +```galaxy +directive_name(arg=value) +``` +```` + +One directive per fenced `galaxy` block. **Inline** (`${galaxy ...}`) works only for the [embeddable directives](#embeddable-directives). + +## Argument value types + +| Type | Meaning | +|---|---| +| `label` | Workflow input/output/step label; resolved to an ID per invocation. | +| `id` | Encoded (export) or numeric (internal) object ID. | +| `int` | Integer. | +| `boolean` | `true` or `false`. | +| `enum` | One of a fixed set of values. | +| `string` | Free display text. | +| `path` | File within a composite / extra-files dataset. | + +## Addressing contexts + +The same directive accepts different parameters depending on how the object is referenced: + +| Context | Use | +|---|---| +| `report` | Workflow report template — labels resolve per invocation. | +| `page` | Page / direct contexts — encoded or numeric IDs. | +| `notebook` | History-relative reference (notebooks). | +| `invocation` | Invocation reference — usually injected automatically. | + +## Universal argument + +`collapse=""` — wraps a block directive in a collapsible section. Valid on every directive. + +--- + +## Dataset directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `history_dataset_display` | | `history_dataset_id` | Interactive dataset card with view/import/download options. | +| `history_dataset_as_image` | ✅ | `history_dataset_id` | Dataset embedded as an image. | +| `history_dataset_index` | | `history_dataset_id` | File/folder listing of a composite dataset. | +| `history_dataset_embedded` | | `history_dataset_id` | Raw dataset content inline. | +| `history_dataset_as_table` | | `history_dataset_id` | Tabular dataset as a formatted table. | +| `history_dataset_type` | ✅ | `history_dataset_id` | Datatype string as text. | +| `history_dataset_link` | | `history_dataset_id` | Download link for a dataset. | +| `history_dataset_name` | ✅ | `history_dataset_id` | Dataset name as text. | +| `history_dataset_peek` | | `history_dataset_id` | Dataset "peek" preview. | +| `history_dataset_info` | | `history_dataset_id` | Dataset "info" metadata. | + +### `history_dataset_display` + +Display a dataset and relevant options for viewing, importing, downloading, +and visualization in the resulting document. To embed a dataset directly +into the document use the "history_dataset_embedded" / "Embedded Dataset" +directive. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `history_dataset_as_image` + +Embed a dataset in the resulting document as an image. This only works for simple image +types. This can be used to present graphs and other visual summaries of an analysis into +a Galaxy Markdown document summary. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `path` | `path` | | | File within a composite / extra-files dataset. | + +### `history_dataset_index` + +For Galaxy composite datasets (datasets that consist on multiple files), this option will +display the contents of the composite dataset as files and folders in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `path` | `path` | | | File within a composite / extra-files dataset. | + +### `history_dataset_embedded` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `history_dataset_as_table` + +Embed a dataset in the resulting document as a table. This only works for datasets with tabular +datatypes. This command works a lot like "history_dataset_embedded" but provides specialized options +for controlling the way tabular data is displayed in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `title` | `string` | | | Table title. | +| `footer` | `string` | | | Table footer. | +| `compact` | `boolean` | | `false` | Compact row height. | +| `show_column_headers` | `boolean` | | `true` | Show column headers. | +| `path` | `path` | | | File within a composite / extra-files dataset. | + +### `history_dataset_type` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `history_dataset_link` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `label` | `string` | | | Link text. | +| `path` | `path` | | | File within a composite / extra-files dataset. | + +### `history_dataset_name` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `history_dataset_peek` + +Display a dataset metadata's "peek" field in the resulting document - this is datatype dependent +metadata but usually this is a few lines from the start of a file. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `history_dataset_info` + +Display a dataset metadata's "info" field in the resulting document. This info field is +usually based on the output of the tool run and sometimes contains useful metadata about a +dataset. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_id` | `id` | `page` | | Encoded (export) or numeric (internal) dataset ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +--- + +## Collection directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `history_dataset_collection_display` | | `history_dataset_collection_id` | Collection browser for paired/list collections. | + +### `history_dataset_collection_display` + +Display a dataset collection and relevant options for viewing, importing, downloading +in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `output` | `label` | `report` | | Workflow output label, resolved per invocation (report templates). | +| `input` | `label` | `report` | | Workflow input label, resolved per invocation (report templates). | +| `hid` | `int` | `notebook` | | History item hid. | +| `history_dataset_collection_id` | `id` | `page` | | Encoded or numeric dataset collection ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +--- + +## Invocation directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `history_link` | | `history_id` | Link to import the referenced history. | +| `invocation_inputs` | | `invocation_id` | Summary of all workflow inputs. | +| `invocation_outputs` | | `invocation_id` | Summary of all workflow outputs. | +| `invocation_time` | ✅ | `invocation_id` | Invocation run timestamp. | + +### `history_link` + +Add a link to import the history the workflow invocation was executed in. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `history_id` | `id` | `page` | | Encoded or numeric history ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `invocation_inputs` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `invocation_outputs` + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `invocation_time` + +Display this workflow run's invocation time in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +--- + +## Workflow directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `workflow_display` | | `workflow_id` | Step-by-step text description of a workflow. | +| `workflow_image` | | `workflow_id` | SVG workflow diagram. | +| `workflow_license` | ✅ | `workflow_id` | Workflow license information. | + +### `workflow_display` + +Embed a text description of this workflow's steps in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `workflow_id` | `id` | `page` | | Stored workflow ID. | +| `workflow_checkpoint` | `int` | `page` | | Workflow version index. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `workflow_image` + +Embed a rough image this workflow in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `workflow_id` | `id` | `page` | | Stored workflow ID. | +| `workflow_checkpoint` | `int` | `page` | | Workflow version index. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `size` | enum (`sm`, `md`, `lg`) | | `lg` | Image width (sm=300px, md=550px, lg=100%). | + +### `workflow_license` + +Display this workflow's license in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `workflow_id` | `id` | `page` | | Stored workflow ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +--- + +## Job directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `job_metrics` | | `job_id` | Runtime metrics table for a job. | +| `job_parameters` | | `job_id` | Tool parameters table for a job. | +| `tool_stdout` | | `job_id` | Tool standard output for a job. | +| `tool_stderr` | | `job_id` | Tool standard error for a job. | + +### `job_metrics` + +Embed the job metrics for this job in the resulting document (if Galaxy is configured and you have +permission). + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `step` | `label` | `report` | | Workflow step label, resolved per invocation (report templates). | +| `job_id` | `id` | `page` | | Encoded or numeric job ID. | +| `implicit_collection_jobs_id` | `id` | `page` | | Mapped-collection job group ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `job_parameters` + +Embed the tool parameters for a job in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `step` | `label` | `report` | | Workflow step label, resolved per invocation (report templates). | +| `job_id` | `id` | `page` | | Encoded or numeric job ID. | +| `implicit_collection_jobs_id` | `id` | `page` | | Mapped-collection job group ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | +| `footer` | `string` | | | Table footer. | + +### `tool_stdout` + +Embed the tool standard output stream for a job in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `step` | `label` | `report` | | Workflow step label, resolved per invocation (report templates). | +| `job_id` | `id` | `page` | | Encoded or numeric job ID. | +| `implicit_collection_jobs_id` | `id` | `page` | | Mapped-collection job group ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +### `tool_stderr` + +Embed the tool standard error stream for a job in the resulting document. + +| Parameter | Type | Context | Default | Description | +|---|---|---|---|---| +| `step` | `label` | `report` | | Workflow step label, resolved per invocation (report templates). | +| `job_id` | `id` | `page` | | Encoded or numeric job ID. | +| `implicit_collection_jobs_id` | `id` | `page` | | Mapped-collection job group ID. | +| `invocation_id` | `id` | `invocation` | | Invocation ID; usually injected automatically during resolution. | + +--- + +## Visualization directive + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `visualization` | | `history_dataset_id` | Galaxy plugin-based visualization. | + +### `visualization` + +Embed a Galaxy visualization in the resulting document. Accepts arguments specific to the +selected visualization plugin; these arguments are not validated by the Galaxy Markdown parser. + +--- + +## Utility & instance directives + +| Directive | Embed | Requires | Renders | +|---|---|---|---| +| `generate_time` | ✅ | — | Current time at generation. | +| `generate_galaxy_version` | ✅ | — | Galaxy version string at generation. | +| `instance_access_link` | ✅ | — | Link to the Galaxy instance. | +| `instance_resources_link` | ✅ | — | Link to instance resources. | +| `instance_help_link` | ✅ | — | Link to instance help. | +| `instance_support_link` | ✅ | — | Link to instance support. | +| `instance_citation_link` | ✅ | — | Link to instance citation information. | +| `instance_terms_link` | ✅ | — | Link to instance terms. | +| `instance_organization_link` | ✅ | — | Link to the instance's organization. | + +### `generate_time` + +Report the current time of report generation. + +Warning: This is the time the report was generated and not the time of +an analysis. This option makes the most sense for PDF generation of reports +designed for external archiving or printing. + +### `generate_galaxy_version` + +Report the current Galaxy version at the time report generation. + +Warning: This is the Galaxy version at the time the report was generated and +not the time of an analysis. This option makes the most sense for PDF generation +of reports designed for external archiving or printing. + +--- + +## Embeddable directives + +Inline `${galaxy ...}` syntax is supported only for these directives; all others require block syntax. + +- `history_dataset_as_image` +- `history_dataset_name` +- `history_dataset_type` +- `workflow_license` +- `invocation_time` +- `generate_time` +- `generate_galaxy_version` +- `instance_access_link` +- `instance_resources_link` +- `instance_help_link` +- `instance_support_link` +- `instance_citation_link` +- `instance_terms_link` +- `instance_organization_link` diff --git a/client/src/components/Markdown/directives.ts b/client/src/components/Markdown/directives.ts index 22901a0285c..525bb475bf5 100644 --- a/client/src/components/Markdown/directives.ts +++ b/client/src/components/Markdown/directives.ts @@ -6,10 +6,25 @@ type DirectiveMetadataValueByMode = { [key: string]: string; }; +interface DirectiveParameter { + type: string; + context?: string; + description?: string; + default?: string | boolean; + values?: string[]; +} + interface DirectiveMetadata { side_panel_name: string | DirectiveMetadataValueByMode; side_panel_description?: string | DirectiveMetadataValueByMode; help?: string | DirectiveMetadataValueByMode; + category?: string; + renders?: string; + embeddable?: boolean; + requires?: string; + parameter_set?: string; + parameters?: { [key: string]: DirectiveParameter }; + dynamic_parameters?: boolean; } type DirectivesMetadata = { diff --git a/client/src/components/Markdown/directives.yml b/client/src/components/Markdown/directives.yml index 480942adca6..0e0a2ab1da4 100644 --- a/client/src/components/Markdown/directives.yml +++ b/client/src/components/Markdown/directives.yml @@ -1,5 +1,110 @@ --- +# Metadata describing every Galaxy Markdown directive. +# +# Consumed at runtime by directives.ts (editor side panel) and used to generate the +# human-readable reference directives.md via scripts/markdown_directives_doc.py. +# The parameter, embeddable and requirement metadata here is checked against the +# authoritative validator in lib/galaxy/managers/markdown_parse.py (and +# Utilities/requirements.yml) by test/unit/app/test_markdown_directives_doc.py, so +# keep them in sync. +# +# Keys starting with "_" are shared schema fragments, not directives. +# +# Per-directive fields: +# category grouping used in the generated reference +# renders one-line summary of the rendered output +# embeddable true when usable inline via ${galaxy ...} (EMBED_CAPABLE_DIRECTIVES) +# requires primary object the directive needs (mirrors requirements.yml) +# parameter_set name of a shared bundle under _parameter_sets to include +# parameters directive-specific arguments (name -> type/default/context/description) +# dynamic_parameters true when arbitrary arguments are accepted (not validated) +# The shared "collapse" argument is valid on every directive and is documented globally. + +_parameter_sets: + dataset_addressing: + output: + type: label + context: report + description: Workflow output label, resolved per invocation (report templates). + input: + type: label + context: report + description: Workflow input label, resolved per invocation (report templates). + hid: + type: int + context: notebook + description: History item hid. + history_dataset_id: + type: id + context: page + description: Encoded (export) or numeric (internal) dataset ID. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + collection_addressing: + output: + type: label + context: report + description: Workflow output label, resolved per invocation (report templates). + input: + type: label + context: report + description: Workflow input label, resolved per invocation (report templates). + hid: + type: int + context: notebook + description: History item hid. + history_dataset_collection_id: + type: id + context: page + description: Encoded or numeric dataset collection ID. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + job_addressing: + step: + type: label + context: report + description: Workflow step label, resolved per invocation (report templates). + job_id: + type: id + context: page + description: Encoded or numeric job ID. + implicit_collection_jobs_id: + type: id + context: page + description: Mapped-collection job group ID. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + workflow_addressing: + workflow_id: + type: id + context: page + description: Stored workflow ID. + workflow_checkpoint: + type: int + context: page + description: Workflow version index. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + invocation_addressing: + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + history_dataset_display: + category: dataset + renders: Interactive dataset card with view/import/download options. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Dataset help: | Display a dataset and relevant options for viewing, importing, downloading, @@ -8,12 +113,26 @@ history_dataset_display: directive. history_dataset_collection_display: + category: collection + renders: Collection browser for paired/list collections. + embeddable: false + requires: history_dataset_collection_id + parameter_set: collection_addressing side_panel_name: Collection help: | Display a dataset collection and relevant options for viewing, importing, downloading - in the resulting documenting. + in the resulting document. history_dataset_as_image: + category: dataset + renders: Dataset embedded as an image. + embeddable: true + requires: history_dataset_id + parameter_set: dataset_addressing + parameters: + path: + type: path + description: File within a composite / extra-files dataset. side_panel_name: Image help: | Embed a dataset in the resulting document as an image. This only works for simple image @@ -21,31 +140,106 @@ history_dataset_as_image: a Galaxy Markdown document summary. history_dataset_index: + category: dataset + renders: File/folder listing of a composite dataset. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing + parameters: + path: + type: path + description: File within a composite / extra-files dataset. side_panel_name: Dataset Index help: | For Galaxy composite datasets (datasets that consist on multiple files), this option will display the contents of the composite dataset as files and folders in the resulting document. + history_dataset_embedded: + category: dataset + renders: Raw dataset content inline. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Embedded Dataset + history_dataset_as_table: + category: dataset + renders: Tabular dataset as a formatted table. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing + parameters: + title: + type: string + description: Table title. + footer: + type: string + description: Table footer. + compact: + type: boolean + default: false + description: Compact row height. + show_column_headers: + type: boolean + default: true + description: Show column headers. + path: + type: path + description: File within a composite / extra-files dataset. side_panel_name: Embedded Dataset as table help: | Embed a dataset in the resulting document as a table. This only works for datasets with tabular datatypes. This command works a lot like "history_dataset_embedded" but provides specialized options for controlling the way tabular data is displayed in the resulting document. + history_dataset_type: + category: dataset + renders: Datatype string as text. + embeddable: true + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Dataset Type + history_dataset_link: + category: dataset + renders: Download link for a dataset. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing + parameters: + label: + type: string + description: Link text. + path: + type: path + description: File within a composite / extra-files dataset. side_panel_name: Link to Dataset + history_dataset_name: + category: dataset + renders: Dataset name as text. + embeddable: true + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Name of Dataset + history_dataset_peek: + category: dataset + renders: Dataset "peek" preview. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Peek into Dataset help: | Display a dataset metadata's "peek" field in the resulting document - this is datatype dependent metadata but usually this is a few lines from the start of a file. history_dataset_info: + category: dataset + renders: Dataset "info" metadata. + embeddable: false + requires: history_dataset_id + parameter_set: dataset_addressing side_panel_name: Dataset Details help: | Display a dataset metadata's "info" field in the resulting document. This info field is @@ -53,6 +247,19 @@ history_dataset_info: dataset. history_link: + category: invocation + renders: Link to import the referenced history. + embeddable: false + requires: history_id + parameters: + history_id: + type: id + context: page + description: Encoded or numeric history ID. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. side_panel_name: Link to Import help: report: | @@ -60,11 +267,31 @@ history_link: page: | Add a link to import the target history referenced by the directive. +invocation_inputs: + category: invocation + renders: Summary of all workflow inputs. + embeddable: false + requires: invocation_id + parameter_set: invocation_addressing + side_panel_name: Invocation Inputs + +invocation_outputs: + category: invocation + renders: Summary of all workflow outputs. + embeddable: false + requires: invocation_id + parameter_set: invocation_addressing + side_panel_name: Invocation Output + invocation_time: + category: invocation + renders: Invocation run timestamp. + embeddable: true + requires: invocation_id + parameter_set: invocation_addressing side_panel_name: report: Time Workflow page: Time a Workflow - side_panel_description: was invoked help: report: | @@ -72,29 +299,12 @@ invocation_time: page: | Display a workflow run's invocation time in the resulting document. -invocation_inputs: - side_panel_name: Invocation Inputs - -invocation_outputs: - side_panel_name: Invocation Output - -workflow_license: - side_panel_name: Workflow License - help: - report: | - Display this workflow's license in the resulting document. - page: | - Display a workflow's license in the resulting document. - -workflow_image: - side_panel_name: Workflow (as image) - help: - report: | - Embed a rough image this workflow in the resulting document. - page: | - Embed a rough image a workflow in the resulting document. - workflow_display: + category: workflow + renders: Step-by-step text description of a workflow. + embeddable: false + requires: workflow_id + parameter_set: workflow_addressing side_panel_name: report: Current Workflow page: Display a Workflow @@ -105,18 +315,111 @@ workflow_display: page: | Embed a text description of a workflow's steps in the resulting document. -generate_galaxy_version: - side_panel_name: Galaxy Version - side_panel_description: as text +workflow_image: + category: workflow + renders: SVG workflow diagram. + embeddable: false + requires: workflow_id + parameter_set: workflow_addressing + parameters: + size: + type: enum + values: [sm, md, lg] + default: lg + description: Image width (sm=300px, md=550px, lg=100%). + side_panel_name: Workflow (as image) + help: + report: | + Embed a rough image this workflow in the resulting document. + page: | + Embed a rough image a workflow in the resulting document. +workflow_license: + category: workflow + renders: Workflow license information. + embeddable: true + requires: workflow_id + parameters: + workflow_id: + type: id + context: page + description: Stored workflow ID. + invocation_id: + type: id + context: invocation + description: Invocation ID; usually injected automatically during resolution. + side_panel_name: Workflow License + help: + report: | + Display this workflow's license in the resulting document. + page: | + Display a workflow's license in the resulting document. + +job_metrics: + category: job + renders: Runtime metrics table for a job. + embeddable: false + requires: job_id + parameter_set: job_addressing + side_panel_name: Job Metrics + side_panel_description: as table help: | - Report the current Galaxy version at the time %MODE% generation. + Embed the job metrics for this job in the resulting document (if Galaxy is configured and you have + permission). - Warning: This is the Galaxy version at the time the %MODE% was generated and - not the time of an analysis. This option makes the most sense for PDF generation - of %MODE%s designed for external archiving or printing. +job_parameters: + category: job + renders: Tool parameters table for a job. + embeddable: false + requires: job_id + parameter_set: job_addressing + parameters: + footer: + type: string + description: Table footer. + side_panel_name: Job Parameters + side_panel_description: as table + help: | + Embed the tool parameters for a job in the resulting document. + +tool_stdout: + category: job + renders: Tool standard output for a job. + embeddable: false + requires: job_id + parameter_set: job_addressing + side_panel_name: Tool Output + side_panel_description: of job run + help: | + Embed the tool standard output stream for a job in the resulting document. + +tool_stderr: + category: job + renders: Tool standard error for a job. + embeddable: false + requires: job_id + parameter_set: job_addressing + side_panel_name: Tool Error + side_panel_description: of job run + help: | + Embed the tool standard error stream for a job in the resulting document. + +visualization: + category: visualization + renders: Galaxy plugin-based visualization. + embeddable: false + requires: history_dataset_id + dynamic_parameters: true + side_panel_name: Visualization + help: | + Embed a Galaxy visualization in the resulting document. Accepts arguments specific to the + selected visualization plugin; these arguments are not validated by the Galaxy Markdown parser. generate_time: + category: utility + renders: Current time at generation. + embeddable: true + requires: none side_panel_name: Current Time side_panel_description: as text help: | @@ -126,27 +429,65 @@ generate_time: an analysis. This option makes the most sense for PDF generation of %MODE%s designed for external archiving or printing. -job_metrics: - side_panel_name: Job Metrics - side_panel_description: as table +generate_galaxy_version: + category: utility + renders: Galaxy version string at generation. + embeddable: true + requires: none + side_panel_name: Galaxy Version + side_panel_description: as text help: | - Embed the job metrics for this job in the resulting document (if Galaxy is configured and you have - permission). + Report the current Galaxy version at the time %MODE% generation. -job_parameters: - side_panel_name: Job Parameters - side_panel_description: as table - help: | - Embed the tool parameters for a job in the resulting document. + Warning: This is the Galaxy version at the time the %MODE% was generated and + not the time of an analysis. This option makes the most sense for PDF generation + of %MODE%s designed for external archiving or printing. -tool_stdout: - side_panel_name: Tool Output - side_panel_description: of job run - help: | - Embed the tool standard output stream for a job in the resulting document. +instance_access_link: + category: utility + renders: Link to the Galaxy instance. + embeddable: true + requires: none + side_panel_name: Instance Access Link -tool_stderr: - side_panel_name: Tool Error - side_panel_description: of job run - help: | - Embed the tool standard error stream for a job in the resulting document. +instance_resources_link: + category: utility + renders: Link to instance resources. + embeddable: true + requires: none + side_panel_name: Instance Resources Link + +instance_help_link: + category: utility + renders: Link to instance help. + embeddable: true + requires: none + side_panel_name: Instance Help Link + +instance_support_link: + category: utility + renders: Link to instance support. + embeddable: true + requires: none + side_panel_name: Instance Support Link + +instance_citation_link: + category: utility + renders: Link to instance citation information. + embeddable: true + requires: none + side_panel_name: Instance Citation Link + +instance_terms_link: + category: utility + renders: Link to instance terms. + embeddable: true + requires: none + side_panel_name: Instance Terms Link + +instance_organization_link: + category: utility + renders: Link to the instance's organization. + embeddable: true + requires: none + side_panel_name: Instance Organization Link diff --git a/scripts/markdown_directives_doc.py b/scripts/markdown_directives_doc.py new file mode 100644 index 00000000000..56650d0e10c --- /dev/null +++ b/scripts/markdown_directives_doc.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python +"""Generate the Galaxy Markdown directive reference from directives.yml. + +directives.yml is the source of truth for directive documentation metadata. This +script renders it to a Markdown reference (directives.md) and can also verify that +the metadata is consistent with the authoritative validator in +``galaxy.managers.markdown_parse`` and that the checked-in reference is up to date. + +Usage:: + + python scripts/markdown_directives_doc.py # (re)write directives.md + python scripts/markdown_directives_doc.py --check # verify, non-zero exit on drift +""" + +import argparse +import os +import sys + +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, "lib"))) + +import yaml + +from galaxy.managers.markdown_parse import ( + DynamicArguments, + EMBED_CAPABLE_DIRECTIVES, + SHARED_ARGUMENTS, + VALID_ARGUMENTS, +) + +MARKDOWN_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, "client", "src", "components", "Markdown") +) +DIRECTIVES_YML = os.path.join(MARKDOWN_DIR, "directives.yml") +REQUIREMENTS_YML = os.path.join(MARKDOWN_DIR, "Utilities", "requirements.yml") +OUTPUT_MD = os.path.join(MARKDOWN_DIR, "directives.md") + +CATEGORY_ORDER = ["dataset", "collection", "invocation", "workflow", "job", "visualization", "utility"] +CATEGORY_TITLES = { + "dataset": "Dataset directives", + "collection": "Collection directives", + "invocation": "Invocation directives", + "workflow": "Workflow directives", + "job": "Job directives", + "visualization": "Visualization directive", + "utility": "Utility & instance directives", +} + +TYPE_ORDER = ["label", "id", "int", "boolean", "enum", "string", "path"] +TYPE_NOTES = { + "label": "Workflow input/output/step label; resolved to an ID per invocation.", + "id": "Encoded (export) or numeric (internal) object ID.", + "int": "Integer.", + "boolean": "`true` or `false`.", + "enum": "One of a fixed set of values.", + "string": "Free display text.", + "path": "File within a composite / extra-files dataset.", +} + +CONTEXT_ORDER = ["report", "page", "notebook", "invocation"] +CONTEXT_NOTES = { + "report": "Workflow report template — labels resolve per invocation.", + "page": "Page / direct contexts — encoded or numeric IDs.", + "notebook": "History-relative reference (notebooks).", + "invocation": "Invocation reference — usually injected automatically.", +} + + +def load_directives(path=DIRECTIVES_YML): + """Return (parameter_sets, directives) parsed from directives.yml.""" + with open(path) as f: + data = yaml.safe_load(f) + parameter_sets = data.get("_parameter_sets", {}) + directives = {key: value for key, value in data.items() if not key.startswith("_")} + return parameter_sets, directives + + +def load_requirements(path=REQUIREMENTS_YML): + """Return a mapping of directive -> required object from requirements.yml.""" + with open(path) as f: + data = yaml.safe_load(f) + requires = {} + for obj, directives in data.items(): + for directive in directives: + requires[directive] = obj + return requires + + +def resolve_parameters(entry, parameter_sets): + """Merge a directive's shared parameter_set and inline parameters, preserving order.""" + parameters = {} + set_name = entry.get("parameter_set") + if set_name and set_name in parameter_sets: + parameters.update(parameter_sets[set_name]) + parameters.update(entry.get("parameters", {})) + return parameters + + +def _mode_value(value): + """Collapse a possibly mode-keyed value to a single string (prefer report).""" + if isinstance(value, dict): + value = value.get("report") or value.get("page") or next(iter(value.values())) + return value + + +def consistency_errors(parameter_sets, directives, requirements): + """Return a list of human-readable mismatches between directives.yml and the validator.""" + errors = [] + + yml_names = set(directives) + valid_names = set(VALID_ARGUMENTS) + for missing in sorted(valid_names - yml_names): + errors.append(f"directives.yml is missing an entry for directive '{missing}'") + for extra in sorted(yml_names - valid_names): + errors.append(f"directives.yml has entry for unknown directive '{extra}'") + + for name in sorted(yml_names & valid_names): + entry = directives[name] + + set_name = entry.get("parameter_set") + if set_name and set_name not in parameter_sets: + errors.append(f"'{name}': unknown parameter_set '{set_name}'") + + expected_embed = name in EMBED_CAPABLE_DIRECTIVES + if bool(entry.get("embeddable")) != expected_embed: + errors.append(f"'{name}': embeddable should be {str(expected_embed).lower()}") + + expected_requires = requirements.get(name, "none") + if entry.get("requires") != expected_requires: + errors.append(f"'{name}': requires should be '{expected_requires}'") + + valid_args = VALID_ARGUMENTS[name] + if isinstance(valid_args, DynamicArguments): + if not entry.get("dynamic_parameters"): + errors.append(f"'{name}': should set dynamic_parameters: true") + continue + + resolved = set(resolve_parameters(entry, parameter_sets)) + expected_args = set(valid_args) + if SHARED_ARGUMENTS[0] in resolved: + errors.append(f"'{name}': must not list shared argument '{SHARED_ARGUMENTS[0]}'") + for missing in sorted(expected_args - resolved): + errors.append(f"'{name}': missing parameter '{missing}'") + for extra in sorted(resolved - expected_args): + errors.append(f"'{name}': parameter '{extra}' is not accepted by the validator") + + return errors + + +def _table(headers, rows): + lines = ["| " + " | ".join(headers) + " |", "|" + "|".join(["---"] * len(headers)) + "|"] + for row in rows: + lines.append("| " + " | ".join(row) + " |") + return "\n".join(lines) + + +def render_markdown(parameter_sets, directives): + """Render directives.yml metadata to the Markdown reference.""" + out = [] + out.append("# Galaxy Markdown Directive Reference") + out.append("") + out.append( + "Generated from `directives.yml` by `scripts/markdown_directives_doc.py` " + "(`make client-gen-markdown-directives`). Do not edit by hand." + ) + out.append("") + out.append("## Syntax") + out.append("") + out.append("**Block** — works for every directive; required in workflow report templates:") + out.append("") + out.append("````") + out.append("```galaxy") + out.append("directive_name(arg=value)") + out.append("```") + out.append("````") + out.append("") + out.append( + "One directive per fenced `galaxy` block. **Inline** (`${galaxy ...}`) works only for the " + "[embeddable directives](#embeddable-directives)." + ) + out.append("") + + out.append("## Argument value types") + out.append("") + present_types = { + param.get("type") + for entry in directives.values() + for param in resolve_parameters(entry, parameter_sets).values() + } + rows = [[f"`{t}`", TYPE_NOTES[t]] for t in TYPE_ORDER if t in present_types] + out.append(_table(["Type", "Meaning"], rows)) + out.append("") + + out.append("## Addressing contexts") + out.append("") + out.append("The same directive accepts different parameters depending on how the object is referenced:") + out.append("") + present_contexts = { + param.get("context") + for entry in directives.values() + for param in resolve_parameters(entry, parameter_sets).values() + if param.get("context") + } + rows = [[f"`{c}`", CONTEXT_NOTES[c]] for c in CONTEXT_ORDER if c in present_contexts] + out.append(_table(["Context", "Use"], rows)) + out.append("") + + out.append("## Universal argument") + out.append("") + out.append( + f'`{SHARED_ARGUMENTS[0]}=""` — wraps a block directive in a collapsible section. ' + "Valid on every directive." + ) + out.append("") + + by_category = {category: [] for category in CATEGORY_ORDER} + for name, entry in directives.items(): + by_category.setdefault(entry.get("category", "utility"), []).append((name, entry)) + + for category in CATEGORY_ORDER: + entries = by_category.get(category) or [] + if not entries: + continue + out.append("---") + out.append("") + out.append(f"## {CATEGORY_TITLES[category]}") + out.append("") + rows = [] + for name, entry in entries: + embed = "✅" if entry.get("embeddable") else "" + requires = entry.get("requires", "none") + requires_cell = "—" if requires == "none" else f"`{requires}`" + rows.append([f"`{name}`", embed, requires_cell, entry.get("renders", "")]) + out.append(_table(["Directive", "Embed", "Requires", "Renders"], rows)) + out.append("") + + for name, entry in entries: + help_text = _mode_value(entry.get("help")) + parameters = resolve_parameters(entry, parameter_sets) + dynamic = entry.get("dynamic_parameters") + if not help_text and not parameters and not dynamic: + continue + out.append(f"### `{name}`") + out.append("") + if help_text: + out.append(help_text.replace("%MODE%", "report").strip()) + out.append("") + if dynamic: + if not help_text: + out.append("Accepts arguments specific to the selected visualization plugin (not validated).") + out.append("") + elif parameters: + param_rows = [] + for param_name, meta in parameters.items(): + default = meta.get("default") + default_cell = ( + "" if default is None else f"`{str(default).lower() if isinstance(default, bool) else default}`" + ) + if meta.get("type") == "enum" and meta.get("values"): + type_cell = "enum (" + ", ".join(f"`{v}`" for v in meta["values"]) + ")" + else: + type_cell = f"`{meta.get('type', '')}`" + param_rows.append( + [ + f"`{param_name}`", + type_cell, + f"`{meta['context']}`" if meta.get("context") else "", + default_cell, + meta.get("description", ""), + ] + ) + out.append(_table(["Parameter", "Type", "Context", "Default", "Description"], param_rows)) + out.append("") + + out.append("---") + out.append("") + out.append("## Embeddable directives") + out.append("") + out.append("Inline `${galaxy ...}` syntax is supported only for these directives; all others require block syntax.") + out.append("") + for name in EMBED_CAPABLE_DIRECTIVES: + out.append(f"- `{name}`") + + return "\n".join(out) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--check", action="store_true", help="verify consistency and up-to-date output") + parser.add_argument("--output", default=OUTPUT_MD, help="output path for directives.md") + args = parser.parse_args() + + parameter_sets, directives = load_directives() + requirements = load_requirements() + + errors = consistency_errors(parameter_sets, directives, requirements) + if errors: + sys.stderr.write("directives.yml is inconsistent with markdown_parse.py:\n") + for error in errors: + sys.stderr.write(f" - {error}\n") + sys.exit(1) + + rendered = render_markdown(parameter_sets, directives) + "\n" + + if args.check: + try: + with open(args.output) as f: + current = f.read() + except FileNotFoundError: + current = None + if current != rendered: + sys.stderr.write(f"{args.output} is out of date; regenerate with scripts/markdown_directives_doc.py\n") + sys.exit(1) + return + + with open(args.output, "w") as f: + f.write(rendered) + + +if __name__ == "__main__": + main() diff --git a/test/unit/app/test_markdown_directives_doc.py b/test/unit/app/test_markdown_directives_doc.py new file mode 100644 index 00000000000..ad4c6923895 --- /dev/null +++ b/test/unit/app/test_markdown_directives_doc.py @@ -0,0 +1,30 @@ +"""Guard that directives.yml, the generated directives.md, and the validator stay in sync.""" + +import importlib.util +import os + +GALAXY_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)) +SCRIPT_PATH = os.path.join(GALAXY_ROOT, "scripts", "markdown_directives_doc.py") + +_spec = importlib.util.spec_from_file_location("markdown_directives_doc", SCRIPT_PATH) +assert _spec and _spec.loader +gen = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(gen) + + +def test_directives_yml_consistent_with_validator(): + parameter_sets, directives = gen.load_directives() + requirements = gen.load_requirements() + errors = gen.consistency_errors(parameter_sets, directives, requirements) + assert not errors, "directives.yml drifted from markdown_parse.py:\n" + "\n".join(errors) + + +def test_directives_md_up_to_date(): + parameter_sets, directives = gen.load_directives() + rendered = gen.render_markdown(parameter_sets, directives) + "\n" + with open(gen.OUTPUT_MD) as f: + current = f.read() + assert current == rendered, ( + "directives.md is out of date; regenerate with " + "`python scripts/markdown_directives_doc.py` (or `make client-gen-markdown-directives`)." + )