mkdocstrings-github 0.6.3__py3-none-any.whl → 0.7.0__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.6.3
3
+ Version: 0.7.0
4
4
  Summary: A GitHub Action handler for mkdocstrings
5
5
  Author-email: Mark Hu <watermarkhu@gmail.com>
6
6
  License: MIT
@@ -1,9 +1,9 @@
1
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=4El8kAupC4np2lwzvvMHi9AyHzeT4D6f8eUJO0RfKxY,8781
4
- mkdocstrings_handlers/github/objects.py,sha256=0E899mr7SQBPAZ7W4i0aIi5dyXIdfdxOm57vatXst0k,7818
2
+ mkdocstrings_handlers/github/config.py,sha256=G__jcRZkhltdwwh1YJWbx9xEdblF9mTc4_L1NEWE6Bw,7873
3
+ mkdocstrings_handlers/github/handler.py,sha256=W1hkFowofpsToDLzkR0MRV3ClCQckUS2VkSjfOLKYRU,8875
4
+ mkdocstrings_handlers/github/objects.py,sha256=2gKgrkkyBem3a0MUpffAH5C5asLk8Dqgqf6ntZuTxCo,9317
5
5
  mkdocstrings_handlers/github/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- mkdocstrings_handlers/github/rendering.py,sha256=pFk621Fqp_R6ZS2dJ0zwEEez0Urqpekial6kqKYDag8,3371
6
+ mkdocstrings_handlers/github/rendering.py,sha256=N92Gbm9Qi4I2EZC69JsEixO1AdHuf1du9isJNXG4vhQ,7192
7
7
  mkdocstrings_handlers/github/templates/material/_macros.html.jinja,sha256=1TNUgoOIxm-a1S7eiESrW1fYuzXOjrwFqXQSyA4tkkU,1576
8
8
  mkdocstrings_handlers/github/templates/material/action.html.jinja,sha256=C7S8I9bjnrEUahyDB7CkFxtakFrr53KE3t3uyczBPzc,2864
9
9
  mkdocstrings_handlers/github/templates/material/heading.html.jinja,sha256=wnvZpNED8Dhb935qnddeDExXN-MIUz8frRRfgq-A9VA,1396
@@ -11,8 +11,8 @@ mkdocstrings_handlers/github/templates/material/inputs.html.jinja,sha256=UE8RmfM
11
11
  mkdocstrings_handlers/github/templates/material/outputs.html.jinja,sha256=Z9QD1KSALLDS9VEyugWG1BHpLCHTKYEx4TEQkKIoC3M,2693
12
12
  mkdocstrings_handlers/github/templates/material/secrets.html.jinja,sha256=jQ_HaG2ivbZTH74pyV4BAFGQu5Vn03kA_vaEbTSABds,2839
13
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.3.dist-info/METADATA,sha256=mVWkkUIE2BuAbaPFHb1BJKZu85LRdhWedEjuzIWhPeg,4052
16
- mkdocstrings_github-0.6.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
- mkdocstrings_github-0.6.3.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
18
- mkdocstrings_github-0.6.3.dist-info/RECORD,,
14
+ mkdocstrings_handlers/github/templates/material/workflow.html.jinja,sha256=VS3AqT0OgX6QPFCwN63tULM9tC4t6FGtOoVCixQGPl4,4543
15
+ mkdocstrings_github-0.7.0.dist-info/METADATA,sha256=6Tyvq8M9xxX2zLCOEA9-fzgOX_zQlUMln3dvOujSO7g,4052
16
+ mkdocstrings_github-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ mkdocstrings_github-0.7.0.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
18
+ mkdocstrings_github-0.7.0.dist-info/RECORD,,
@@ -21,6 +21,7 @@ logger = get_logger(__name__)
21
21
  SIGNATURE_VERSION = Literal["ref", "major", "semver", "string"]
22
22
  PARAMETERS_ORDER = Literal["alphabetical", "source"]
