mkdocstrings-github 0.4.4__py3-none-any.whl → 0.5.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.4.4
3
+ Version: 0.5.0
4
4
  Summary: A GitHub Action handler for mkdocstrings
5
5
  Author-email: Mark Hu <watermarkhu@gmail.com>
6
6
  License: MIT
@@ -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] -->
@@ -1,17 +1,17 @@
1
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
2
+ mkdocstrings_handlers/github/config.py,sha256=JrI9HK6g-XwXuKFJegfPOpvw-_ZMG0iipSNnEqU0wcw,6105
3
+ mkdocstrings_handlers/github/handler.py,sha256=ShE0EUsWDgonE4LtR3MFPEzCLwvDb2UztrAiXqQs_oc,8415
4
+ mkdocstrings_handlers/github/objects.py,sha256=ckMEEoj3pdLasGHzZMwYY7XdjwbUdHRch07dAzgEHs0,8217
5
5
  mkdocstrings_handlers/github/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  mkdocstrings_handlers/github/rendering.py,sha256=6xcE2WwyTRW_38g7Ek55hlm53EsFFqueazFw12__DyA,2820
7
- mkdocstrings_handlers/github/templates/material/action.html.jinja,sha256=_mfYFUJfaYtraK-VF2_i2vPafU0QsBIDb3bYHxuXlI8,2510
7
+ mkdocstrings_handlers/github/templates/material/action.html.jinja,sha256=C7S8I9bjnrEUahyDB7CkFxtakFrr53KE3t3uyczBPzc,2864
8
8
  mkdocstrings_handlers/github/templates/material/heading.html.jinja,sha256=wnvZpNED8Dhb935qnddeDExXN-MIUz8frRRfgq-A9VA,1396
9
9
  mkdocstrings_handlers/github/templates/material/inputs.html.jinja,sha256=CIcw3OBQdCP4e5A4srLu1v3xoOjsedIw1Zh3qxtG0-A,3482
10
10
  mkdocstrings_handlers/github/templates/material/outputs.html.jinja,sha256=jlzVF93q5AyJfOiSl3_1VBVL3c6rjmEcS81s3sri5Gg,2670
11
11
  mkdocstrings_handlers/github/templates/material/secrets.html.jinja,sha256=1lMJoxjjeiqetVu0mdLKmZ1faYRReeyjiYvYTZESots,2881
12
12
  mkdocstrings_handlers/github/templates/material/style.css,sha256=R2hh1RfHwJ6XNT4YQl_txNkNXcPBZJMCBZn5kQEMQ6M,962
13
- mkdocstrings_handlers/github/templates/material/workflow.html.jinja,sha256=FsUCb91LdWBwOtMpWOLMTVtP44QQSHuxO-HKR4c9Ld4,3444
14
- mkdocstrings_github-0.4.4.dist-info/METADATA,sha256=CV-IoKbaNQqIsXV6xzyoxV4W4l51Z-q_If7JqFw0m7w,3340
15
- mkdocstrings_github-0.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- mkdocstrings_github-0.4.4.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
17
- mkdocstrings_github-0.4.4.dist-info/RECORD,,
13
+ mkdocstrings_handlers/github/templates/material/workflow.html.jinja,sha256=5dLdHRSQyulyFAVCVZAR_pkw-WXxCtM20cj6RS7IH1Q,4206
14
+ mkdocstrings_github-0.5.0.dist-info/METADATA,sha256=maSQ6Gbg1xgruSrClF-EHllBLo4fy2q5A30XctacPSw,3867
15
+ mkdocstrings_github-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ mkdocstrings_github-0.5.0.dist-info/licenses/LICENSE,sha256=5enZtJ4zSp0Ps3jTqFQ4kadcx62BhgTaDNPrXWb3g3E,1069
17
+ mkdocstrings_github-0.5.0.dist-info/RECORD,,
@@ -92,6 +92,16 @@ class GitHubOptions(BaseModel):
92
92
  description="Whether to show the signature in the documentation.",
93
93
  )
94
94
 
