repo-review 0.12.0__tar.gz → 0.12.2__tar.gz

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.
Files changed (66) hide show
  1. {repo_review-0.12.0 → repo_review-0.12.2}/.github/workflows/ci.yml +6 -3
  2. {repo_review-0.12.0 → repo_review-0.12.2}/.pre-commit-config.yaml +5 -5
  3. {repo_review-0.12.0 → repo_review-0.12.2}/PKG-INFO +3 -2
  4. {repo_review-0.12.0 → repo_review-0.12.2}/README.md +1 -1
  5. {repo_review-0.12.0 → repo_review-0.12.2}/action.yml +1 -1
  6. {repo_review-0.12.0 → repo_review-0.12.2}/docs/index.html +5 -5
  7. {repo_review-0.12.0 → repo_review-0.12.2}/docs/webapp.js +193 -26
  8. {repo_review-0.12.0 → repo_review-0.12.2}/docs/webapp.md +1 -1
  9. {repo_review-0.12.0 → repo_review-0.12.2}/pyproject.toml +3 -1
  10. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/__main__.py +8 -1
  11. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_version.py +9 -4
  12. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/html.py +5 -8
  13. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/processor.py +21 -1
  14. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_package.py +2 -1
  15. {repo_review-0.12.0 → repo_review-0.12.2}/.devcontainer/devcontainer.json +0 -0
  16. {repo_review-0.12.0 → repo_review-0.12.2}/.git_archival.txt +0 -0
  17. {repo_review-0.12.0 → repo_review-0.12.2}/.gitattributes +0 -0
  18. {repo_review-0.12.0 → repo_review-0.12.2}/.github/CONTRIBUTING.md +0 -0
  19. {repo_review-0.12.0 → repo_review-0.12.2}/.github/ISSUE_TEMPLATE/new-issue.md +0 -0
  20. {repo_review-0.12.0 → repo_review-0.12.2}/.github/dependabot.yml +0 -0
  21. {repo_review-0.12.0 → repo_review-0.12.2}/.github/release.yml +0 -0
  22. {repo_review-0.12.0 → repo_review-0.12.2}/.github/workflows/cd.yml +0 -0
  23. {repo_review-0.12.0 → repo_review-0.12.2}/.gitignore +0 -0
  24. {repo_review-0.12.0 → repo_review-0.12.2}/.pre-commit-hooks.yaml +0 -0
  25. {repo_review-0.12.0 → repo_review-0.12.2}/.readthedocs.yaml +0 -0
  26. {repo_review-0.12.0 → repo_review-0.12.2}/LICENSE +0 -0
  27. {repo_review-0.12.0 → repo_review-0.12.2}/docs/.nojekyll +0 -0
  28. {repo_review-0.12.0 → repo_review-0.12.2}/docs/api/repo_review.resources.rst +1 -1
  29. {repo_review-0.12.0 → repo_review-0.12.2}/docs/api/repo_review.rst +9 -9
  30. {repo_review-0.12.0 → repo_review-0.12.2}/docs/changelog.md +0 -0
  31. {repo_review-0.12.0 → repo_review-0.12.2}/docs/checks.md +0 -0
  32. {repo_review-0.12.0 → repo_review-0.12.2}/docs/cli.md +0 -0
  33. {repo_review-0.12.0 → repo_review-0.12.2}/docs/conf.py +0 -0
  34. {repo_review-0.12.0 → repo_review-0.12.2}/docs/families.md +0 -0
  35. {repo_review-0.12.0 → repo_review-0.12.2}/docs/fixtures.md +0 -0
  36. {repo_review-0.12.0 → repo_review-0.12.2}/docs/index.md +0 -0
  37. {repo_review-0.12.0 → repo_review-0.12.2}/docs/intro.md +0 -0
  38. {repo_review-0.12.0 → repo_review-0.12.2}/docs/plugins.md +0 -0
  39. {repo_review-0.12.0 → repo_review-0.12.2}/docs/programmatic.md +0 -0
  40. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/__init__.py +0 -0
  41. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/__init__.py +0 -0
  42. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/importlib/__init__.py +0 -0
  43. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/importlib/resources/__init__.py +0 -0
  44. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/importlib/resources/abc.py +0 -0
  45. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/tomllib.py +0 -0
  46. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_compat/typing.py +0 -0
  47. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/_version.pyi +0 -0
  48. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/checks.py +0 -0
  49. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/families.py +0 -0
  50. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/fixtures.py +0 -0
  51. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/ghpath.py +0 -0
  52. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/py.typed +0 -0
  53. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/resources/__init__.py +0 -0
  54. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/resources/repo-review.schema.json +0 -0
  55. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/schema.py +0 -0
  56. {repo_review-0.12.0 → repo_review-0.12.2}/src/repo_review/testing.py +0 -0
  57. {repo_review-0.12.0 → repo_review-0.12.2}/tests/conftest.py +0 -0
  58. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_checks.py +0 -0
  59. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_cmd.py +0 -0
  60. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_depends.py +0 -0
  61. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_families.py +0 -0
  62. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_fixtures.py +0 -0
  63. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_multi.py +0 -0
  64. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_self.py +0 -0
  65. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_utilities/pyproject.py +0 -0
  66. {repo_review-0.12.0 → repo_review-0.12.2}/tests/test_utilities/pyproject.toml +0 -0