23
23
  PARAMETERS_SECTION_STYLE = Literal["table", "list"]
24
+ STEP_DIRECTION = Literal["TB", "LR"]
24
25
 
25
26
 
26
27
  class GitHubOptions(BaseModel):
@@ -201,6 +202,26 @@ class GitHubOptions(BaseModel):
201
202
  description="Whether to add anchors to parameters in the documentation.",
202
203
  )
203
204
 
205
+ # Workflow chart option
206
+ workflow_chart: bool = Field(
207
+ default=False,
208
+ description="""Whether to generate a Mermaid flowchart for reusable workflows.
209
+
210
+ The flowchart displays the workflow's jobs and steps in a flowchart diagram.
211
+ Multiple jobs are rendered as subgraphs, and calls to other workflows are visually distinct.
212
+ The diagram is rendered client-side in the browser using mkdocs-mermaid2.
213
+ """,
214
+ )
215
+
216
+ workflow_chart_step_direction: STEP_DIRECTION = Field(
217
+ default="LR",
218
+ description="""The direction of the flowchart for steps within jobs.
219
+
220
+ - `TB`: top-to-bottom layout,
221
+ - `LR`: left-to-right layout.
222
+ """,
223
+ )
224
+
204
225
 
205
226
  class GitHubConfig(BaseModel):
206
227
  """Configuration options for the GitHub handler."""
@@ -173,6 +173,7 @@ class GitHubHandler(BaseHandler):
173
173
  self.env.filters["group_parameters"] = rendering.group_parameters
174
174
  self.env.filters["anchor_id"] = rendering.anchor_id
175
175
  self.env.filters["as_string"] = rendering.as_string
176
+ self.env.filters["generate_mermaid_flowchart"] = rendering.generate_mermaid_flowchart
176
177
  self.env.globals["semver_tag"] = self.semver
177
178
  self.env.globals["major_tag"] = self.major
178
179
  self.env.globals["git_repo"] = self.repo
@@ -2,7 +2,7 @@ import re
2
2
  from dataclasses import dataclass, field
3
3
  from enum import Enum
4
4
  from os import PathLike
5
- from typing import Any, Literal, Optional
5
+ from typing import Any, Literal
6
6
 
7
7
  from ruamel.yaml import YAML
8
8
  from ruamel.yaml.comments import CommentedMap
@@ -35,7 +35,7 @@ class Input:
35
35
  required: bool = False
36
36
  type: Literal["boolean", "number", "string"] = "string"
37
37
  default: bool | float | int | str | None = None
38
- deprecationMessage: Optional[str] = None
38
+ deprecationMessage: str | None = None
39
39
  group: str = ""
40
40
 
41
41
 
@@ -55,6 +55,31 @@ class Secret:
55
55
  group: str = ""
56
56
 
57
57
 
58
+ @dataclass
59
+ class Step:
60
+ """Represents a step within a job."""
61
+
62
+ name: str
63
+ uses: str = "" # For steps that use actions
64
+ run: str = "" # For steps that run commands
65
+
66
+
67
+ @dataclass
68
+ class Job:
69
+ """Represents a job within a workflow."""
70
+
71
+ id: str
72
+ name: str
73
+ uses: str | None
74
+ steps: list[Step] = field(default_factory=list)
75
+ needs: list[str] = field(default_factory=list) # Job dependencies
76
+
77
+ @property
78
+ def mermaid_id(self) -> str:
79
+ job_id_safe = self.id.replace("-", "_").replace(".", "_")
80
+ return f"job_{job_id_safe}"
81
+
82
+
58
83
  def _get_member(d: dict, key: str, error_message: str = "", default: Any = None) -> Any:
59
84
  if key not in d:
60
85
  if default is not None:
@@ -164,6 +189,7 @@ class Workflow:
164
189
  inputs: list[Input] = field(default_factory=list)
165
190
  secrets: list[Secret] = field(default_factory=list)