95
+ signature_repository: str = Field(
96
+ default="",
97
+ description="""The GitHub repository in the format *owner/repo*.
98
+
99
+ By default, the repository is inferred from the current git repository using the default origin remote.
100
+ If it cannot be inferred, it must be set manually.
101
+ """,
102
+ pattern=re.compile(r"^[\w.-]+/[\w.-]+$"),
103
+ )
104
+
95
105
  signature_show_secrets: bool = Field(
96
106
  default=False,
97
107
  description="Whether to show secrets in the signature.",
@@ -171,16 +181,6 @@ class GitHubOptions(BaseModel):
171
181
  class GitHubConfig(BaseModel):
172
182
  """Configuration options for the GitHub handler."""
173
183
 
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
184
  feather_icons_source: str = Field(
185
185
  default="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js",
186
186
  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
@@ -179,6 +175,7 @@ class GitHubHandler(BaseHandler):
179
175
  self.env.globals["semver_tag"] = self.semver
180
176
  self.env.globals["major_tag"] = self.major
181
177
  self.env.globals["git_repo"] = self.repo
178
+ self.env.globals["repository_name"] = self.get_repository_name()
182
179
 
183
180
  def collect(self, identifier: str, options: GitHubOptions) -> Workflow | Action | None:
184
181
  path = Path(self.repo.working_tree_dir) / identifier
@@ -106,10 +106,6 @@ class Action:
106
106
  branding: dict = field(default_factory=dict)
107
107
  template: Literal["action.html.jinja"] = "action.html.jinja"
108
108
 
109
- @property
110
- def members(self) -> list[Input | Output]:
111
- return self.inputs + self.outputs
112
-
113
109
  @staticmethod
114
110
  def from_file(file: PathLike, id: str) -> "Action":
115
111
  source, data = _read_file(file)
@@ -131,6 +127,25 @@ class Action:
131
127
  return action
132
128
 
133
129
 
130
+ # https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idpermissions
131
+ PERMISSION_SCOPES: list[str] = [
132
+ "actions",
133
+ "attestations",
134
+ "checks",
135
+ "contents",
136
+ "deployments",
137
+ "discussions",
138
+ "id-token",
139
+ "issues",
140
+ "models",
141
+ "packages",
142
+ "pages",
143
+ "pull-requests",
144
+ "security-events",
145
+ "statuses",
146
+ ]
147
+
148
+
134
149
  class PermissionLevel(Enum):
135
150
  none = ("none", 0)
136
151
  read = ("read", 1)
@@ -151,21 +166,6 @@ class PermissionLevel(Enum):
151
166
  return perm
152
167
  raise ValueError(f"No Permission with label '{label}'")
153
168
 
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
169
  def __gt__(self, other):
170
170
  if isinstance(other, PermissionLevel):
171
171
  return self.number > other.number
@@ -187,8 +187,18 @@ class Workflow:
187
187
  template: Literal["workflow.html.jinja"] = "workflow.html.jinja"
188
188
 
189
189
  @property
190
- def members(self) -> list[Input | Output | Secret]:
191
- return self.inputs + self.outputs + self.secrets
190
+ def permission_read_all(self) -> bool:
191
+ return all(
192
+ scope in self.permissions and self.permissions[scope] == PermissionLevel.read
193
+ for scope in PERMISSION_SCOPES
194
+ )
195
+
196
+ @property
197
+ def permission_write_all(self) -> bool:
198
+ return all(
199
+ scope in self.permissions and self.permissions[scope] == PermissionLevel.write
200
+ for scope in PERMISSION_SCOPES
201
+ )
192
202
 
193
203
  @staticmethod
194
204
  def from_file(file: PathLike, id: str) -> "Workflow | None":
@@ -213,13 +223,36 @@ class Workflow:
213
223
  workflow.outputs.append(Output.from_data(key, **value))
214
224
  for key, value in call.get("secrets", {}).items():
215
225
  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)
226
+
227
+ def set_all_permissions(level: str):
228
+ if level == "read-all":
229
+ for key in PERMISSION_SCOPES:
230
+ workflow.permissions[key] = PermissionLevel.read
231
+ elif level == "write-all":
232
+ for key in PERMISSION_SCOPES:
233
+ workflow.permissions[key] = PermissionLevel.write
234
+ else:
235
+ raise ValueError(f"Unknown permission level '{level}'")
236
+
237
+ if isinstance(permissions := data.get("permissions", {}), str):
238
+ set_all_permissions(permissions)
239
+ elif isinstance(permissions, dict):
240
+ for key, label in permissions.items():
241
+ workflow.permissions[key] = PermissionLevel.from_label(label)
242
+ else:
243
+ raise ValueError("permissions must be a string or a dictionary")
218
244
  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)
245
+ if isinstance(permissions := job.get("permissions", {}), str):
246
+ set_all_permissions(permissions)
247
+ elif isinstance(permissions, dict):
248
+ for key, label in job.get("permissions", {}).items():
249
+ if key in workflow.permissions:
250
+ permission = PermissionLevel.from_label(label)
251
+ if permission > workflow.permissions[key]:
252
+ workflow.permissions[key] = permission
253
+ else:
254
+ workflow.permissions[key] = PermissionLevel.from_label(label)
255
+ else:
256
+ raise ValueError("permissions must be a string or a dictionary")
257
+
225
258
  return workflow
@@ -7,7 +7,6 @@ Context:
7
7
  config (mkdocstrings_handlers.github.config.GitHubConfig): The global configuration
8
8
  options (dict): The local options
9
9
  -#}