@@ -48,17 +48,20 @@ jobs:
48
48
  fetch-depth: 0
49
49
  persist-credentials: false
50
50
 
51
+ # Last one is activated
52
+ # yaml circular import issue on 3.14t on ubuntu
51
53
  - uses: actions/setup-python@v5
52
54
  with:
53
55
  python-version: |
54
56
  3.10
55
57
  3.11
56
58
  3.12
59
+ 3.14
57
60
  3.13
58
61
  allow-prereleases: true
59
62
 
60
63
  - name: Setup uv
61
- uses: yezz123/setup-uv@v4
64
+ uses: astral-sh/setup-uv@v6
62
65
 
63
66
  - name: Install hatch
64
67
  run: uv pip install --system hatch
@@ -88,7 +91,7 @@ jobs:
88
91
  persist-credentials: false
89
92
 
90
93
  - name: Setup uv
91
- uses: yezz123/setup-uv@v4
94
+ uses: astral-sh/setup-uv@v6
92
95
 
93
96
  - uses: actions/setup-python@v5
94
97
  with:
@@ -120,4 +123,4 @@ jobs:
120
123
  - name: Run repo-review action
121
124
  uses: ./
122
125
  with:
123
- plugins: sp-repo-review==2025.01.22
126
+ plugins: sp-repo-review==2025.05.02
@@ -11,14 +11,14 @@ repos:
11
11
  additional_dependencies: [black==24.*]
12
12
 
13
13
  - repo: https://github.com/astral-sh/ruff-pre-commit
14
- rev: "v0.9.4"
14
+ rev: "v0.11.8"
15
15
  hooks:
16
16
  - id: ruff
17
17
  args: ["--fix", "--show-fixes"]
18
18
  - id: ruff-format
19
19
 
20
20
  - repo: https://github.com/rbubley/mirrors-prettier
21
- rev: "v3.4.2"
21
+ rev: "v3.5.3"
22
22
  hooks:
23
23
  - id: prettier
24
24
  types_or: [yaml, markdown, html, css, scss, javascript, json]
@@ -45,7 +45,7 @@ repos:
45
45
  - id: rst-inline-touching-normal
46
46
 
47
47
  - repo: https://github.com/pre-commit/mirrors-mypy
48
- rev: v1.14.1
48
+ rev: v1.15.0
49
49
  hooks:
50
50
  - id: mypy
51
51
  files: (src|web|tests)
@@ -79,12 +79,12 @@ repos:
79
79
  exclude: .pre-commit-config.yaml
80
80
 
81
81
  - repo: https://github.com/henryiii/validate-pyproject-schema-store
82
- rev: 2025.02.03
82
+ rev: 2025.04.28
83
83
  hooks:
84
84
  - id: validate-pyproject
85
85
 
86
86
  - repo: https://github.com/python-jsonschema/check-jsonschema
87
- rev: 0.31.1
87
+ rev: 0.33.0
88
88
  hooks:
89
89
  - id: check-dependabot
90
90
  - id: check-github-workflows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: repo_review
3
- Version: 0.12.0
3
+ Version: 0.12.2
4
4
  Summary: Framework that can run checks on repos
5
5
  Project-URL: Changelog, https://github.com/scientific-python/repo-review/releases