166
191
  outputs: list[Output] = field(default_factory=list)
192
+ jobs: dict[str, Job] = field(default_factory=dict)
167
193
  template: Literal["workflow.html.jinja"] = "workflow.html.jinja"
168
194
 
169
195
  @property
@@ -221,11 +247,11 @@ class Workflow:
221
247
  workflow.permissions[key] = PermissionLevel.from_label(label)
222
248
  else:
223
249
  raise ValueError("permissions must be a string or a dictionary")
224
- for job in data.get("jobs", {}).values():
225
- if isinstance(permissions := job.get("permissions", {}), str):
250
+ for job_id, job_data in data.get("jobs", {}).items():
251
+ if isinstance(permissions := job_data.get("permissions", {}), str):
226
252
  set_all_permissions(permissions)
227
253
  elif isinstance(permissions, dict):
228
- for key, label in job.get("permissions", {}).items():
254
+ for key, label in job_data.get("permissions", {}).items():
229
255
  if key in workflow.permissions:
230
256
  permission = PermissionLevel.from_label(label)
231
257
  if permission > workflow.permissions[key]:
@@ -235,4 +261,29 @@ class Workflow:
235
261
  else:
236
262
  raise ValueError("permissions must be a string or a dictionary")
237
263
 
264
+ # Parse job information for flowchart
265
+ job = Job(id=job_id, name=job_data.get("name", job_id), uses=job_data.get("uses", None))
266
+
267
+ # Parse job dependencies
268
+ needs = job_data.get("needs", [])
269
+ if isinstance(needs, str):
270
+ job.needs = [needs]
271
+ elif isinstance(needs, list):
272
+ job.needs = needs
273
+
274
+ # Parse steps
275
+ for step_data in job_data.get("steps", []):
276
+ step_name = step_data.get("name", "")
277
+ step_uses = step_data.get("uses", "")
278
+ step_run = step_data.get("run", "")
279
+
280
+ step = Step(
281
+ name=step_name,
282
+ uses=step_uses,
283
+ run=step_run,
284
+ )
285
+ job.steps.append(step)
286
+
287
+ workflow.jobs[job.id] = job
288
+
238
289
  return workflow
@@ -6,8 +6,8 @@ from typing import TYPE_CHECKING, Sequence
6
6
 
7
7
  from jinja2 import pass_context
8
8
 
9
- from mkdocstrings_handlers.github.config import PARAMETERS_ORDER, GitHubOptions
10
- from mkdocstrings_handlers.github.objects import Input, Output, Secret
9
+ from mkdocstrings_handlers.github.config import PARAMETERS_ORDER, STEP_DIRECTION, GitHubOptions
10
+ from mkdocstrings_handlers.github.objects import Input, Output, Secret, Workflow
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from git import Repo
@@ -106,3 +106,99 @@ def as_string(value: bool | str | int | float | None) -> str:
106
106
  return ""
107
107
  case _:
108
108
  raise TypeError(f"Unsupported type: {type(value)}")
