mkdocstrings-github 0.4.5__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mkdocstrings-github
3
- Version: 0.4.5
3
+ Version: 0.6.1
4
4
  Summary: A GitHub Action handler for mkdocstrings
5
5
  Author-email: Mark Hu <watermarkhu@gmail.com>
6
6
  License: MIT
@@ -21,9 +21,9 @@ Classifier: Topic :: Software Development :: Documentation
21
21
  Classifier: Topic :: Utilities
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: <3.15,>=3.10
24
- Requires-Dist: gitpython~=3.1.45
25
- Requires-Dist: mkdocstrings~=0.29
26
- Requires-Dist: packaging~=25.0
24
+ Requires-Dist: gitpython<4,>=3.1.45
25
+ Requires-Dist: mkdocstrings<1,>=0.29
26
+ Requires-Dist: ruamel-yaml<1,>=0.18.16
27
27
  Requires-Dist: typing-extensions>=4.0; python_version < '3.11'
28
28
  Description-Content-Type: text/markdown
29
29
 
@@ -33,14 +33,26 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  <p align="center">A GitHub Actions handler for <a href="https://github.com/mkdocstrings/mkdocstrings"><i>mkdocstrings</i></a>.</p>
35
35
 
36
- <p align="center"><img width=300px src="logo.png"></p>
36
+ <!-- --8<-- [end:header] -->
37
+
38
+ <p align="center"><img width=300px src="docs/img/logo.png"></p>
37
39
 