6
6
  Project-URL: Demo, https://scientific-python.github.io/repo-review
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
25
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
27
  Classifier: Topic :: Software Development :: Quality Assurance
27
28
  Classifier: Typing :: Typed
@@ -128,7 +129,7 @@ repos have some [pre-commit][] check.
128
129
  ## Development of repo-review and plugins
129
130
 
130
131
  This project is intended to be fun and easy to develop and design checks for -
131
- it requires and uses Python 3.10, and uses a lot of the new features in 3.9 and
132
+ it requires and uses Python 3.10+, and uses a lot of the new features in 3.9 and
132
133
  3.10. It's maybe not entirely conventional, but it enables very simple plugin
133
134
  development. It works locally, remotely, and in WebAssembly (using
134
135
  [Pyodide][]). [See the docs][writing-a-plugin].
@@ -90,7 +90,7 @@ repos have some [pre-commit][] check.
90
90
  ## Development of repo-review and plugins
91
91
 
92
92
  This project is intended to be fun and easy to develop and design checks for -
93
- it requires and uses Python 3.10, and uses a lot of the new features in 3.9 and
93
+ it requires and uses Python 3.10+, and uses a lot of the new features in 3.9 and
94
94
  3.10. It's maybe not entirely conventional, but it enables very simple plugin
95
95
  development. It works locally, remotely, and in WebAssembly (using
96
96
  [Pyodide][]). [See the docs][writing-a-plugin].
@@ -26,7 +26,7 @@ runs:
26
26
  - uses: actions/setup-python@v5
27
27
  id: python
28
28
  with:
29
- python-version: "3.12"
29
+ python-version: "3.13"
30
30
  update-environment: false
31
31
 
32
32
  - name: Install repo-review and plugins
@@ -6,7 +6,7 @@
6
6
  content="initial-scale=1, width=device-width"
7
7
  />
8
8
  <script
9
- src="https://cdn.jsdelivr.net/pyodide/v0.27.1/full/pyodide.js"
9
+ src="https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js"
10
10
  crossorigin
11
11
  ></script>
12
12
  <!-- Production -->
@@ -64,10 +64,10 @@
64
64
  <App
65
65
  header={true}
66
66
  deps={[
67
- "repo-review~=0.11.3",
68
- "sp-repo-review==2025.01.22",
69
- "validate-pyproject-schema-store==2025.01.20",
70
- "validate-pyproject[all]~=0.22.0",
67
+ "repo-review~=0.12.1",
68
+ "sp-repo-review==2025.05.02",
69
+ "validate-pyproject[all]~=0.24.0",
70
+ "validate-pyproject-schema-store==2025.04.28",
71
71
  ]}
72
72
  />,
73
73
  );
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_MSG =
2
- "Enter a GitHub repo and branch to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
2
+ "Enter a GitHub repo and branch/tag to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
3
3
 
4
4
  const urlParams = new URLSearchParams(window.location.search);
5
5
  const baseurl = window.location.pathname;
@@ -93,7 +93,7 @@ function Results(props) {
93
93
  variant="body2"
94
94
  color="text.disabled"
95
95
  >
96
- {" [skipped]"}
96
+ {` [skipped] ${result.skip_reason}`}
97
97
  </MaterialUI.Typography>
98
98
  );
99
99
  const msg = (
@@ -154,6 +154,39 @@ function Results(props) {
154
154
  );
155
155
  }
156
156
 