10
-
11
10
  {% block logs scoped %}
12
11
  {#- Logging block.
13
12
 
@@ -23,24 +22,30 @@ Context:
23
22
 
24
23
  {% include "heading.html.jinja" with context %}
25
24
 
26
- {% 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) %}
27
26
 
28
27
  {% block signature scoped %}
29
28
  {% if options.show_signature %}
30
- {% filter highlight(language="yaml", inline=False, linenums=False) %}
31
- - uses: {{ signature }}
32
- {% with inputs = data.inputs | order_parameters(options.parameters_order) | filter_parameters(required=True) %}
33
- {% if inputs|length > 0 %}
34
- with:
35
- {% for input in inputs %}
36
- {{ input.name }}: 📝
37
- {% endfor %}
38
- {% endif %}
39
- {% endwith %}
40
- {% endfilter %}
29
+ {% with inputs = data.inputs | order_parameters(options.parameters_order) | filter_parameters(required=True) %}
30
+ <div class="annotate">
31
+ {% filter highlight(language="yaml", inline=False, linenums=False) %}
32
+ - uses: {{ signature }}
33
+ {% if inputs|length > 0 %}
34
+ with:
35
+ {% for input in inputs %}
36
+ {{ input.name }}: ({{ loop.index }})
37
+ {% endfor %}
38
+ {% endif %}
39
+ {% endfilter %}
40
+ </div>
41
+ <ol>
42
+ {% for input in inputs %}
43
+ <li>{{ input.description | convert_markdown(options.heading_level) if input.description else "(no description)" }}</li>
44
+ {% endfor %}
45
+ </ol>
46
+ {% endwith %}
41
47
  {% endif %}
42
48
  {% endblock signature %}
43
-
44
49
  {% block description scoped %}
45
50
  {% if options.show_description %}
46
51
  {{ options.description if options.description else data.description | convert_markdown(options.heading_level, data.id) }}
@@ -20,37 +20,51 @@ 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 %}
27
- {% filter highlight(language="yaml", inline=False, linenums=False) %}
28
- uses: {{ signature }}
29
- {% if options.signature_show_permissions and data.permissions|length > 0 %}
30
- permissions:
31
- {% for scope, level in data.permissions | items %}
32
- {{ scope }}: {{ level.label }}
33
- {% endfor %}
34
- {% endif %}
35
- {% with inputs = data.inputs | order_parameters(options.parameters_order) | filter_parameters(required=True) %}
36
- {% if inputs|length > 0 %}
37
- with:
38
- {% for input in inputs %}
39
- {{ input.name }}: 📝
40
- {% endfor %}
41
- {% endif %}
42
- {% endwith %}
43
- {% if options.signature_show_secrets %}
44
- {% with secrets = data.secrets | order_parameters(options.parameters_order) | filter_parameters(required=True) %}
45
- {% if secrets|length > 0 %}
46
- secrets:
47
- {% for secret in secrets %}
48
- {{ secret.name }}: 📝
49
- {% endfor %}
50
- {% endif %}
51
- {% endwith %}
52
- {% endif %}
53
- {% endfilter %}
27
+ {% with inputs = data.inputs | order_parameters(options.parameters_order) | filter_parameters(required=True), secrets = data.secrets | order_parameters(options.parameters_order) | filter_parameters(required=True) %}
28
+ <div class="annotate">
29
+ {% filter highlight(language="yaml", inline=False, linenums=False) %}
30
+ uses: {{ signature }}
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 %}
37
+ permissions:
38
+ {% for scope, level in data.permissions | items %}
39
+ {{ scope }}: {{ level.label }}
40
+ {% endfor %}
41
+ {% endif %}
42
+ {% endif %}
43
+ {% if inputs|length > 0 %}
44
+ with:
45
+ {% for input in inputs %}
46
+ {{ input.name }}: ({{ loop.index }})
47
+ {% endfor %}
48
+ {% endif %}
49
+ {% if options.signature_show_secrets %}
50
+ {% if secrets|length > 0 %}
51
+ secrets:
52
+ {% for secret in secrets %}
53
+ {{ secret.name }}: ({{ inputs|length + loop.index }})
54
+ {% endfor %}
55
+ {% endif %}
56
+ {% endif %}
57
+ {% endfilter %}
58
+ </div>
59
+ <ol>
60
+ {% for input in inputs %}
61
+ <li>{{ input.description | convert_markdown(options.heading_level) if input.description else "(no description)" }}</li>
62
+ {% endfor %}
63
+ {% for secret in secrets %}
64
+ <li>{{ secret.description | convert_markdown(options.heading_level) if secret.description else "(no description)" }}</li>
65
+ {% endfor %}
66
+ </ol>
67
+ {% endwith %}
54
68
  {% endif %}
55
69
  {% endblock signature %}
56
70