devboard 0.2.0__tar.gz → 0.3.0__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 (87) hide show
  1. devboard-0.3.0/CHANGELOG.md +64 -0
  2. {devboard-0.2.0 → devboard-0.3.0}/CONTRIBUTING.md +1 -0
  3. {devboard-0.2.0 → devboard-0.3.0}/PKG-INFO +4 -4
  4. {devboard-0.2.0 → devboard-0.3.0}/README.md +1 -1
  5. {devboard-0.2.0 → devboard-0.3.0}/config/pytest.ini +2 -0
  6. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/commit.py +5 -4
  7. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/commit_pull.py +7 -6
  8. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/commit_pull_push.py +9 -8
  9. devboard-0.3.0/docs/examples/columns/commit_pull_push_release.py +49 -0
  10. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/notify.py +25 -14
  11. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/pull.py +5 -4
  12. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/push.py +5 -4
  13. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/columns/release.py +5 -4
  14. devboard-0.3.0/docs/examples/screenshot.py +75 -0
  15. {devboard-0.2.0 → devboard-0.3.0}/docs/index.md +7 -4
  16. {devboard-0.2.0 → devboard-0.3.0}/docs/tutorial.md +214 -125
  17. {devboard-0.2.0 → devboard-0.3.0}/pyproject.toml +4 -3
  18. {devboard-0.2.0 → devboard-0.3.0}/scripts/gen_credits.py +1 -1
  19. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/__init__.py +9 -3
  20. devboard-0.3.0/src/devboard/_internal/app.py +431 -0
  21. devboard-0.3.0/src/devboard/_internal/board.py +441 -0
  22. devboard-0.3.0/src/devboard/_internal/cache.py +148 -0
  23. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/cli.py +6 -1
  24. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/datatable.py +106 -20
  25. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/default_board.py +97 -82
  26. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/devboard.tcss +13 -1
  27. devboard-0.3.0/src/devboard/_internal/loader.py +140 -0
  28. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/modal.py +9 -10
  29. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/projects.py +38 -6
  30. devboard-0.3.0/tests/__snapshots__/test_snapshots/test_collapsed_column_layout.svg +156 -0
  31. devboard-0.3.0/tests/__snapshots__/test_snapshots/test_populated_board_layout.svg +157 -0
  32. devboard-0.3.0/tests/snapshot_app.py +88 -0
  33. devboard-0.3.0/tests/test_actions.py +128 -0
  34. {devboard-0.2.0 → devboard-0.3.0}/tests/test_api.py +1 -1
  35. devboard-0.3.0/tests/test_app_bindings.py +70 -0
  36. devboard-0.3.0/tests/test_cache.py +461 -0
  37. {devboard-0.2.0 → devboard-0.3.0}/tests/test_cli.py +21 -4
  38. devboard-0.3.0/tests/test_column.py +176 -0
  39. devboard-0.3.0/tests/test_items.py +252 -0
  40. devboard-0.3.0/tests/test_loader.py +126 -0
  41. {devboard-0.2.0 → devboard-0.3.0}/tests/test_modal.py +45 -1
  42. devboard-0.3.0/tests/test_progress.py +291 -0
  43. devboard-0.3.0/tests/test_projects.py +83 -0
  44. devboard-0.3.0/tests/test_scan.py +142 -0
  45. devboard-0.3.0/tests/test_snapshots.py +73 -0
  46. {devboard-0.2.0 → devboard-0.3.0}/zensical.toml +2 -0
  47. devboard-0.2.0/CHANGELOG.md +0 -35
  48. devboard-0.2.0/docs/examples/columns/commit_pull_push_release.py +0 -40
  49. devboard-0.2.0/docs/examples/screenshot.py +0 -51
  50. devboard-0.2.0/src/devboard/_internal/app.py +0 -320
  51. devboard-0.2.0/src/devboard/_internal/board.py +0 -158
  52. devboard-0.2.0/src/devboard/_internal/cache.py +0 -87
  53. {devboard-0.2.0 → devboard-0.3.0}/AI_POLICY.md +0 -0
  54. {devboard-0.2.0 → devboard-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  55. {devboard-0.2.0 → devboard-0.3.0}/LICENSE +0 -0
  56. {devboard-0.2.0 → devboard-0.3.0}/config/coverage.ini +0 -0
  57. {devboard-0.2.0 → devboard-0.3.0}/config/git-changelog.toml +0 -0
  58. {devboard-0.2.0 → devboard-0.3.0}/config/ruff.toml +0 -0
  59. {devboard-0.2.0 → devboard-0.3.0}/config/ty.toml +0 -0
  60. {devboard-0.2.0 → devboard-0.3.0}/config/vscode/launch.json +0 -0
  61. {devboard-0.2.0 → devboard-0.3.0}/config/vscode/settings.json +0 -0
  62. {devboard-0.2.0 → devboard-0.3.0}/config/vscode/tasks.json +0 -0
  63. {devboard-0.2.0 → devboard-0.3.0}/config/zed/debug.json +0 -0
  64. {devboard-0.2.0 → devboard-0.3.0}/config/zed/settings.json +0 -0
  65. {devboard-0.2.0 → devboard-0.3.0}/config/zed/tasks.json +0 -0
  66. {devboard-0.2.0 → devboard-0.3.0}/docs/.overrides/main.html +0 -0
  67. {devboard-0.2.0 → devboard-0.3.0}/docs/.overrides/partials/comments.html +0 -0
  68. {devboard-0.2.0 → devboard-0.3.0}/docs/changelog.md +0 -0
  69. {devboard-0.2.0 → devboard-0.3.0}/docs/code_of_conduct.md +0 -0
  70. {devboard-0.2.0 → devboard-0.3.0}/docs/contributing.md +0 -0
  71. {devboard-0.2.0 → devboard-0.3.0}/docs/credits.md +0 -0
  72. {devboard-0.2.0 → devboard-0.3.0}/docs/css/apidocs.css +0 -0
  73. {devboard-0.2.0 → devboard-0.3.0}/docs/examples/repositories.py +0 -0
  74. {devboard-0.2.0 → devboard-0.3.0}/docs/js/feedback.js +0 -0
  75. {devboard-0.2.0 → devboard-0.3.0}/docs/license.md +0 -0
  76. {devboard-0.2.0 → devboard-0.3.0}/docs/reference/api.md +0 -0
  77. {devboard-0.2.0 → devboard-0.3.0}/duties.py +0 -0
  78. {devboard-0.2.0 → devboard-0.3.0}/scripts/get_version.py +0 -0
  79. {devboard-0.2.0 → devboard-0.3.0}/scripts/make +0 -0
  80. {devboard-0.2.0 → devboard-0.3.0}/scripts/make.py +0 -0
  81. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/__main__.py +0 -0
  82. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/__init__.py +0 -0
  83. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/debug.py +0 -0
  84. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/_internal/notifications.py +0 -0
  85. {devboard-0.2.0 → devboard-0.3.0}/src/devboard/py.typed +0 -0
  86. {devboard-0.2.0 → devboard-0.3.0}/tests/__init__.py +0 -0
  87. {devboard-0.2.0 → devboard-0.3.0}/tests/conftest.py +0 -0
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
+
8
+ <!-- insertion marker -->
9
+ ## [0.3.0](https://github.com/pawamoy/devboard/releases/tag/0.3.0) - 2026-09-23
10
+
11
+ <small>[Compare with 0.2.0](https://github.com/pawamoy/devboard/compare/0.2.0...0.3.0)</small>
12
+
13
+ ### Features
14
+
15
+ - Render Rich content in modals ([fb2393e](https://github.com/pawamoy/devboard/commit/fb2393e75edc21b71e4684b82394f4516785499c) by Timothée Mazzucotelli).
16
+ - Let columns report status bar progress ([08a6943](https://github.com/pawamoy/devboard/commit/08a6943c6ab66bde2699c116e22a1ab025c11f7a) by Timothée Mazzucotelli).
17
+ - Add maximize/unmaximize keybinding to all columns ([8fd99d4](https://github.com/pawamoy/devboard/commit/8fd99d48d715189c5679cb97edb07806964738d5) by Timothée Mazzucotelli). [Issue-2](https://github.com/pawamoy/devboard/issues/2)
18
+ - Allow collapsing/expanding column with keybinding ([51160e1](https://github.com/pawamoy/devboard/commit/51160e152a9bcbcc3a77ede5ee2878252b7a6946) by Timothée Mazzucotelli).
19
+ - Add progress bar (scanning/fetching) ([34290ea](https://github.com/pawamoy/devboard/commit/34290ea4b1bfb04ac9560b47d5fbd0244034e5a6) by Timothée Mazzucotelli).
20
+
21
+ ### Bug Fixes
22
+
23
+ - Update cache after each (batch) row operation ([25b1370](https://github.com/pawamoy/devboard/commit/25b13703240c94107146d1de2fcff7a20f16d6c3) by Timothée Mazzucotelli).
24
+ - Emptied columns are collapsed ([6e49906](https://github.com/pawamoy/devboard/commit/6e49906ba1dcdb3cedc04d92909c838c4b9e1627) by Timothée Mazzucotelli).
25
+ - Always encode in UTF8 ([fe0f17f](https://github.com/pawamoy/devboard/commit/fe0f17f8016b55c8edff96dbcba2d12975987511) by Timothée Mazzucotelli).
26
+ - Support restoring cache with moved columns ([a014e82](https://github.com/pawamoy/devboard/commit/a014e82f49b4d3f3d47a0f5bf4d5edda1eacb5b0) by Timothée Mazzucotelli).
27
+ - Identify projects by their paths, to avoid redundant work ([2708a80](https://github.com/pawamoy/devboard/commit/2708a80282ef5a37b9a1b62efa081542bd1ef39f) by Timothée Mazzucotelli).
28
+ - Save to cache after each reload ([091a229](https://github.com/pawamoy/devboard/commit/091a2296ac968ba92ef562b50eb4b67db4165114) by Timothée Mazzucotelli).
29
+
30
+ ### Code Refactoring
31
+
32
+ - Let each board declare its own bindings ([cf7a6a9](https://github.com/pawamoy/devboard/commit/cf7a6a928a6fb50bc50d4caa5d010117305e8eae) by Timothée Mazzucotelli).
33
+ - Declare keybindings with Textual actions ([5b1ec54](https://github.com/pawamoy/devboard/commit/5b1ec548daf2fc3c5db6171f35a1c680fa91b429) by Timothée Mazzucotelli).
34
+ - Generalize refresh/force-refresh ([342ef55](https://github.com/pawamoy/devboard/commit/342ef552ccbf3e151750ef44800fe68d0f6f8e20) by Timothée Mazzucotelli).
35
+ - Make columns generic ([6532042](https://github.com/pawamoy/devboard/commit/6532042da615c7f9c54ffa1cd9f713db552c77c3) by Timothée Mazzucotelli).
36
+ - Restructure app to separate concerns ([c11b897](https://github.com/pawamoy/devboard/commit/c11b897a3a5e45aad0e78b9c7a6749e0bbed7e91) by Timothée Mazzucotelli).
37
+
38
+ ## [0.2.0](https://github.com/pawamoy/devboard/releases/tag/0.2.0) - 2026-09-10
39
+
40
+ <small>[Compare with 0.1.0](https://github.com/pawamoy/devboard/compare/0.1.0...0.2.0)</small>
41
+
42
+ The project is update to support latest version of Textual.
43
+
44
+ ### Bug Fixes
45
+
46
+ - Prevent event propagation from modal ([e61eadd](https://github.com/pawamoy/devboard/commit/e61eaddaca58f91c02150c2f11465fcb82d576d1) by Timothée Mazzucotelli).
47
+ - Fix script entrypoint ([acb054d](https://github.com/pawamoy/devboard/commit/acb054d11395861c34a60e2a60711379a3b48019) by Timothée Mazzucotelli).
48
+
49
+ ### Performance Improvements
50
+
51
+ - Start faster (cache), scan faster ([9a740e5](https://github.com/pawamoy/devboard/commit/9a740e55aff8add3016b438d1b0912376a5552aa) by Timothée Mazzucotelli).
52
+
53
+ ## [0.1.0](https://github.com/pawamoy/devboard/releases/tag/0.1.0) - 2025-11-08
54
+
55
+ <small>[Compare with first commit](https://github.com/pawamoy/devboard/compare/286120e957168a9162d5a8dc19c7dd72fb0a6d65...0.1.0)</small>
56
+
57
+ ### Features
58
+
59
+ - Release Insiders project to the public ([ce7aad4](https://github.com/pawamoy/devboard/commit/ce7aad4c6c2da3924aebcf143f3a0d1068573297) by Timothée Mazzucotelli).
60
+
61
+ ### Code Refactoring
62
+
63
+ - Update imports ([976e578](https://github.com/pawamoy/devboard/commit/976e57897d92431c60f510efbe6d8be5516747cd) by Timothée Mazzucotelli).
64
+ - Move submodules under internal folder ([9ec90e1](https://github.com/pawamoy/devboard/commit/9ec90e11392d86022213e7dc4cfcfd520fa7783c) by Timothée Mazzucotelli).
@@ -38,6 +38,7 @@ As usual:
38
38
  1. run `make format` to auto-format the code
39
39
  1. run `make check` to check everything (fix any warning)
40
40
  1. run `make test` to run the tests (fix any issue)
41
+ 1. if you changed the terminal layout, run `.venv/bin/pytest tests/test_snapshots.py -c config/pytest.ini --snapshot-update` and review the updated SVG files
41
42
  1. if you updated the documentation or the project dependencies:
42
43
  1. run `make docs`
43
44
  1. go to http://localhost:8000 and check that everything looks good
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devboard
3
- Version: 0.2.0
4
- Summary: A development dashboard for your projects.
3
+ Version: 0.3.0
4
+ Summary: A development dashboard for projects, issues, pull requests, and other work.
5
5
  Author-Email: =?utf-8?q?Timoth=C3=A9e_Mazzucotelli?= <dev@pawamoy.fr>
6
6
  License-Expression: ISC
7
7
  License-File: LICENSE
@@ -28,7 +28,7 @@ Project-URL: Discussions, https://github.com/pawamoy/devboard/discussions
28
28
  Project-URL: Gitter, https://gitter.im/devboard/community
29
29
  Project-URL: Funding, https://github.com/sponsors/pawamoy
30
30
  Requires-Python: >=3.10
31
- Requires-Dist: textual>=0.40
31
+ Requires-Dist: textual>=8.2
32
32
  Requires-Dist: gitpython>=3.1
33
33
  Requires-Dist: appdirs>=1.4.4
34
34
  Requires-Dist: tomli>=2.0; python_version < "3.11"
@@ -41,7 +41,7 @@ Description-Content-Type: text/markdown
41
41
  [![pypi version](https://img.shields.io/pypi/v/devboard.svg)](https://pypi.org/project/devboard/)
42
42
  [![gitter](https://img.shields.io/badge/matrix-chat-4DB798.svg?style=flat)](https://app.gitter.im/#/room/#devboard:gitter.im)
43
43
 
44
- A development dashboard for your projects.
44
+ A development dashboard for projects, issues, pull requests, and other work.
45
45
 
46
46
  ## Installation
47
47
 
@@ -5,7 +5,7 @@
5
5
  [![pypi version](https://img.shields.io/pypi/v/devboard.svg)](https://pypi.org/project/devboard/)
6
6
  [![gitter](https://img.shields.io/badge/matrix-chat-4DB798.svg?style=flat)](https://app.gitter.im/#/room/#devboard:gitter.im)
7
7
 
8
- A development dashboard for your projects.
8
+ A development dashboard for projects, issues, pull requests, and other work.
9
9
 
10
10
  ## Installation
11
11
 
@@ -12,5 +12,7 @@ filterwarnings =
12
12
  error
13
13
  default::EncodingWarning
14
14
  error::EncodingWarning:devboard
15
+ # pytest-textual-snapshot 1.1 uses datetime.utcnow while building a failure report.
16
+ ignore:datetime.datetime.utcnow\(\) is deprecated.*:DeprecationWarning:pytest_textual_snapshot
15
17
  # TODO: remove once pytest-xdist 4 is released
16
18
  ignore:.*rsyncdir:DeprecationWarning:xdist
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToCommit
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,15 +12,15 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToCommit(ToCommit):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
- columns = [ToCommit]
26
+ board = Board([ToCommit])
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToCommit, ToPull
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,20 +12,20 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToCommit(ToCommit):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
26
  class ToPull(ToPull):
26
- def list_projects(self):
27
- yield from Project.list_projects()
27
+ def list_items(self):
28
+ yield from Project.list_items()
28
29
 
29
30
 
30
- columns = [ToCommit, ToPull]
31
+ board = Board([ToCommit, ToPull])
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToCommit, ToPull, ToPush
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,25 +12,25 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToCommit(ToCommit):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
26
  class ToPull(ToPull):
26
- def list_projects(self):
27
- yield from Project.list_projects()
27
+ def list_items(self):
28
+ yield from Project.list_items()
28
29
 
29
30
 
30
31
  class ToPush(ToPush):
31
- def list_projects(self):
32
- yield from Project.list_projects()
32
+ def list_items(self):
33
+ yield from Project.list_items()
33
34
 
34
35
 
35
- columns = [ToCommit, ToPull, ToPush]
36
+ board = Board([ToCommit, ToPull, ToPush])
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from devboard._internal.default_board import ProjectsBoard, ToCommit, ToPull, ToPush, ToRelease
7
+ from devboard._internal.projects import Project as BaseProject
8
+
9
+ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
10
+
11
+
12
+ class Project(BaseProject):
13
+ @classmethod
14
+ def list_items(cls):
15
+ for filedir in BASE_DIR.iterdir():
16
+ if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
+ yield cls(filedir)
18
+
19
+
20
+ class ToCommit(ToCommit):
21
+ def list_items(self):
22
+ yield from Project.list_items()
23
+
24
+
25
+ class ToPull(ToPull):
26
+ def list_items(self):
27
+ yield from Project.list_items()
28
+
29
+
30
+ class ToPush(ToPush):
31
+ def list_items(self):
32
+ yield from Project.list_items()
33
+
34
+
35
+ class ToRelease(ToRelease):
36
+ def list_items(self):
37
+ yield from Project.list_items()
38
+
39
+
40
+ board = ProjectsBoard(
41
+ [ToCommit, ToPull, ToPush, ToRelease],
42
+ bindings=[
43
+ ("question_mark", "show_help", "Help"),
44
+ ("ctrl+q, q, escape", "exit", "Exit"),
45
+ ("ctrl+r", "refresh", "Refresh"),
46
+ ("ctrl+shift+r", "force_refresh", "Force refresh"),
47
+ ],
48
+ force_refresh_on_startup=True,
49
+ )
@@ -4,7 +4,8 @@ import os
4
4
  import time
5
5
  from pathlib import Path
6
6
 
7
- from devboard._internal.default_board import ToCommit, ToPull, ToPush, ToRelease
7
+ from devboard import row_action
8
+ from devboard._internal.default_board import ProjectsBoard, ToCommit, ToPull, ToPush, ToRelease
8
9
  from devboard._internal.projects import Project as BaseProject
9
10
 
10
11
  BASE_DIR = Path(os.environ["PROJECTS_DIR"])
@@ -12,22 +13,23 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
12
13
 
13
14
  class Project(BaseProject):
14
15
  @classmethod
15
- def list_projects(cls):
16
+ def list_items(cls):
16
17
  for filedir in BASE_DIR.iterdir():
17
18
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
18
19
  yield cls(filedir)
19
20
 
20
21
 
21
22
  class ToCommit(ToCommit):
22
- def list_projects(self):
23
- yield from Project.list_projects()
23
+ def list_items(self):
24
+ yield from Project.list_items()
24
25
 
25
26
 
26
27
  class ToPull(ToPull):
27
- def list_projects(self):
28
- yield from Project.list_projects()
28
+ def list_items(self):
29
+ yield from Project.list_items()
29
30
 
30
- def apply(self, action, row):
31
+ @row_action
32
+ def action_pull(self, row):
31
33
  project, branch, _ = row.data
32
34
  message = f"Pulling branch [i]{branch}[/] in [i]{project}[/]"
33
35
  if not project.is_dirty:
@@ -51,13 +53,22 @@ class ToPull(ToPull):
51
53
 
52
54
 
53
55
  class ToPush(ToPush):
54
- def list_projects(self):
55
- yield from Project.list_projects()
56
+ def list_items(self):
57
+ yield from Project.list_items()
56
58
 
57
59
 
58
60
  class ToRelease(ToRelease):
59
- def list_projects(self):
60
- yield from Project.list_projects()
61
-
62
-
63
- columns = [ToCommit, ToPull, ToPush, ToRelease]
61
+ def list_items(self):
62
+ yield from Project.list_items()
63
+
64
+
65
+ board = ProjectsBoard(
66
+ [ToCommit, ToPull, ToPush, ToRelease],
67
+ bindings=[
68
+ ("question_mark", "show_help", "Help"),
69
+ ("ctrl+q, q, escape", "exit", "Exit"),
70
+ ("ctrl+r", "refresh", "Refresh"),
71
+ ("ctrl+shift+r", "force_refresh", "Force refresh"),
72
+ ],
73
+ force_refresh_on_startup=True,
74
+ )
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToPull
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,15 +12,15 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToPull(ToPull):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
- columns = [ToPull]
26
+ board = Board([ToPull])
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToPush
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,15 +12,15 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToPush(ToPush):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
- columns = [ToPush]
26
+ board = Board([ToPush])
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import os
4
4
  from pathlib import Path
5
5
 
6
+ from devboard import Board
6
7
  from devboard._internal.default_board import ToRelease
7
8
  from devboard._internal.projects import Project as BaseProject
8
9
 
@@ -11,15 +12,15 @@ BASE_DIR = Path(os.environ["PROJECTS_DIR"])
11
12
 
12
13
  class Project(BaseProject):
13
14
  @classmethod
14
- def list_projects(cls):
15
+ def list_items(cls):
15
16
  for filedir in BASE_DIR.iterdir():
16
17
  if filedir.is_dir() and filedir.joinpath(".git").is_dir():
17
18
  yield cls(filedir)
18
19
 
19
20
 
20
21
  class ToRelease(ToRelease):
21
- def list_projects(self):
22
- yield from Project.list_projects()
22
+ def list_items(self):
23
+ yield from Project.list_items()
23
24
 
24
25
 
25
- columns = [ToRelease]
26
+ board = Board([ToRelease])
@@ -0,0 +1,75 @@
1
+ import asyncio
2
+ import hashlib
3
+ import inspect
4
+ import os
5
+ from collections.abc import Hashable
6
+ from pathlib import Path
7
+ from tempfile import gettempdir
8
+
9
+ from textual.pilot import Pilot
10
+
11
+ from devboard import Devboard
12
+
13
+ TMP_DIR = Path(gettempdir(), "devboard")
14
+ SCREENSHOT_CACHE = TMP_DIR / "screenshots"
15
+ REPOSITORIES_DIR = TMP_DIR / "repositories"
16
+ PROJECTS_DIR = TMP_DIR / "projects"
17
+ SCREENSHOT_SOURCE = Path("docs/examples/screenshot.py")
18
+
19
+
20
+ def _get_cache_key(path: Path, *args: Hashable, **kwargs: Hashable) -> str:
21
+ hash = hashlib.md5() # noqa: S324
22
+ package_dir = Path(inspect.getfile(Devboard)).parent
23
+ sources = [path, SCREENSHOT_SOURCE, *sorted(package_dir.glob("*.py")), *sorted(package_dir.glob("*.tcss"))]
24
+ for source in sources:
25
+ hash.update(source.read_bytes())
26
+ hash.update(f"{args}-{kwargs}".encode("utf-8"))
27
+ return f"{hash.hexdigest()}.svg"
28
+
29
+
30
+ def screenshot(
31
+ board: str,
32
+ size: tuple[int, int] = (80, 24),
33
+ press: tuple[str, ...] = (),
34
+ env: tuple[tuple[str, str], ...] = (),
35
+ ) -> str:
36
+ """Render a documentation example as an SVG screenshot."""
37
+ path = Path("docs/examples", board).with_suffix(".py")
38
+ SCREENSHOT_CACHE.mkdir(parents=True, exist_ok=True)
39
+
40
+ screenshot_path = SCREENSHOT_CACHE / _get_cache_key(path, size, press, env)
41
+ if screenshot_path.exists():
42
+ return screenshot_path.read_text(encoding="utf-8")
43
+
44
+ environment = {"PROJECTS_DIR": str(PROJECTS_DIR), **dict(env)}
45
+ previous_environment = {name: os.environ.get(name) for name in environment}
46
+ os.environ.update(environment)
47
+ try:
48
+ app = Devboard(board=path, background_tasks=False)
49
+ svg = _take_svg_screenshot(app, size=size, press=press)
50
+ screenshot_path.write_text(svg, encoding="utf-8")
51
+ return svg
52
+ finally:
53
+ for name, value in previous_environment.items():
54
+ if value is None:
55
+ os.environ.pop(name, None)
56
+ else:
57
+ os.environ[name] = value
58
+
59
+
60
+ def _take_svg_screenshot(app: Devboard, *, size: tuple[int, int], press: tuple[str, ...]) -> str:
61
+ """Run an app headlessly and export its final screen."""
62
+
63
+ async def capture(pilot: Pilot) -> None:
64
+ await pilot.pause()
65
+ await asyncio.wait_for(pilot.app.workers.wait_for_complete(), timeout=30)
66
+ if press:
67
+ await pilot.press(*press)
68
+ await pilot.wait_for_scheduled_animations()
69
+ await pilot.pause()
70
+ pilot.app.exit(pilot.app.export_screenshot())
71
+
72
+ svg = app.run(headless=True, size=size, auto_pilot=capture)
73
+ if not isinstance(svg, str):
74
+ raise TypeError("The screenshot run ended without SVG output")
75
+ return svg
@@ -11,9 +11,12 @@ hide:
11
11
  Devboard displays columns stacked horizontally, like a Kanban board.
12
12
  Each column is a "To Do Something" and presents information in a data table.
13
13
  Data tables have a header line with labels, and multiple rows
14
- presenting the information collected in your projects.
15
- Projects are supposed to be Git repositories from which we can collect information
16
- such as status, commits, branches, tags, etc.
14
+ presenting information collected from source items. An item can be a project,
15
+ an issue, a pull request, or an object from another provider.
16
+ The default board uses Git projects and collects their status, commits,
17
+ branches, and tags.
18
+
19
+ Press ++ctrl+r++ to scan the current project data again. Press ++ctrl+shift+r++ to fetch and then scan each project.
17
20
 
18
21
  To start using Devboard, try to run the `devboard` command
19
22
  in your terminal. It will show you a default board with four columns:
@@ -142,4 +145,4 @@ Finally, you can also pass a path to a board module:
142
145
 
143
146
  ```bash
144
147
  devboard ./path/to/myboard.py
145
- ```
148
+ ```