157
+ async function fetchRepoRefs(repo) {
158
+ if (!repo) return { branches: [], tags: [] };
159
+ try {
160
+ // Fetch both branches and tags from GitHub API
161
+ const [branchesResponse, tagsResponse] = await Promise.all([
162
+ fetch(`https://api.github.com/repos/${repo}/branches`),
163
+ fetch(`https://api.github.com/repos/${repo}/tags`),
164
+ ]);
165
+
166
+ if (!branchesResponse.ok || !tagsResponse.ok) {
167
+ console.error("Error fetching repo data");
168
+ return { branches: [], tags: [] };
169
+ }
170
+
171
+ const branches = await branchesResponse.json();
172
+ const tags = await tagsResponse.json();
173
+
174
+ return {
175
+ branches: branches.map((branch) => ({
176
+ name: branch.name,
177
+ type: "branch",
178
+ })),
179
+ tags: tags.map((tag) => ({
180
+ name: tag.name,
181
+ type: "tag",
182
+ })),
183
+ };
184
+ } catch (error) {
185
+ console.error("Error fetching repo references:", error);
186
+ return { branches: [], tags: [] };
187
+ }
188
+ }
189
+
157
190
  async function prepare_pyodide(deps) {
158
191
  const deps_str = deps.map((i) => `"${i}"`).join(", ");
159
192
  const pyodide = await loadPyodide();
@@ -196,27 +229,58 @@ class App extends React.Component {
196
229
  this.state = {
197
230
  results: [],
198
231
  repo: urlParams.get("repo") || "",
199
- branch: urlParams.get("branch") || "",
232
+ ref: urlParams.get("ref") || "",
233
+ refType: urlParams.get("refType") || "branch",
234
+ refs: { branches: [], tags: [] },
200
235
  msg: `<p>${DEFAULT_MSG}</p><h4>Packages:</h4> ${deps_str}`,
201
236
  progress: false,
237
+ loadingRefs: false,
202
238
  err_msg: "",
239
+ skip_reason: "",
203
240
  url: "",
204
241
  };
205
242
  this.pyodide_promise = prepare_pyodide(props.deps);
243
+ this.refInputDebounce = null;
244
+ }
245
+
246
+ async fetchRepoReferences(repo) {
247
+ if (!repo) return;
248
+
249
+ this.setState({ loadingRefs: true });
250
+ const refs = await fetchRepoRefs(repo);
251
+ this.setState({
252
+ refs: refs,
253
+ loadingRefs: false,
254
+ });
255
+ }
256
+
257
+ handleRepoChange(repo) {
258
+ this.setState({ repo });
259
+
260
+ // debounce the API call to avoid too many requests
261
+ clearTimeout(this.refInputDebounce);
262
+ this.refInputDebounce = setTimeout(() => {
263
+ this.fetchRepoReferences(repo);
264
+ }, 500);
265
+ }
266
+
267
+ handleRefChange(ref, refType) {
268
+ this.setState({ ref, refType });
206
269
  }
207
270
 
208
271
  handleCompute() {
209
- if (!this.state.repo || !this.state.branch) {
272
+ if (!this.state.repo || !this.state.ref) {
210
273
  this.setState({ results: [], msg: DEFAULT_MSG });
211
274
  window.history.replaceState(null, "", baseurl);
212
275
  alert(
213
- `Please enter a repo (${this.state.repo}) and branch (${this.state.branch})`,
276
+ `Please enter a repo (${this.state.repo}) and branch/tag (${this.state.ref})`,
214
277
  );
215
278
  return;
216
279
  }
217
280
  const local_params = new URLSearchParams({
218
281
  repo: this.state.repo,
219
- branch: this.state.branch,
282
+ ref: this.state.ref,
283
+ refType: this.state.refType,
220
284
  });
221
285
  window.history.replaceState(null, "", `${baseurl}?${local_params}`);
222
286
  this.setState({
@@ -231,22 +295,24 @@ class App extends React.Component {
231
295
  families_checks = pyodide.runPython(`
232
296
  from repo_review.processor import process, md_as_html
233
297
  from repo_review.ghpath import GHPath
298
+ from dataclasses import replace
234
299
 
235
- package = GHPath(repo="${state.repo}", branch="${state.branch}")
236
- result = process(package)
300
+ package = GHPath(repo="${state.repo}", branch="${state.ref}")
301
+ families, checks = process(package)
237
302
 
238
- for v in result[0].values():
303
+ for v in families.values():
239
304
  if v.get("description"):
240
305
  v["description"] = md_as_html(v["description"])
306
+ checks = [res.md_as_html() for res in checks]
241
307
 
242
- result
308
+ (families, checks)
243
309
  `);
244
310
  } catch (e) {
245
311
  if (e.message.includes("KeyError: 'tree'")) {
246
312
  this.setState({
247
313
  msg: DEFAULT_MSG,
248
314
  progress: false,
249
- err_msg: "Invalid repository or branch. Please try again.",
315
+ err_msg: "Invalid repository or branch/tag. Please try again.",
250
316
  });
251
317
  return;
252
318
  }
@@ -276,15 +342,16 @@ class App extends React.Component {
276
342
  name: val.name.toString(),
277
343
  description: val.description.toString(),
278
344
  state: val.result,
279
- err_msg: val.err_as_html().toString(),
345
+ err_msg: val.err_msg.toString(),
280
346
  url: val.url.toString(),
347
+ skip_reason: val.skip_reason.toString(),
281
348
  });
282
349
  }
283
350
 
284
351
  this.setState({
285
352
  results: results,
286
353
  families: families,
287
- msg: `Results for ${state.repo}@${state.branch}`,
354
+ msg: `Results for ${state.repo}@${state.ref} (${state.refType})`,
288
355
  progress: false,
289
356
  err_msg: "",
290
357
  url: "",
@@ -296,13 +363,78 @@ class App extends React.Component {
296
363
  }
297
364
 
298
365
  componentDidMount() {
299
- if (urlParams.get("repo") && urlParams.get("branch")) {
300
- this.handleCompute();
366
+ if (urlParams.get("repo")) {
367
+ this.fetchRepoReferences(urlParams.get("repo"));
368
+
369
+ if (urlParams.get("ref")) {
370
+ this.handleCompute();
371
+ }
301
372
  }
302
373
  }
303
374
 
304
375
  render() {
305
- const common_branches = ["main", "master", "develop", "stable"];
376
+ const priorityBranches = ["HEAD", "main", "master", "develop", "stable"];
377
+ const branchMap = new Map(
378
+ this.state.refs.branches.map((branch) => [branch.name, branch]),
379
+ );
380
+
381
+ let availableOptions = [];
382
+
383
+ // If no repo is entered or API hasn't returned any branches/tags yet,
384
+ // show all five priority branches.
385
+ if (
386
+ this.state.repo === "" ||
387
+ (this.state.refs.branches.length === 0 &&
388
+ this.state.refs.tags.length === 0)
389
+ ) {
390
+ availableOptions = [
391
+ { label: "HEAD (default branch)", value: "HEAD", type: "branch" },
392
+ { label: "main (branch)", value: "main", type: "branch" },
393
+ { label: "master (branch)", value: "master", type: "branch" },
394
+ { label: "develop (branch)", value: "develop", type: "branch" },
395
+ { label: "stable (branch)", value: "stable", type: "branch" },
396
+ ];
397
+ } else {
398
+ const prioritizedBranches = [
399
+ { label: "HEAD (default branch)", value: "HEAD", type: "branch" },
400
+ ];
401
+
402
+ priorityBranches.slice(1).forEach((branchName) => {
403
+ if (branchMap.has(branchName)) {
404
+ prioritizedBranches.push({
405
+ label: `${branchName} (branch)`,
406
+ value: branchName,
407
+ type: "branch",
408
+ });
409
+ // Remove from map so it doesn't get added twice.
410
+ branchMap.delete(branchName);
411
+ }
412
+ });
413
+
414
+ const otherBranches = [];
415
+ branchMap.forEach((branch) => {
416
+ otherBranches.push({
417
+ label: `${branch.name} (branch)`,
418
+ value: branch.name,
419
+ type: "branch",
420
+ });
421
+ });
422
+ otherBranches.sort((a, b) => a.value.localeCompare(b.value));
423
+
424
+ const tagOptions = this.state.refs.tags.map((tag) => ({
425
+ label: `${tag.name} (tag)`,
426
+ value: tag.name,
427
+ type: "tag",
428
+ }));
429
+ tagOptions.sort((a, b) => a.value.localeCompare(b.value));
430
+
431
+ availableOptions = [
432
+ ...prioritizedBranches,
433
+ ...otherBranches,
434
+ ...tagOptions,
435
+ ];
436
+ }
437
+
306
438
  return (
307
439
  <MyThemeProvider>
308
440
  <MaterialUI.CssBaseline />
@@ -322,29 +454,64 @@ class App extends React.Component {
322
454
  autoFocus={true}
323
455
  onKeyDown={(e) => {
324
456
  if (e.keyCode === 13)
325
- document.getElementById("branch-select").focus();
457
+ document.getElementById("ref-select").focus();
326
458
  }}
327
- onInput={(e) => this.setState({ repo: e.target.value })}
459
+ onInput={(e) => this.handleRepoChange(e.target.value)}
328
460
  defaultValue={urlParams.get("repo")}
329
461
  sx={{ flexGrow: 3 }}
330
462
  />
331
463
  <MaterialUI.Autocomplete
332
464
  disablePortal
333
- id="branch-select"
334
- options={common_branches}
465
+ id="ref-select"
466
+ options={availableOptions}
467
+ loading={this.state.loadingRefs}
335
468
  freeSolo={true}
336
469
  onKeyDown={(e) => {
337
470
  if (e.keyCode === 13) this.handleCompute();
338
471
  }}
339
- onInputChange={(e, value) => this.setState({ branch: value })}
340
- defaultValue={urlParams.get("branch")}
472
+ getOptionLabel={(option) =>
473
+ typeof option === "string" ? option : option.label
474
+ }
475
+ renderOption={(props, option) => (
476
+ <li {...props}>{option.label}</li>
477
+ )}
478
+ onInputChange={(e, value) => {
479
+ // If the user enters free text, treat it as a branch
480
+ if (typeof value === "string") {
481
+ this.handleRefChange(value, "branch");
482
+ }
483
+ }}
484
+ onChange={(e, option) => {
485
+ if (option) {
486
+ if (typeof option === "object") {
487
+ this.handleRefChange(option.value, option.type);
488
+ } else {
489
+ this.handleRefChange(option, "branch");
490
+ }
491
+ }
492
+ }}
493
+ defaultValue={urlParams.get("ref")}
341
494
  renderInput={(params) => (
342
495
  <MaterialUI.TextField
343
496
  {...params}
344
- label="Branch"
497
+ label="Branch/Tag"
345
498
  variant="outlined"
346
- helperText="e.g. main"
347
- sx={{ flexGrow: 2, minWidth: 130 }}
499
+ helperText="e.g. HEAD, main, or v1.0.0"
500
+ sx={{ flexGrow: 2, minWidth: 200 }}
501
+ InputProps={{
502
+ ...params.InputProps,
503
+ endAdornment: (
504
+ <React.Fragment>
505
+ {this.state.loadingRefs ? (
506
+ <MaterialUI.CircularProgress
507
+ color="inherit"
508
+ size={20}
509
+ />
510
+ ) : null}
511
+ {params.InputProps.endAdornment}
512
+ </React.Fragment>
513
+ ),
514
+ }}
348
515
  />
349
516
  )}
350
517
  />
@@ -354,7 +521,7 @@ class App extends React.Component {
354
521
  variant="contained"
355
522
  size="large"
356
523
  disabled={
357
- this.state.progress || !this.state.repo || !this.state.branch
524
+ this.state.progress || !this.state.repo || !this.state.ref
358
525
  }
359
526
  >
360
527
  <MaterialUI.Icon>start</MaterialUI.Icon>
@@ -20,7 +20,7 @@ You can also use the `html` output and write your own webapp. You need to provid
20
20
 
21
21
  ```html
22
22
  <script
23
- src="https://cdn.jsdelivr.net/pyodide/v0.25.1/full/pyodide.js"
23
+ src="https://cdn.jsdelivr.net/pyodide/v0.27.6/full/pyodide.js"
24
24
  crossorigin
25
25
  ></script>
26
26
  ```
@@ -24,6 +24,7 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  "Programming Language :: Python :: 3.12",
26
26
  "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
27
28
  "Programming Language :: Python",
28
29
  "Topic :: Software Development :: Quality Assurance",
29
30
  "Topic :: Software Development :: Libraries :: Python Modules",
@@ -148,7 +149,7 @@ skip-install = true
148
149
  scripts.serve = "cd docs && echo 'Serving on http://localhost:8080' && python -m http.server 8080"
149
150
 
150
151
  [[tool.hatch.envs.hatch-test.matrix]]
151
- python = ["3.13", "3.12", "3.11", "3.10"]
152
+ python = ["3.14", "3.13", "3.12", "3.11", "3.10"]
152
153
 
153
154
 
154
155
  [tool.pytest.ini_options]
@@ -201,6 +202,7 @@ messages_control.disable = [
201
202
  "used-before-assignment", # False positive on conditional import
202
203
  "unnecessary-ellipsis", # Not correct for typing
203
204
  "import-outside-toplevel", # better handled elsewhere
205
+ "duplicate-code", # Triggers incorrectly
204
206
  ]
205
207
 
206
208
 
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import sys
8
8
  import typing
9
+ import urllib.error
9
10
  from collections.abc import Mapping, Sequence
10
11
  from pathlib import Path
11
12
  from typing import Any, Literal
@@ -17,6 +18,7 @@ if typing.TYPE_CHECKING:
17
18
  else:
18
19
  import rich_click as click
19
20
 
21
+ import rich
20
22
  import rich.console
21
23
  import rich.markdown
22
24
  import rich.syntax
@@ -265,7 +267,12 @@ def _remote_path_processor(package: Path) -> Path | GHPath:
265
267
  msg = "online repo must be of the form 'gh:org/repo@branch[:path]' (:branch missing)"
266
268
  raise click.BadParameter(msg)
267
269
  org_repo, branch = org_repo_branch.split("@", maxsplit=1)
268
- return GHPath(repo=org_repo, branch=branch, path=p[0] if p else "")
270
+ try:
271
+ return GHPath(repo=org_repo, branch=branch, path=p[0] if p else "")
272
+ except urllib.error.HTTPError as e:
273
+ rich.print(f"[red][bold]Error[/bold] accessing {e.url}", file=sys.stderr)
274
+ rich.print(f"[red]{e}", file=sys.stderr)
275
+ raise SystemExit(1) from None
269
276
 
270
277
 
271
278
  @click.command(context_settings={"help_option_names": ["-h", "--help"]})
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.12.0'
16
- __version_tuple__ = version_tuple = (0, 12, 0)
20
+ __version__ = version = '0.12.2'
21
+ __version_tuple__ = version_tuple = (0, 12, 2)
@@ -6,10 +6,8 @@ import io
6
6
  import typing
7
7
  from collections.abc import Mapping, Sequence
8
8
 
9
- import markdown_it
10
-
11
9
  from .families import Family, get_family_description, get_family_name
12
- from .processor import Result
10
+ from .processor import Result, md_as_html
13
11
 
14
12
  if typing.TYPE_CHECKING:
15
13
  from .__main__ import Status
@@ -37,16 +35,15 @@ def to_html(
37
35
  """
38
36
  out = io.StringIO()
39
37
  print = functools.partial(builtins.print, file=out)
40
- md = markdown_it.MarkdownIt()
41
38
 
42
39
  for family in families:
43
40
  family_name = get_family_name(families, family)
44
41
  family_description = get_family_description(families, family)
45
- family_results = [r for r in processed if r.family == family]
42
+ family_results = [r.md_as_html() for r in processed if r.family == family]
46
43
  if family_results or family_description:
47
44
  print(f"<h3>{family_name}</h3>")
48
45
  if family_description:
49
- print(md.render(family_description).strip())
46
+ print("<p>", md_as_html(family_description), "</p>")
50
47
  if family_results:
51
48
  print("<table>")
52
49
  print(
@@ -82,7 +79,7 @@ def to_html(
82
79
  if result.skip_reason:
83
80
  description += (
84
81
  f'<br/><span style="color:DarkKhaki;"><b>Skipped:</b> '
85
- f"<em>{md.render(result.skip_reason)}</em></span>"
82
+ f"<em>{result.skip_reason}</em></span>"
86
83
  )
87
84
  print(f'<tr style="color: {color};">')
88
85
  print(f'<td><span role="img" aria-label="{result_txt}">{icon}</span></td>')
@@ -93,7 +90,7 @@ def to_html(
93
90
  print("<td>")
94
91
  print(description)
95
92
  print("<br/>")
96
- print(md.render(result.err_msg))
93
+ print(result.err_msg)
97
94
  print("</td>")
98
95
  print("</tr>")
99
96
  if family_results:
@@ -3,11 +3,12 @@ from __future__ import annotations
3
3
  import copy
4
4
  import dataclasses
5
5
  import graphlib
6
+ import sys
6
7
  import textwrap
7
8
  import typing
8
9
  import warnings
9
10
  from collections.abc import Mapping, Set
10
- from typing import Any, TypeVar
11
+ from typing import TYPE_CHECKING, Any, TypeVar
11
12
 
12
13
  import markdown_it
13
14
 
@@ -24,6 +25,12 @@ from .families import Family, collect_families
24
25
  from .fixtures import apply_fixtures, collect_fixtures, compute_fixtures, pyproject
25
26
  from .ghpath import EmptyTraversable
26
27
 
28
+ if TYPE_CHECKING:
29
+ if sys.version_info >= (3, 11):
30
+ from typing import Self
31
+ else:
32
+ from typing_extensions import Self
33
+
27
34
  __all__ = [
28
35
  "CollectionReturn",
29
36
  "ProcessReturn",
@@ -84,9 +91,22 @@ class Result:
84
91
  def err_as_html(self) -> str:
85
92
  """
86
93
  Produces HTML from the error message, assuming it is in markdown.
94
+ Deprecated, use :meth:`md_as_html` directly instead.
87
95
  """
88
96
  return md_as_html(self.err_msg)
89
97
 
98
+ def md_as_html(self) -> Self:
99
+ """
100
+ Process fields that are assumed to be markdown.
101
+
102
+ .. versionadded:: 0.12.1
103
+ """
104
+ return dataclasses.replace(
105
+ self,
106
+ err_msg=md_as_html(self.err_msg),
107
+ skip_reason=md_as_html(self.skip_reason),
108
+ )
109
+
90
110
 
91
111
  class ProcessReturn(typing.NamedTuple):
92
112
  """
@@ -25,7 +25,8 @@ def test_local():
25
25
  assert "BSD License" in results.families["general"]["description"]
26
26
  assert "[tool.repo-review]" in results.families["validate-pyproject"]["description"]
27
27
  for result in results.results:
28
- assert result.result
28
+ if result.result is not None:
29
+ assert result.result
29
30
 
30
31
 
31
32
  def test_broken_validate_pyproject(tmp_path: Path) -> None:
File without changes
File without changes
@@ -3,5 +3,5 @@ repo\_review.resources package
3
3
 
4
4
  .. automodule:: repo_review.resources
5
5
  :members:
6
- :undoc-members:
7
6
  :show-inheritance:
7
+ :undoc-members:
@@ -3,8 +3,8 @@ repo\_review package
3
3
 
4
4
  .. automodule:: repo_review
5
5
  :members:
6
- :undoc-members:
7
6
  :show-inheritance:
7
+ :undoc-members:
8
8
 
9
9
  Subpackages
10
10
  -----------
@@ -22,61 +22,61 @@ repo\_review.checks module
22
22
 
23
23
  .. automodule:: repo_review.checks
24
24
  :members:
25
- :undoc-members:
26
25
  :show-inheritance:
26
+ :undoc-members:
27
27
 
28
28
  repo\_review.families module
29
29
  ----------------------------
30
30
 
31
31
  .. automodule:: repo_review.families
32
32
  :members:
33
- :undoc-members:
34
33
  :show-inheritance:
34
+ :undoc-members:
35
35
 
36
36
  repo\_review.fixtures module
37
37
  ----------------------------
38
38
 
39
39
  .. automodule:: repo_review.fixtures
40
40
  :members:
41
- :undoc-members:
42
41
  :show-inheritance:
42
+ :undoc-members:
43
43
 
44
44
  repo\_review.ghpath module
45
45
  --------------------------
46
46
 
47
47
  .. automodule:: repo_review.ghpath
48
48
  :members:
49
- :undoc-members:
50
49
  :show-inheritance:
50
+ :undoc-members:
51
51
 
52
52
  repo\_review.html module
53
53
  ------------------------
54
54
 
55
55
  .. automodule:: repo_review.html
56
56
  :members:
57
- :undoc-members:
58
57
  :show-inheritance:
58
+ :undoc-members:
59
59
 
60
60
  repo\_review.processor module
61
61
  -----------------------------
62
62
 
63
63
  .. automodule:: repo_review.processor
64
64
  :members:
65
- :undoc-members:
66
65
  :show-inheritance:
66
+ :undoc-members:
67
67
 
68
68
  repo\_review.schema module
69
69
  --------------------------
70
70
 
71
71
  .. automodule:: repo_review.schema
72
72
  :members:
73
- :undoc-members:
74
73
  :show-inheritance:
74
+ :undoc-members:
75
75
 
76
76
  repo\_review.testing module
77
77
  ---------------------------
78
78
 
79
79
  .. automodule:: repo_review.testing
80
80
  :members:
81
- :undoc-members:
82
81
  :show-inheritance:
82
+ :undoc-members:
File without changes
File without changes
File without changes
File without changes