38
40
  [![Qualify](https://github.com/watermarkhu/mkdocstrings-github/actions/workflows/qualify.yaml/badge.svg?branch=main)](https://github.com/watermarkhu/mkdocstrings-github/actions/workflows/qualify.yaml)
39
41
  [![documentation](https://img.shields.io/badge/docs-mkdocs-708FCC.svg?style=flat)](https://watermarkhu.nl/mkdocstrings-github)
40
42
  [![pypi version](https://img.shields.io/pypi/v/mkdocstrings-github.svg)](https://pypi.org/project/mkdocstrings-github/)
41
43
  [![codecov](https://codecov.io/github/watermarkhu/mkdocstrings-github/graph/badge.svg?token=M6XW8UeURE)](https://codecov.io/github/watermarkhu/mkdocstrings-github)
42
44
 
43
- <!-- --8<-- [end:header] -->
45
+
46
+
47
+ For example, the following page is generated from [actions/checkout](https://github.com/actions/checkout):
48
+
49
+ <picture>
50
+ <source media="(prefers-color-scheme: dark)" srcset="docs/img/example_dark.png">
51
+ <source media="(prefers-color-scheme: light)" srcset="docs/img/example_light.png">
52
+ <img alt="Fallback image description" src="docs/img/example_light.png">
53
+ </picture>
54
+
55
+
44
56
  <!-- --8<-- [start:install] -->
45
57
  You can install the GitHub handler by specifying it as a dependency:
46
58
 
@@ -49,9 +61,17 @@ You can install the GitHub handler by specifying it as a dependency:
49
61
  # adapt to your dependencies manager
50
62
  [project]
51
63
  dependencies = [
52
- "mkdocstrings-github>=0.X.Y",
64
+ "mkdocstrings-github",
53
65
  ]
54
66
  ```
67
+
68
+ after which the generated documentation can be inserted in the markdown page with:
69
+
70
+ ```md
71
+ ::: <path-to-action-or-workflow>
72
+ handler: github
73
+ ```
74
+
55
75
  <!-- --8<-- [end:install] -->
56
76
 
57
77
  <!-- --8<-- [start:footer] -->
@@ -63,5 +83,6 @@ dependencies = [
63
83
  - 🧩 **Individual Parameter Hyperlinks**: Each action or workflow parameter—including inputs, outputs, and secrets—receives a unique HTML id, facilitating direct linking to specific parameter documentation.
64
84
  - 🔒 **Automated Permission Aggregation**: For reusable workflows, if permissions are specified at the job level rather than the workflow level, the required final permissions are automatically determined and displayed in the signature.
65
85
  - 🔗 **Parameter cross-linking**: Link to other parameters of the action or workflow via a simple Markdown syntax.
86
+ - 🧑‍🤝‍🧑 **Parameter grouping**: Organize related inputs, outputs, and secrets into visual groups using inline YAML comments for clearer documentation structure.
66
87
 
67
88
  <!-- --8<-- [end:footer] -->
@@ -0,0 +1,18 @@
1
+ mkdocstrings_handlers/github/__init__.py,sha256=0WdFUIq4Xu2ZFtlZNIYCQSoqcx3Ot9Wv41_X_dwbFww,248
2
+ mkdocstrings_handlers/github/config.py,sha256=r7efiI-vKbVEeD6utrc55h4RP6VIlabqDebhiIIx_ZA,7120
3
+ mkdocstrings_handlers/github/handler.py,sha256=SQcd08VA3g4f3Fof2mam85ahPLiK99TteAJJpUg-iB4,8489
4
+ mkdocstrings_handlers/github/objects.py,sha256=v1GchB9fzqasnXbVEOXoDzOR2iVTwcfPQ9mFT4sdjgs,7625
5
+ mkdocstrings_handlers/github/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ mkdocstrings_handlers/github/rendering.py,sha256=pFk621Fqp_R6ZS2dJ0zwEEez0Urqpekial6kqKYDag8,3371
7
+ mkdocstrings_handlers/github/templates/material/_macros.html.jinja,sha256=1TNUgoOIxm-a1S7eiESrW1fYuzXOjrwFqXQSyA4tkkU,1576
8
+ mkdocstrings_handlers/github/templates/material/action.html.jinja,sha256=C7S8I9bjnrEUahyDB7CkFxtakFrr53KE3t3uyczBPzc,2864
9
+ mkdocstrings_handlers/github/templates/material/heading.html.jinja,sha256=wnvZpNED8Dhb935qnddeDExXN-MIUz8frRRfgq-A9VA,1396
10
+ mkdocstrings_handlers/github/templates/material/inputs.html.jinja,sha256=UE8RmfMlF6np7CP-920CwgkbXIcKNgl9KgK1BJ588GM,3271
11
+ mkdocstrings_handlers/github/templates/material/outputs.html.jinja,sha256=Z9QD1KSALLDS9VEyugWG1BHpLCHTKYEx4TEQkKIoC3M,2693
12
+ mkdocstrings_handlers/github/templates/material/secrets.html.jinja,sha256=jQ_HaG2ivbZTH74pyV4BAFGQu5Vn03kA_vaEbTSABds,2839
13
+ mkdocstrings_handlers/github/templates/material/style.css,sha256=Nfmds-xHtPJ_IzOv5svA7ih5talHDTiQryN_n0DGdZs,1553
14
+ mkdocstrings_handlers/github/templates/material/workflow.html.jinja,sha256=5dLdHRSQyulyFAVCVZAR_pkw-WXxCtM20cj6RS7IH1Q,4206
15
+ mkdocstrings_github-0.6.1.dist-info/METADATA,sha256=fdZ69Xer6TQ4ZpkPnzyv1P2H2eE5KLi27DltQAJWNqA,4055
16
+ mkdocstrings_github-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ mkdocstrings_github-0.6.1.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
18
+ mkdocstrings_github-0.6.1.dist-info/RECORD,,
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import re
6
5
  import sys
7
6
  from typing import Literal
8
7
 
@@ -92,6 +91,15 @@ class GitHubOptions(BaseModel):
92
91
  description="Whether to show the signature in the documentation.",
93
92
  )
94
93
 
94
+ signature_repository: str = Field(
95
+ default="",
96
+ description="""The GitHub repository in the format *owner/repo*.
97
+
98
+ By default, the repository is inferred from the current git repository using the default origin remote.
99
+ If it cannot be inferred, it must be set manually.
100
+ """,
101
+ )
102
+
95
103
  signature_show_secrets: bool = Field(
96
104
  default=False,
97
105
  description="Whether to show secrets in the signature.",
@@ -162,6 +170,32 @@ class GitHubOptions(BaseModel):
162
170
  """,
163
171
  )
164
172
 
173
+ parameters_groups: bool = Field(
174
+ default=True,
175
+ description="""Whether to group parameters by their group in the documentation.
176
+
177
+ This is done by adding a comment `# group: <group name>` directly after the parameter definition in the action/workflow file.
178
+ This can be done for inputs, outputs and secrets. E.g.:
179
+
180
+ ```yaml
181
+ inputs:
182
+ my_input: # group: Example Group
183
+ description: "An example input"
184
+ required: true
185
+ ```
186
+
187
+ This has no effect if there are no groups defined for any parameter.
188
+ """,
189
+ )
190
+
191
+ parameters_group_title_row: bool = Field(
192
+ default=True,
193
+ description="""Whether to add a title row for each parameter group in the documentation.
194
+ This only has an effect if [`parameters_groups`][mkdocstrings_handlers.github.config.GitHubOptions.parameters_groups] is set to `true`,
195
+ and if the [`parameters_section_style`][mkdocstrings_handlers.github.config.GitHubOptions.parameters_section_style] is set to `table`.
196
+ """,
197
+ )
198
+
165
199
  parameters_anchors: bool = Field(
166
200
  default=True,
167
201
  description="Whether to add anchors to parameters in the documentation.",
@@ -171,16 +205,6 @@ class GitHubOptions(BaseModel):
171
205
  class GitHubConfig(BaseModel):
172
206
  """Configuration options for the GitHub handler."""
173
207
 
174
- repo: str = Field(
175
- default="",
176
- description="""The GitHub repository in the format *owner/repo*.
177
-
178
- By default, the repository is inferred from the current git repository using the default origin remote.
179
- If it cannot be inferred, it must be set manually.
180
- """,
181
- pattern=re.compile(r"^[\w.-]+/[\w.-]+$"),
182
- )
183
-
184
208
  feather_icons_source: str = Field(
185
209
  default="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js",
186
210
  description="""The source URL for Feather icons.
@@ -70,41 +70,9 @@ class GitHubHandler(BaseHandler):
70
70
  self.major: str = ""
71
71
  self.semver: str = ""
72
72
 
73
- # Determine owner and repo name
74
- if not self.config.repo:
75
- self.get_repo()
76
-
77
73
  if rendering.ENV_MAJOR_TAG not in os.environ or rendering.ENV_SEMVER_TAG not in os.environ:
78
74
  self.get_releases()
79
75
 
80
- def get_repo(self) -> None:
81
- # Get repo from environment variable or git remotes.
82
- if os.environ.get("GITHUB_ACTIONS") == "true" and (
83
- repo := os.environ.get("GITHUB_REPOSITORY")
84
- ):
85
- self.config.repo = repo
86
- else:
87
- # Try each remote to find a valid GitHub owner/repo
88
- owner = None
89
- repo_name = None
90
- for remote in self.repo.remotes:
91
- for url in remote.urls:
92
- match = re.search(
93
- r"(?P<host>[\w\.-]+)[/:](?P<owner>[^/]+)/(?P<repo>[^/.]+?)(?:\.git)?$",
94
- url,
95
- )
96
- if match:
97
- owner = match.group("owner")
98
- repo_name = match.group("repo")
99
- break
100
- if owner and repo_name:
101
- break
102
- if not (owner and repo_name):
103
- raise PluginError(
104
- f"Could not determine GitHub repository owner/name from config.repo='{self.config.repo}' or any git remote URL."
105
- )
106
- self.config.repo = f"{owner}/{repo_name}"
107
-
108
76
  def get_releases(self) -> None:
109
77
  # Get all tags from the local git repository.
110
78
  try:
@@ -167,6 +135,34 @@ class GitHubHandler(BaseHandler):
167
135
  except Exception as error:
168
136
  raise PluginError(f"Invalid options: {error}") from error
169
137
 
138
+ def get_repository_name(self) -> str:
139
+ # Get repo from environment variable or git remotes.
140
+ if os.environ.get("GITHUB_ACTIONS") == "true" and (
141
+ repo := os.environ.get("GITHUB_REPOSITORY")
142
+ ):
143
+ return repo
144
+ else:
145
+ # Try each remote to find a valid GitHub owner/repo
146
+ owner = None
147
+ repo_name = None
148
+ for remote in self.repo.remotes:
149
+ for url in remote.urls:
150
+ match = re.search(
151
+ r"(?P<host>[\w\.-]+)[/:](?P<owner>[^/]+)/(?P<repo>[^/.]+?)(?:\.git)?$",
152
+ url,
153
+ )
154
+ if match:
155
+ owner = match.group("owner")
156
+ repo_name = match.group("repo")
157
+ break
158
+ if owner and repo_name:
159
+ break
160
+ if not (owner and repo_name):
161
+ raise PluginError(
162
+ "Could not determine GitHub repository owner/name from any git remote URL."
163
+ )
164
+ return f"{owner}/{repo_name}"
165
+
170
166
  def update_env(self, config: Any) -> None:
171
167
  self.env.trim_blocks = True
172
168
  self.env.lstrip_blocks = True
@@ -174,11 +170,13 @@ class GitHubHandler(BaseHandler):
174
170
  self.env.filters["format_action_signature"] = rendering.format_action_signature
175
171
  self.env.filters["order_parameters"] = rendering.order_parameters
176
172
  self.env.filters["filter_parameters"] = rendering.filter_parameters
173
+ self.env.filters["group_parameters"] = rendering.group_parameters
177
174
  self.env.filters["anchor_id"] = rendering.anchor_id
178
175
  self.env.filters["as_string"] = rendering.as_string
179
176
  self.env.globals["semver_tag"] = self.semver
180
177
  self.env.globals["major_tag"] = self.major
181
178
  self.env.globals["git_repo"] = self.repo
179
+ self.env.globals["repository_name"] = self.get_repository_name()
182
180
 
183
181
  def collect(self, identifier: str, options: GitHubOptions) -> Workflow | Action | None:
184
182
  path = Path(self.repo.working_tree_dir) / identifier
@@ -1,10 +1,28 @@
1
- from collections import OrderedDict
1
+ import re
2
2
  from dataclasses import dataclass, field
3
3
  from enum import Enum
4
4
  from os import PathLike
5
5
  from typing import Any, Literal, Optional
6
6
 
7
- import yaml
7
+ from ruamel.yaml import YAML
8
+ from ruamel.yaml.comments import CommentedMap
9
+
10
+ yaml = YAML()
11
+
12
+
13
+ GROUP_PATTERN = r"#\s*group:\s*(.+)$"
14
+
15
+
16
+ def group_from_map(map: CommentedMap) -> str:
17
+ """Extract group string from a comment line if it matches the group pattern."""
18
+ if map.ca.comment:
19
+ for comment in map.ca.comment:
20
+ if comment is not None:
21
+ group_matches = re.finditer(GROUP_PATTERN, comment.value)
22
+ group_string = next((m.group(1).strip() for m in group_matches), "")
23
+ if group_string:
24
+ return group_string
25
+ return ""
8
26
 
9
27
 
10
28
  @dataclass
@@ -15,28 +33,15 @@ class Input:
15
33
  type: Literal["boolean", "number", "string"] = "string"
16
34
  default: bool | float | int | str | None = None
17
35
  deprecationMessage: Optional[str] = None
18
-
19
- @staticmethod
20
- def from_data(
21
- name: str,
22
- description: str = "",
23
- required: bool = False,
24
- type: Literal["boolean", "number", "string"] = "string",
25
- default: bool | float | int | str | None = None,
26
- deprecationMessage: Optional[str] = None,
27
- **kwargs,
28
- ) -> "Input":
29
- return Input(name, description, required, type, default, deprecationMessage)
36
+ group: str = ""
30
37
 
31
38
 
32
39
  @dataclass
33
40
  class Output:
34
41
  name: str
35
42
  description: str = ""
36
-
37
- @staticmethod
38
- def from_data(name: str, description: str = "", **kwargs) -> "Output":
39
- return Output(name, description)
43
+ value: str = ""
44
+ group: str = ""
40
45
 
41
46
 
42
47
  @dataclass
@@ -44,10 +49,7 @@ class Secret:
44
49
  name: str
45
50
  description: str = ""
46
51
  required: bool = False
47
-
48
- @staticmethod
49
- def from_data(name: str, description: str = "", required: bool = False, **kwargs) -> "Secret":
50
- return Secret(name, description, required)
52
+ group: str = ""
51
53
 
52
54
 
53
55
  def _get_member(d: dict, key: str, error_message: str = "", default: Any = None) -> Any:
@@ -58,36 +60,11 @@ def _get_member(d: dict, key: str, error_message: str = "", default: Any = None)
58
60
  return d[key]
59
61
 
60
62
 
61
- class _OrderedLoader(yaml.Loader):
62
- pass
63
-
64
-
65
- # Remove boolean resolver for "on", "off", "yes", "no" (and case variants)
66
- for ch in "yYnNoO":
67
- if ch in _OrderedLoader.yaml_implicit_resolvers:
68
- _OrderedLoader.yaml_implicit_resolvers[ch] = [
69
- res
70
- for res in _OrderedLoader.yaml_implicit_resolvers[ch]
71
- if res[0] != "tag:yaml.org,2002:bool"
72
- ]
73
-
74
-
75
- def _construct_mapping(loader, node):
76
- loader.flatten_mapping(node)
77
- return OrderedDict(loader.construct_pairs(node))
78
-
79
-
80
- _OrderedLoader.add_constructor(
81
- yaml.SafeLoader.DEFAULT_MAPPING_TAG,
82
- _construct_mapping,
83
- )
84
-
85
-
86
63
  def _read_file(file: PathLike) -> tuple[str, dict]:
87
64
  with open(file, "r", encoding="utf-8") as f:
88
65
  source = f.read()
89
66
  f.seek(0)
90
- data = yaml.load(f, Loader=_OrderedLoader)
67
+ data = yaml.load(f)
91
68
  return source, data
92
69
 
93
70
 
@@ -106,10 +83,6 @@ class Action:
106
83
  branding: dict = field(default_factory=dict)
107
84
  template: Literal["action.html.jinja"] = "action.html.jinja"
108
85
 
109
- @property
110
- def members(self) -> list[Input | Output]:
111
- return self.inputs + self.outputs
112
-
113
86
  @staticmethod
114
87
  def from_file(file: PathLike, id: str) -> "Action":
115
88
  source, data = _read_file(file)
@@ -125,12 +98,31 @@ class Action:
125
98
  branding=_get_member(data, "branding", default={}),
126
99
  )
127
100
  for key, value in data.get("inputs", {}).items():
128
- action.inputs.append(Input.from_data(key, **value))
101
+ action.inputs.append(Input(name=key, **value, group=group_from_map(value)))
129
102
  for key, value in data.get("outputs", {}).items():
130
- action.outputs.append(Output.from_data(key, **value))
103
+ action.outputs.append(Output(name=key, **value, group=group_from_map(value)))
131
104
  return action
132
105
 
133
106
 
107
+ # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idpermissions
108
+ PERMISSION_SCOPES: list[str] = [
109
+ "actions",
110
+ "attestations",
111
+ "checks",
112
+ "contents",
113
+ "deployments",
114
+ "discussions",
115
+ "id-token",
116
+ "issues",
117
+ "models",
118
+ "packages",
119
+ "pages",
120
+ "pull-requests",
121
+ "security-events",
122
+ "statuses",
123
+ ]
124
+
125
+
134
126
  class PermissionLevel(Enum):
135
127
  none = ("none", 0)
136
128
  read = ("read", 1)
@@ -151,21 +143,6 @@ class PermissionLevel(Enum):
151
143
  return perm
152
144
  raise ValueError(f"No Permission with label '{label}'")
153
145
 
154
- def __le__(self, other):
155
- if isinstance(other, PermissionLevel):
156
- return self.number <= other.number
157
- return NotImplemented
158
-
159
- def __lt__(self, other):
160
- if isinstance(other, PermissionLevel):
161
- return self.number < other.number
162
- return NotImplemented
163
-
164
- def __ge__(self, other):
165
- if isinstance(other, PermissionLevel):
166
- return self.number >= other.number
167
- return NotImplemented
168
-
169
146
  def __gt__(self, other):
170
147
  if isinstance(other, PermissionLevel):
171
148
  return self.number > other.number
@@ -187,8 +164,18 @@ class Workflow:
187
164
  template: Literal["workflow.html.jinja"] = "workflow.html.jinja"
188
165
 
189
166
  @property
190
- def members(self) -> list[Input | Output | Secret]:
191
- return self.inputs + self.outputs + self.secrets
167
+ def permission_read_all(self) -> bool:
168
+ return all(
169
+ scope in self.permissions and self.permissions[scope] == PermissionLevel.read
170
+ for scope in PERMISSION_SCOPES
171
+ )
172
+
173
+ @property
174
+ def permission_write_all(self) -> bool:
175
+ return all(
176
+ scope in self.permissions and self.permissions[scope] == PermissionLevel.write
177
+ for scope in PERMISSION_SCOPES
178
+ )
192
179
 
193
180
  @staticmethod
194
181
  def from_file(file: PathLike, id: str) -> "Workflow | None":
@@ -208,18 +195,41 @@ class Workflow:
208
195
  call = data["on"]["workflow_call"]
209
196
  if call:
210
197
  for key, value in call.get("inputs", {}).items():
211
- workflow.inputs.append(Input.from_data(key, **value))
198
+ workflow.inputs.append(Input(name=key, **value, group=group_from_map(value)))
212
199
  for key, value in call.get("outputs", {}).items():
213
- workflow.outputs.append(Output.from_data(key, **value))
200
+ workflow.outputs.append(Output(name=key, **value, group=group_from_map(value)))
214
201
  for key, value in call.get("secrets", {}).items():
215
- workflow.secrets.append(Secret.from_data(key, **value))
216
- for key, label in data.get("permissions", {}).items():
217
- workflow.permissions[key] = PermissionLevel.from_label(label)
202
+ workflow.secrets.append(Secret(name=key, **value, group=group_from_map(value)))
203
+
204
+ def set_all_permissions(level: str):
205
+ if level == "read-all":
206
+ for key in PERMISSION_SCOPES:
207
+ workflow.permissions[key] = PermissionLevel.read
208
+ elif level == "write-all":
209
+ for key in PERMISSION_SCOPES:
210
+ workflow.permissions[key] = PermissionLevel.write
211
+ else:
212
+ raise ValueError(f"Unknown permission level '{level}'")
213
+
214
+ if isinstance(permissions := data.get("permissions", {}), str):
215
+ set_all_permissions(permissions)
216
+ elif isinstance(permissions, dict):
217
+ for key, label in permissions.items():
218
+ workflow.permissions[key] = PermissionLevel.from_label(label)
219
+ else:
220
+ raise ValueError("permissions must be a string or a dictionary")
218
221
  for job in data.get("jobs", {}).values():
219
- for key, label in job.get("permissions", {}).items():
220
- if key in workflow.permissions:
221
- if permission := PermissionLevel.from_label(label) > workflow.permissions[key]:
222
- workflow.permissions[key] = permission
223
- else:
224
- workflow.permissions[key] = PermissionLevel.from_label(label)
222
+ if isinstance(permissions := job.get("permissions", {}), str):
223
+ set_all_permissions(permissions)
224
+ elif isinstance(permissions, dict):
225
+ for key, label in job.get("permissions", {}).items():
226
+ if key in workflow.permissions:
227
+ permission = PermissionLevel.from_label(label)
228
+ if permission > workflow.permissions[key]:
229
+ workflow.permissions[key] = permission
230
+ else:
231
+ workflow.permissions[key] = PermissionLevel.from_label(label)
232
+ else:
233
+ raise ValueError("permissions must be a string or a dictionary")
234
+
225
235
  return workflow
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
+ from collections import OrderedDict
4
5
  from typing import TYPE_CHECKING, Sequence
5
6
 
6
7
  from jinja2 import pass_context
@@ -40,6 +41,22 @@ def format_action_signature(context: Context, id: str, repo: str, options: GitHu
40
41
  return f"{name}@{version}"
41
42
 
42
43
 
44
+ def group_parameters(
45
+ parameters: Sequence[Input | Output | Secret],
46
+ do_group: bool,
47
+ ) -> OrderedDict[str, list[Input | Output | Secret]]:
48
+ grouped: OrderedDict[str, list[Input | Output | Secret]] = OrderedDict()
49
+ if not do_group:
50
+ grouped[""] = list(parameters)
51
+ return grouped
52
+ for parameter in parameters:
53
+ group = getattr(parameter, "group", "")
54
+ if group not in grouped:
55
+ grouped[group] = []
56
+ grouped[group].append(parameter)
57
+ return grouped
58
+
59
+
43
60
  def order_parameters(
44
61
  parameters: Sequence[Input | Output | Secret], parameters_order: PARAMETERS_ORDER
45
62
  ):
@@ -0,0 +1,38 @@
1
+ {#- Shared macros for rendering parameters in different styles -#}
2
+
3
+ {#- Macro to render a single parameter item in list style -#}
4
+ {% macro render_parameter_item(item, item_type, options, data) %}
5
+ {% set anchor_id = item.name | anchor_id(item_type, data.id) %}
6
+ <li class="doc-section-item field-body">
7
+ <b><code>{{ item.name }}</code></b>
8
+ {%- if item.required is defined and item.required %} - <em>required</em>{% endif %}
9
+ {% if options.parameters_anchors %}
10
+ <a class="headerlink" href="#{{ anchor_id }}" title="Link to {{ item.name }}">¤</a>
11
+ {% endif %}
12
+ <div class="doc-md-description">
13
+ {{ item.description | convert_markdown(options.heading_level, data.id) }}
14
+ </div>
15
+ {% if item.default is defined and item.default is not none %}
16
+ <div class="doc-md-description">
17
+ <code>{{ item.default | as_string }}</code>
18
+ </div>
19
+ {% endif %}
20
+ </li>
21
+ {% endmacro %}
22
+
23
+ {#- Macro to render grouped parameters in list style -#}
24
+ {% macro render_grouped_list(items, item_type, options, data) %}
25
+ {% set grouped_items = items | group_parameters(options.parameters_groups) %}
26
+ {% for group, group_items in grouped_items.items() %}
27
+ {% if group %}
28
+ <li class="gh-group-item">
29
+ <em>{{ group }}:</em>
30
+ <ul>
31
+ {% for item in group_items %}{{ render_parameter_item(item, item_type, options, data) }}{% endfor %}
32
+ </ul>
33
+ </li>
34
+ {% else %}
35
+ {% for item in group_items %}{{ render_parameter_item(item, item_type, options, data) }}{% endfor %}
36
+ {% endif %}
37
+ {% endfor %}
38
+ {% endmacro %}
@@ -22,7 +22,7 @@ Context:
22
22
 
23
23
  {% include "heading.html.jinja" with context %}
24
24
 
25
- {% set signature = data.id | format_action_signature(config.repo, options) %}
25
+ {% set signature = data.id | format_action_signature(options.signature_repository if options.signature_repository else repository_name, options) %}
26
26
 
27
27
  {% block signature scoped %}
28
28
  {% if options.show_signature %}
@@ -7,6 +7,8 @@ Context:
7
7
  options (dict): The local options
8
8
  -#}
9
9
 
10
+ {% from "_macros.html.jinja" import render_grouped_list %}
11
+
10
12
  {% block logs scoped %}
11
13
  {{ log.debug("Rendering inputs of " + data.id) }}
12
14
  {% endblock logs %}
@@ -27,7 +29,7 @@ Context:
27
29
  <span class="doc-section-title">Inputs:</span>
28
30
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
29
31
  </p>
30
- <table>
32
+ <table data-gh-parameters>
31
33
  <thead>
32
34
  <tr>
33
35
  <th>Name</th>
@@ -35,8 +37,15 @@ Context:
35
37
  {% if default_column %}<th>Default</th>{% endif %}
36
38
  </tr>
37
39
  </thead>
40
+ {% set grouped_inputs = inputs | group_parameters(options.parameters_groups) %}
41
+ {% for group, group_inputs in grouped_inputs.items() %}
38
42
  <tbody>
39
- {% for input in inputs %}
43
+ {% if options.parameters_group_title_row and grouped_inputs | length > 1 and group != "" %}
44
+ <tr class="doc-section-item">
45
+ <td class="gh-group-title" colspan="{% if default_column %}3{% else %}2{% endif %}">{{ group }}</td>
46
+ </tr>
47
+ {% endif %}
48
+ {% for input in group_inputs %}
40
49
  {% if options.parameters_anchors %}
41
50
  {% set anchor_id = input.name | anchor_id("inputs", data.id) %}
42
51
  {% else %}
@@ -66,6 +75,7 @@ Context:
66
75
  </tr>
67
76
  {% endfor %}
68
77
  </tbody>
78
+ {% endfor %}
69
79
  </table>
70
80
  {% endblock table_style %}
71
81
  {% elif options.parameters_section_style == "list" %}
@@ -75,24 +85,7 @@ Context:
75
85
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
76
86
  </p>
77
87
  <ul>
78
- {% for input in inputs %}
79
- {% set anchor_id = input.name | anchor_id("inputs", data.id) %}
80
- <li class="doc-section-item field-body">
81
- <b><code>{{ input.name }}</code></b>
82
- {%- if input.required %} - <em>required</em>{% endif %}
83
- {% if options.parameters_anchors %}
84
- <a class="headerlink" href="#{{ anchor_id }}" title="Link to {{ input.name }}">¤</a>
85
- {% endif %}
86
- <div class="doc-md-description">
87
- {{ input.description | convert_markdown(options.heading_level, data.id) }}
88
- </div>
89
- {% if input.default is not none %}
90
- <div class="doc-md-description">
91
- Default: <code>{{ input.default | as_string }}</code>
92
- </div>
93
- {% endif %}
94
- </li>
95
- {% endfor %}
88
+ {{ render_grouped_list(inputs, "inputs", options, data) }}
96
89
  </ul>
97
90
  {% endblock list_style %}
98
91
  {% endif %}
@@ -7,6 +7,8 @@ Context:
7
7
  options (dict): The local options
8
8
  -#}
9
9
 
10
+ {% from "_macros.html.jinja" import render_grouped_list %}
11
+
10
12
  {% block logs scoped %}
11
13
  {{ log.debug("Rendering outputs of " + data.id) }}
12
14
  {% endblock logs %}
@@ -24,15 +26,22 @@ Context:
24
26
  <span class="doc-section-title">Outputs:</span>
25
27
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
26
28
  </p>
27
- <table>
29
+ <table data-gh-parameters>
28
30
  <thead>
29
31
  <tr>
30
32
  <th>Name</th>
31
33
  <th>Description</th>
32
34
  </tr>
33
35
  </thead>
36
+ {% set grouped_outputs = outputs | group_parameters(options.parameters_groups) %}
37
+ {% for group, group_outputs in grouped_outputs.items() %}
34
38
  <tbody>
35
- {% for output in outputs %}
39
+ {% if options.parameters_group_title_row and grouped_outputs | length > 1 and group != "" %}
40
+ <tr class="doc-section-item">
41
+ <td class="gh-group-title" colspan="2">{{ group }}</td>
42
+ </tr>
43
+ {% endif %}
44
+ {% for output in group_outputs %}
36
45
  {% if options.parameters_anchors %}
37
46
  {% set anchor_id = output.name | anchor_id("outputs", data.id) %}
38
47
  {% else %}
@@ -48,8 +57,10 @@ Context:
48
57
  <td>
49
58
  {{ output.description | convert_markdown(options.heading_level, data.id) }}
50
59
  </td>
60
+ </tr>
51
61
  {% endfor %}
52
62
  </tbody>
63
+ {% endfor %}
53
64
  </table>
54
65
  {% endblock table_style %}
55
66
  {% elif options.parameters_section_style == "list" %}
@@ -59,18 +70,7 @@ Context:
59
70
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
60
71
  </p>
61
72
  <ul>
62
- {% for output in outputs %}
63
- {% set anchor_id = output.name | anchor_id("outputs", data.id) %}
64
- <li class="doc-section-item field-body">
65
- <b><code>{{ output.name }}</code></b>
66
- {% if options.parameters_anchors %}
67
- <a class="headerlink" href="#{{ anchor_id }}" title="Link to {{ output.name }}">¤</a>
68
- {% endif %}
69
- <div class="doc-md-description">
70
- {{ output.description | convert_markdown(options.heading_level, data.id) }}
71
- </div>
72
- </li>
73
- {% endfor %}
73
+ {{ render_grouped_list(outputs, "outputs", options, data) }}
74
74
  </ul>
75
75
  {% endblock list_style %}
76
76
  {% endif %}
@@ -7,6 +7,8 @@ Context:
7
7
  options (dict): The local options
8
8
  -#}
9
9
 
10
+ {% from "_macros.html.jinja" import render_grouped_list %}
11
+
10
12
  {% block logs scoped %}
11
13
  {{ log.debug("Rendering secrets of " + data.id) }}
12
14
  {% endblock logs %}
@@ -24,15 +26,22 @@ Context:
24
26
  <span class="doc-section-title">Secrets:</span>
25
27
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
26
28
  </p>
27
- <table>
29
+ <table data-gh-parameters>
28
30
  <thead>
29
31
  <tr>
30
32
  <th>Name</th>
31
33
  <th>Description</th>
32
34
  </tr>
33
35
  </thead>
36
+ {% set grouped_secrets = secrets | group_parameters(options.parameters_groups) %}
37
+ {% for group, group_secrets in grouped_secrets.items() %}
34
38
  <tbody>
35
- {% for secret in secrets %}
39
+ {% if options.parameters_group_title_row and grouped_secrets | length > 1 and group != "" %}
40
+ <tr class="doc-section-item">
41
+ <td class="gh-group-title" colspan="2">{{ group }}</td>
42
+ </tr>
43
+ {% endif %}
44
+ {% for secret in group_secrets %}
36
45
  {% if options.parameters_anchors %}
37
46
  {% set anchor_id = secret.name | anchor_id("secrets", data.id) %}
38
47
  {% else %}
@@ -52,8 +61,10 @@ Context:
52
61
  <td>
53
62
  {{ secret.description | convert_markdown(options.heading_level, data.id) }}
54
63
  </td>
64
+ </tr>
55
65
  {% endfor %}
56
66
  </tbody>
67
+ {% endfor %}
57
68
  </table>
58
69
  {% endblock table_style %}
59
70
  {% elif options.parameters_section_style == "list" %}
@@ -63,19 +74,7 @@ Context:
63
74
  <a class="headerlink" href="#{{ header_id }}" title="Link to {{ data.name }} outputs">¤</a>
64
75
  </p>
65
76
  <ul>
66
- {% for secret in secrets %}
67
- {% set anchor_id = secret.name | anchor_id("secrets", data.id) %}
68
- <li class="doc-section-item field-body">
69
- <b><code>{{ secret.name }}</code></b>
70
- {%- if secret.required %} - <em>required</em>{% endif %}
71
- {% if options.parameters_anchors %}
72
- <a class="headerlink" href="#{{ anchor_id }}" title="Link to {{ secret.name }}">¤</a>
73
- {% endif %}
74
- <div class="doc-md-description">
75
- {{ secret.description | convert_markdown(options.heading_level, data.id) }}
76
- </div>
77
- </li>
78
- {% endfor %}
77
+ {{ render_grouped_list(secrets, "secrets", options, data) }}
79
78
  </ul>
80
79
  {% endblock list_style %}
81
80
  {% endif %}
@@ -57,3 +57,22 @@
57
57
  background-color: #24292e;
58
58
  color: #ffffff;
59
59
  }
60
+
61
+
62
+ /* Add thicker divider between thead and tbody, and between tbody groups */
63
+ table[data-gh-parameters] thead + tbody tr:first-child td,
64
+ table[data-gh-parameters] tbody:not(:first-of-type) tr:first-child td {
65
+ border-top: 2px solid var(--md-default-fg-color--lighter) !important;
66
+ }
67
+
68
+ /* Disable word-wrap in first column (except group titles) */
69
+ table[data-gh-parameters] td:first-child:not(.gh-group-title) {
70
+ white-space: nowrap;
71
+ }
72
+
73
+ /* Style group title rows */
74
+ table[data-gh-parameters] td.gh-group-title {
75
+ text-align: center !important;
76
+ font-style: italic;
77
+ white-space: normal;
78
+ }
@@ -20,7 +20,7 @@ Context:
20
20
  <div class="doc doc-object doc-workflow">
21
21
  {% include "heading.html.jinja" with context %}
22
22
 
23
- {% set signature = data.id | format_action_signature(config.repo, options) %}
23
+ {% set signature = data.id | format_action_signature(options.signature_repository if options.signature_repository else repository_name, options) %}
24
24
 
25
25
  {% block signature scoped %}
26
26
  {% if options.show_signature %}
@@ -29,11 +29,17 @@ Context:
29
29
  {% filter highlight(language="yaml", inline=False, linenums=False) %}
30
30
  uses: {{ signature }}
31
31
  {% if options.signature_show_permissions and data.permissions|length > 0 %}
32
+ {% if data.permission_read_all %}
33
+ permissions: read-all
34
+ {% elif data.permission_write_all %}
35
+ permissions: write-all
36
+ {% else %}
32
37
  permissions:
33
38
  {% for scope, level in data.permissions | items %}
34
39
  {{ scope }}: {{ level.label }}
35
40
  {% endfor %}
36
41
  {% endif %}
42
+ {% endif %}
37
43
  {% if inputs|length > 0 %}
38
44
  with:
39
45
  {% for input in inputs %}
@@ -1,17 +0,0 @@
1
- mkdocstrings_handlers/github/__init__.py,sha256=0WdFUIq4Xu2ZFtlZNIYCQSoqcx3Ot9Wv41_X_dwbFww,248
2
- mkdocstrings_handlers/github/config.py,sha256=pSjA6gU-kC_mXQqNIIGOYEOhtX5VzZDT0H1hRFlGaj8,6089
3
- mkdocstrings_handlers/github/handler.py,sha256=QJbZjRbllUuGs5rY6SLwaII6hsPQnioa_OmauWGL-lU,8495
4
- mkdocstrings_handlers/github/objects.py,sha256=JDkY7mg_LGlIEwZHP2wSd8ZkB6RVtRsu_JEpwV-PikQ,7069
5
- mkdocstrings_handlers/github/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- mkdocstrings_handlers/github/rendering.py,sha256=6xcE2WwyTRW_38g7Ek55hlm53EsFFqueazFw12__DyA,2820
7
- mkdocstrings_handlers/github/templates/material/action.html.jinja,sha256=rCzKrkLyUiD3jKODjQcBDsC-eq2KKvTQ6t3JgUmpm_k,2794
8
- mkdocstrings_handlers/github/templates/material/heading.html.jinja,sha256=wnvZpNED8Dhb935qnddeDExXN-MIUz8frRRfgq-A9VA,1396
9
- mkdocstrings_handlers/github/templates/material/inputs.html.jinja,sha256=CIcw3OBQdCP4e5A4srLu1v3xoOjsedIw1Zh3qxtG0-A,3482
10
- mkdocstrings_handlers/github/templates/material/outputs.html.jinja,sha256=jlzVF93q5AyJfOiSl3_1VBVL3c6rjmEcS81s3sri5Gg,2670
11
- mkdocstrings_handlers/github/templates/material/secrets.html.jinja,sha256=1lMJoxjjeiqetVu0mdLKmZ1faYRReeyjiYvYTZESots,2881
12
- mkdocstrings_handlers/github/templates/material/style.css,sha256=R2hh1RfHwJ6XNT4YQl_txNkNXcPBZJMCBZn5kQEMQ6M,962
13
- mkdocstrings_handlers/github/templates/material/workflow.html.jinja,sha256=OLFnkkX0bxVwIksZ_hf9ujMnns2nwV2df0vIrn1ly-U,3937
14
- mkdocstrings_github-0.4.5.dist-info/METADATA,sha256=C6T2gQ9mAp6RpIHLsLyQyucWn131bzPanwP1hHBn40I,3340
15
- mkdocstrings_github-0.4.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- mkdocstrings_github-0.4.5.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
17
- mkdocstrings_github-0.4.5.dist-info/RECORD,,