109
+
110
+
111
+ def generate_mermaid_flowchart(workflow: Workflow, direction: STEP_DIRECTION = "TB") -> str:
112
+ """Generate a Mermaid flowchart for a reusable workflow.
113
+
114
+ Args:
115
+ workflow: The workflow object containing jobs and steps.
116
+ direction: The direction of steps within jobs (TB for top-to-bottom, LR for left-to-right).
117
+
118
+ Returns:
119
+ A Mermaid flowchart diagram as a string.
120
+ """
121
+ if not workflow.jobs:
122
+ return ""
123
+
124
+ lines = ["flowchart TB"]
125
+
126
+ # Track all nodes for dependency linking
127
+ job_start_nodes = {}
128
+ job_end_nodes = {}
129
+
130
+ for job in workflow.jobs.values():
131
+ job_id_safe = job.mermaid_id
132
+
133
+ # Check if job calls another workflow (has any steps with workflow set)
134
+ if job.uses is not None:
135
+ # Job that calls a workflow - render as a single subroutine node
136
+ lines.append(f' {job_id_safe}[["{job.name}"]]')
137
+ job_start_nodes[job.id] = job_id_safe
138
+ job_end_nodes[job.id] = job_id_safe
139
+ continue
140
+
141
+ # Regular job - render as a subgraph with steps
142
+
143
+ lines.append(f' subgraph {job_id_safe}["{job.name}"]')
144
+ lines.append(f" direction {direction}")
145
+
146
+ if job.steps:
147
+ # Filter steps that have a name
148
+ named_steps = [(idx, step) for idx, step in enumerate(job.steps) if step.name]
149
+
150
+ if named_steps:
151
+ prev_step_id = None
152
+
153
+ for idx, step in named_steps:
154
+ step_id = f"{job_id_safe}_step_{idx}"
155
+ step_name = step.name
156
+
157
+ # Escape special characters in step names
158
+ step_name_escaped = (
159
+ step_name.replace('"', "&quot;").replace("[", "&#91;").replace("]", "&#93;")
160
+ )
161
+
162
+ # Determine node style based on step type
163
+ if step.uses:
164
+ # Action uses get rounded rectangle
165
+ lines.append(f' {step_id}("{step_name_escaped}")')
166
+ else:
167
+ # Regular run steps get standard rectangle
168
+ lines.append(f' {step_id}["{step_name_escaped}"]')
169
+
170
+ # Link to previous step
171
+ if prev_step_id:
172
+ lines.append(f" {prev_step_id} --> {step_id}")
173
+
174
+ prev_step_id = step_id
175
+
176
+ # Track first and last step nodes for job dependencies
177
+ first_step_idx, _ = named_steps[0]
178
+ last_step_idx, _ = named_steps[-1]
179
+ first_step_id = f"{job_id_safe}_step_{first_step_idx}"
180
+ last_step_id = f"{job_id_safe}_step_{last_step_idx}"
181
+ job_start_nodes[job.id] = first_step_id
182
+ job_end_nodes[job.id] = last_step_id
183
+ else:
184
+ # Job with no named steps - create a single placeholder node
185
+ placeholder_id = f"{job_id_safe}_placeholder"
186
+ lines.append(f" {placeholder_id}[No named steps defined]")
187
+ job_start_nodes[job.id] = placeholder_id
188
+ job_end_nodes[job.id] = placeholder_id
189
+ else:
190
+ # Job with no steps - create a single node
191
+ placeholder_id = f"{job_id_safe}_placeholder"
192
+ lines.append(f" {placeholder_id}[No steps defined]")
193
+ job_start_nodes[job.id] = placeholder_id
194
+ job_end_nodes[job.id] = placeholder_id
195
+
196
+ lines.append(" end")
197
+
198
+ # Add job dependencies
199
+ for job in workflow.jobs.values():
200
+ for needed_job_id in job.needs:
201
+ needed_job = workflow.jobs[needed_job_id]
202
+ lines.append(f" {needed_job.mermaid_id} -.-> {job.mermaid_id}")
203
+
204
+ return "\n".join(lines)
@@ -74,6 +74,17 @@ Context:
74
74
  {% endif %}
75
75
  {% endblock description %}
76
76
 
77
+ {% block flowchart scoped %}
78
+ {% if options.workflow_chart %}
79
+ {% set flowchart = data | generate_mermaid_flowchart(options.workflow_chart_step_direction) %}
80
+ {% if flowchart %}
81
+ <pre class="mermaid"><code>
82
+ {{ flowchart }}
83
+ </code></pre>
84
+ {% endif %}
85
+ {% endif %}
86
+ {% endblock flowchart %}
87
+
77
88
  {% block inputs scoped %}
78
89
  {% if options.show_inputs %}
79
90
  {% with inputs = data.inputs | order_parameters(options.parameters_order) | filter_parameters(required=options.show_inputs_only_required) %}