Skip to content

devboard ¤

Devboard package.

A development dashboard for your projects.

Classes:

Functions:

  • get_parser

    Return the CLI argument parser.

  • main

    Run the main program.

Checkbox dataclass ¤

Checkbox(checked: bool = False)

A checkbox, added to rows to make them selectable.

Methods:

  • check

    Uncheck the checkbox.

  • toggle

    Toggle the checkbox.

  • uncheck

    Uncheck the checkbox.

Attributes:

checked class-attribute instance-attribute ¤

checked: bool = False

Whether the checkbox is checked.

check ¤

check() -> None

Uncheck the checkbox.

Source code in src/devboard/_internal/datatable.py
30
31
32
def check(self) -> None:
    """Uncheck the checkbox."""
    self.checked = True

toggle ¤

toggle() -> bool

Toggle the checkbox.

Source code in src/devboard/_internal/datatable.py
38
39
40
41
def toggle(self) -> bool:
    """Toggle the checkbox."""
    self.checked = not self.checked
    return self.checked

uncheck ¤

uncheck() -> None

Uncheck the checkbox.

Source code in src/devboard/_internal/datatable.py
34
35
36
def uncheck(self) -> None:
    """Uncheck the checkbox."""
    self.checked = False

Column ¤

Bases: Container, ModalMixin, NotifyMixin

A Devboard column.

Methods:

Attributes:

DEFAULT_CLASSES class-attribute instance-attribute ¤

DEFAULT_CLASSES = 'box'

Textual CSS classes.

HEADERS class-attribute instance-attribute ¤

HEADERS: tuple[str, ...] = ()

The data table headers.

THREADED class-attribute instance-attribute ¤

THREADED: bool = True

Whether actions of this column should run in the background.

TITLE class-attribute instance-attribute ¤

TITLE: str = ''

The title of the column.

app instance-attribute ¤

app: App

Textual application.

table property ¤

table: DataTable

Data table.

action_apply ¤

action_apply(action: str = 'default') -> None

Apply an action to selected rows.

Source code in src/devboard/_internal/board.py
71
72
73
74
75
76
77
78
79
def action_apply(self, action: str = "default") -> None:
    """Apply an action to selected rows."""
    selected_rows = list(self.table.selected_rows) or [self.table.current_row]
    if self.THREADED:
        for row in selected_rows:
            self.run_worker(partial(self.apply, action=action, row=row), thread=True)  # type: ignore[arg-type]
    else:
        for row in selected_rows:
            self.apply(action=action, row=row)  # type: ignore[arg-type]

apply ¤

apply(action: str, row: Row) -> None

Apply action on given row.

Source code in src/devboard/_internal/board.py
139
140
141
def apply(self, action: str, row: Row) -> None:  # noqa: ARG002
    """Apply action on given row."""
    return

compose ¤

compose() -> ComposeResult

Compose column widgets.

Source code in src/devboard/_internal/board.py
59
60
61
62
def compose(self) -> ComposeResult:
    """Compose column widgets."""
    yield Static("▶ " + self.TITLE, classes="column-title")
    yield DataTable(id="table")

list_projects ¤

list_projects() -> Iterable[Project]

List projects for this column.

Source code in src/devboard/_internal/board.py
130
131
132
def list_projects(self) -> Iterable[Project]:
    """List projects for this column."""
    return ()

modal ¤

modal(text: str) -> None

Push a modal.

Source code in src/devboard/_internal/modal.py
43
44
45
def modal(self, text: str) -> None:
    """Push a modal."""
    self.app.push_screen(Modal(text=text))

notify_error ¤

notify_error(message: str, timeout: float = 3.0) -> None

Notify error.

Source code in src/devboard/_internal/notifications.py
22
23
24
def notify_error(self, message: str, timeout: float = 3.0) -> None:
    """Notify error."""
    self.app.notify(f"[b red]ERROR[/]  {message}", severity="error", timeout=timeout)

notify_info ¤

notify_info(message: str, timeout: float = 3.0) -> None

Notify information.

Source code in src/devboard/_internal/notifications.py
10
11
12
def notify_info(self, message: str, timeout: float = 3.0) -> None:
    """Notify information."""
    self.app.notify(f"[b blue]INFO[/]  {message}", severity="information", timeout=timeout)

notify_success ¤

notify_success(message: str, timeout: float = 3.0) -> None

Notify success.

Source code in src/devboard/_internal/notifications.py
14
15
16
def notify_success(self, message: str, timeout: float = 3.0) -> None:
    """Notify success."""
    self.app.notify(f"[b green]SUCCESS[/]  {message}", severity="information", timeout=timeout)

notify_warning ¤

notify_warning(message: str, timeout: float = 3.0) -> None

Notify warning.

Source code in src/devboard/_internal/notifications.py
18
19
20
def notify_warning(self, message: str, timeout: float = 3.0) -> None:
    """Notify warning."""
    self.app.notify(f"[b yellow]WARNING[/]  {message}", severity="warning", timeout=timeout)

on_mount ¤

on_mount() -> None

Fill data table.

Source code in src/devboard/_internal/board.py
64
65
66
def on_mount(self) -> None:
    """Fill data table."""
    self.update()

populate_rows staticmethod ¤

populate_rows(project: Project) -> list[tuple[Any, ...]]

Populate rows for this column.

Source code in src/devboard/_internal/board.py
134
135
136
137
@staticmethod
def populate_rows(project: Project) -> list[tuple[Any, ...]]:  # noqa: ARG004
    """Populate rows for this column."""
    return []

update ¤

update() -> None

Update the column (recompute data).

Source code in src/devboard/_internal/board.py
89
90
91
92
93
94
95
96
97
def update(self) -> None:
    """Update the column (recompute data)."""
    table = self.query_one(DataTable)
    if table.loading:
        return
    table.loading = True
    table.clear(columns=True)
    table.cursor_type = "row"
    self._load_data(table)

DataTable ¤

Bases: SelectableRowsDataTable

A Devboard data table.

Methods:

Attributes:

BINDINGS class-attribute instance-attribute ¤

BINDINGS: ClassVar = [
    Binding(
        "space",
        "toggle_select_row",
        "Toggle select",
        show=False,
    ),
    Binding(
        "ctrl+a, *",
        "toggle_select_all",
        "Toggle select all",
        show=False,
    ),
    Binding(
        "exclamation_mark",
        "reverse_select",
        "Reverse select",
        show=False,
    ),
    Binding(
        "shift+up",
        "toggle_select_up",
        "Expand select up",
        show=False,
    ),
    Binding(
        "shift+down",
        "toggle_select_down",
        "Expand select down",
        show=False,
    ),
]

Key bindings for selecting rows.

ROW class-attribute instance-attribute ¤

ROW = Row

The class to instantiate rows.

current_row property ¤

current_row: SelectableRow

Currently selected row.

selectable_rows property ¤

selectable_rows: Iterator[SelectableRow]

Rows, as selectable ones.

selected_rows property ¤

selected_rows: Iterator[SelectableRow]

Selected rows.

action_reverse_select ¤

action_reverse_select() -> None

Reverse selection.

Source code in src/devboard/_internal/datatable.py
171
172
173
174
175
def action_reverse_select(self) -> None:
    """Reverse selection."""
    for row in self.selectable_rows:
        row.toggle_select()
    self.force_refresh()

action_toggle_select_all ¤

action_toggle_select_all() -> None

Toggle-select all rows.

Source code in src/devboard/_internal/datatable.py
160
161
162
163
164
165
166
167
168
169
def action_toggle_select_all(self) -> None:
    """Toggle-select all rows."""
    rows = list(self.selectable_rows)
    if all(row.selected for row in rows):
        for row in rows:
            row.unselect()
    else:
        for row in rows:
            row.select()
    self.force_refresh()

action_toggle_select_down ¤

action_toggle_select_down() -> None

Toggle selection down.

Source code in src/devboard/_internal/datatable.py
189
190
191
192
193
194
195
196
197
198
199
def action_toggle_select_down(self) -> None:
    """Toggle selection down."""
    try:
        row = self.current_row
        next_row = row.next
    except CellDoesNotExist:
        pass
    else:
        next_row.toggle_select()
        self.move_cursor(row=next_row.index)
        self.force_refresh()

action_toggle_select_row ¤

action_toggle_select_row() -> None

Toggle-select current row.

Source code in src/devboard/_internal/datatable.py
151
152
153
154
155
156
157
158
def action_toggle_select_row(self) -> None:
    """Toggle-select current row."""
    try:
        row = self.current_row
    except CellDoesNotExist:
        return
    row.toggle_select()
    self.force_refresh()

action_toggle_select_up ¤

action_toggle_select_up() -> None

Toggle selection up.

Source code in src/devboard/_internal/datatable.py
177
178
179
180
181
182
183
184
185
186
187
def action_toggle_select_up(self) -> None:
    """Toggle selection up."""
    try:
        row = self.current_row
        previous_row = row.previous
    except CellDoesNotExist:
        pass
    else:
        previous_row.toggle_select()
        self.move_cursor(row=previous_row.index)
        self.force_refresh()

add_rows ¤

add_rows(rows: Iterable[Iterable]) -> list[RowKey]

Add rows.

Automatically insert a column with checkboxes in position 0.

Source code in src/devboard/_internal/datatable.py
131
132
133
134
135
136
def add_rows(self, rows: Iterable[Iterable]) -> list[RowKey]:
    """Add rows.

    Automatically insert a column with checkboxes in position 0.
    """
    return super().add_rows((Checkbox(), *row) for row in rows)

clear ¤

clear(columns: bool = True) -> SelectableRowsDataTable

Clear rows and optionally columns.

When clearing columns, automatically re-add a column for checkboxes.

Source code in src/devboard/_internal/datatable.py
138
139
140
141
142
143
144
145
146
def clear(self, columns: bool = True) -> SelectableRowsDataTable:  # noqa: FBT001,FBT002
    """Clear rows and optionally columns.

    When clearing columns, automatically re-add a column for checkboxes.
    """
    super().clear(columns)
    if columns:
        self.add_column("", key="checkbox")
    return self

force_refresh ¤

force_refresh() -> None

Force refresh table.

Source code in src/devboard/_internal/datatable.py
204
205
206
207
208
209
def force_refresh(self) -> None:
    """Force refresh table."""
    # HACK: Without such increment, the table is refreshed
    # only when focus changes to another column.
    self._update_count += 1
    self.refresh()

Devboard ¤

Devboard(
    *args: Any,
    board: str | Path | None = None,
    background_tasks: bool = True,
    **kwargs: Any,
)

Bases: App, ModalMixin

The Devboard application.

Methods:

Attributes:

Source code in src/devboard/_internal/app.py
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(
    self,
    *args: Any,
    board: str | Path | None = None,
    background_tasks: bool = True,
    **kwargs: Any,
) -> None:
    """Initialize the app."""
    super().__init__(*args, **kwargs)
    self._board = board
    self._config_file = Path(user_config_dir(), "devboard", "config.toml")
    self._background_tasks = background_tasks

BINDINGS class-attribute instance-attribute ¤

BINDINGS: ClassVar = [
    Binding("F5, ctrl+r", "refresh", "Refresh"),
    Binding("question_mark", "show_help", "Help"),
    Binding(
        "ctrl+q, q, escape", "exit", "Exit", key_display="Q"
    ),
]

Application key bindings.

CSS_PATH class-attribute instance-attribute ¤

CSS_PATH = parent / 'devboard.tcss'

Path to the CSS file.

app instance-attribute ¤

app: App

Textual application.

action_exit ¤

action_exit() -> None

Exit application.

Source code in src/devboard/_internal/app.py
93
94
95
96
def action_exit(self) -> None:
    """Exit application."""
    self.workers.cancel_all()
    self.exit()

action_refresh ¤

action_refresh() -> None

Refresh all columns.

Source code in src/devboard/_internal/app.py
88
89
90
91
def action_refresh(self) -> None:
    """Refresh all columns."""
    for column in self.query(Column):
        column.update()

action_show_help ¤

action_show_help() -> None

Show help.

Source code in src/devboard/_internal/app.py
78
79
80
81
82
83
84
85
86
def action_show_help(self) -> None:
    """Show help."""
    lines = ["# Main keys\n\n"]
    lines.extend(self._bindings_help(Devboard))
    lines.extend(self._bindings_help(DataTable, search_up=True))
    for column in self.query(Column):
        lines.append(f"\n\n# {column.__class__.TITLE}\n\n")
        lines.extend(self._bindings_help(column.__class__))
    self.push_screen(Modal(text=Markdown("\n".join(lines))))

compose ¤

compose() -> ComposeResult

Compose the layout.

Source code in src/devboard/_internal/app.py
61
62
63
64
65
66
67
68
def compose(self) -> ComposeResult:
    """Compose the layout."""
    for column in self._load_columns():
        if isinstance(column, Column):
            yield column
        else:
            yield column()
    yield Footer()

fetch_all ¤

fetch_all() -> None

Run git fetch in all projects, in background.

Source code in src/devboard/_internal/app.py
101
102
103
104
105
106
107
108
109
@work(thread=True)
def fetch_all(self) -> None:
    """Run `git fetch` in all projects, in background."""
    projects = set()
    for column in self.query(Column):
        projects |= set(column.list_projects())
    with Pool() as pool:
        for project in projects:
            pool.apply_async(project.fetch)

modal ¤

modal(text: str) -> None

Push a modal.

Source code in src/devboard/_internal/modal.py
43
44
45
def modal(self, text: str) -> None:
    """Push a modal."""
    self.app.push_screen(Modal(text=text))

on_mount ¤

on_mount() -> None

Run background tasks.

Source code in src/devboard/_internal/app.py
70
71
72
73
def on_mount(self) -> None:
    """Run background tasks."""
    if self._background_tasks:
        self.fetch_all()

Modal ¤

Modal(*args: Any, text: Any, **kwargs: Any)

Bases: ModalScreen

A modal screen.

Methods:

  • compose

    Screen composition.

  • on_key

    Dismiss on any unbound key.

Attributes:

  • text

    Text content.

Source code in src/devboard/_internal/modal.py
18
19
20
21
22
23
24
25
def __init__(self, *args: Any, text: Any, **kwargs: Any) -> None:
    """Initialize the screen."""
    super().__init__(*args, **kwargs)
    if isinstance(text, str):
        self.text = Text.from_ansi(text)
        """Text content."""
    else:
        self.text = text

text instance-attribute ¤

text = from_ansi(text)

Text content.

compose ¤

compose() -> ComposeResult

Screen composition.

Source code in src/devboard/_internal/modal.py
27
28
29
def compose(self) -> ComposeResult:
    """Screen composition."""
    yield VerticalScroll(Static(self.text), id="modal-contents")

on_key ¤

on_key(event: Key) -> None

Dismiss on any unbound key.

Source code in src/devboard/_internal/modal.py
31
32
33
34
def on_key(self, event: Key) -> None:
    """Dismiss on any unbound key."""
    if not any(bindings.keys.get(event.key) for _, bindings in self.app._modal_binding_chain):  # type: ignore[attr-defined]
        self.dismiss()

ModalMixin ¤

Mixin class to add a modal method.

Methods:

  • modal

    Push a modal.

Attributes:

  • app (App) –

    Textual application.

app instance-attribute ¤

app: App

Textual application.

modal ¤

modal(text: str) -> None

Push a modal.

Source code in src/devboard/_internal/modal.py
43
44
45
def modal(self, text: str) -> None:
    """Push a modal."""
    self.app.push_screen(Modal(text=text))

NotifyMixin ¤

Mixin class to add notify methods.

Methods:

Attributes:

  • app (App) –

    Textual application.

app instance-attribute ¤

app: App

Textual application.

notify_error ¤

notify_error(message: str, timeout: float = 3.0) -> None

Notify error.

Source code in src/devboard/_internal/notifications.py
22
23
24
def notify_error(self, message: str, timeout: float = 3.0) -> None:
    """Notify error."""
    self.app.notify(f"[b red]ERROR[/]  {message}", severity="error", timeout=timeout)

notify_info ¤

notify_info(message: str, timeout: float = 3.0) -> None

Notify information.

Source code in src/devboard/_internal/notifications.py
10
11
12
def notify_info(self, message: str, timeout: float = 3.0) -> None:
    """Notify information."""
    self.app.notify(f"[b blue]INFO[/]  {message}", severity="information", timeout=timeout)

notify_success ¤

notify_success(message: str, timeout: float = 3.0) -> None

Notify success.

Source code in src/devboard/_internal/notifications.py
14
15
16
def notify_success(self, message: str, timeout: float = 3.0) -> None:
    """Notify success."""
    self.app.notify(f"[b green]SUCCESS[/]  {message}", severity="information", timeout=timeout)

notify_warning ¤

notify_warning(message: str, timeout: float = 3.0) -> None

Notify warning.

Source code in src/devboard/_internal/notifications.py
18
19
20
def notify_warning(self, message: str, timeout: float = 3.0) -> None:
    """Notify warning."""
    self.app.notify(f"[b yellow]WARNING[/]  {message}", severity="warning", timeout=timeout)

Project dataclass ¤

Project(path: Path)

A class representing development projects.

It is instantiated with a path, and then provides many utility properties and methods.

Methods:

  • checkout

    Checkout branch, restore previous one when exiting.

  • delete

    Delete branch.

  • fetch

    Fetch.

  • lock

    Lock project.

  • pull

    Pull branch.

  • push

    Push branch.

  • unlock

    Unlock project.

  • unpulled

    Number of unpulled commits, per branch.

  • unpushed

    Number of unpushed commits, per branch.

  • unreleased

    List unreleased commits.

Attributes:

  • DEFAULT_BRANCHES (tuple[str, ...]) –

    Name of common default branches. Mainly useful to compute unreleased commits.

  • LOCKS (dict[Project, Lock]) –

    Locks for projects, to avoid concurrent operations.

  • branch (Head) –

    Currently checked out branch.

  • default_branch (str) –

    Default branch (or main branch), as checked out when cloning.

  • is_dirty (bool) –

    Whether the project is in a "dirty" state (uncommitted modifications).

  • latest_tag (TagReference) –

    Latest tag.

  • name (str) –

    Name of the project.

  • path (Path) –

    Path of the project on the file-system.

  • repo (Repo) –

    GitPython's Repo object.

  • status (Status) –

    Status of the project.

  • status_line (str) –

    Status of the project, as a string.

DEFAULT_BRANCHES class-attribute ¤

DEFAULT_BRANCHES: tuple[str, ...] = ('main', 'master')

Name of common default branches. Mainly useful to compute unreleased commits.

LOCKS class-attribute ¤

Locks for projects, to avoid concurrent operations.

branch property ¤

branch: Head

Currently checked out branch.

default_branch property ¤

default_branch: str

Default branch (or main branch), as checked out when cloning.

is_dirty property ¤

is_dirty: bool

Whether the project is in a "dirty" state (uncommitted modifications).

latest_tag property ¤

latest_tag: TagReference

Latest tag.

name property ¤

name: str

Name of the project.

path instance-attribute ¤

path: Path

Path of the project on the file-system.

repo property ¤

repo: Repo

GitPython's Repo object.

status property ¤

status: Status

Status of the project.

status_line property ¤

status_line: str

Status of the project, as a string.

checkout ¤

checkout(branch: str | None) -> Iterator[None]

Checkout branch, restore previous one when exiting.

Source code in src/devboard/_internal/projects.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
@contextmanager
def checkout(self, branch: str | None) -> Iterator[None]:
    """Checkout branch, restore previous one when exiting."""
    if not branch:
        yield
        return
    current = self.branch
    if branch == current:
        yield
        return
    self.repo.branches[branch].checkout()
    try:
        yield
    finally:
        current.checkout()

delete ¤

delete(branch: str) -> None

Delete branch.

Source code in src/devboard/_internal/projects.py
162
163
164
def delete(self, branch: str) -> None:
    """Delete branch."""
    self.repo.delete_head(branch, force=True)

fetch ¤

fetch() -> None

Fetch.

Source code in src/devboard/_internal/projects.py
185
186
187
188
189
190
def fetch(self) -> None:
    """Fetch."""
    with suppress(AttributeError, GitCommandError):
        self.repo.remotes.origin.fetch()
    with suppress(AttributeError, GitCommandError):
        self.repo.remotes.upstream.fetch()

lock ¤

lock() -> bool

Lock project.

Source code in src/devboard/_internal/projects.py
197
198
199
def lock(self) -> bool:
    """Lock project."""
    return self.LOCKS[self].acquire(blocking=False)

pull ¤

pull(branch: str | None = None) -> None

Pull branch.

Source code in src/devboard/_internal/projects.py
152
153
154
155
def pull(self, branch: str | None = None) -> None:
    """Pull branch."""
    with self.checkout(branch):
        self.repo.remotes.origin.pull()

push ¤

push(branch: str | None = None) -> None

Push branch.

Source code in src/devboard/_internal/projects.py
157
158
159
160
def push(self, branch: str | None = None) -> None:
    """Push branch."""
    with self.checkout(branch):
        self.repo.remotes.origin.push()

unlock ¤

unlock() -> None

Unlock project.

Source code in src/devboard/_internal/projects.py
201
202
203
def unlock(self) -> None:
    """Unlock project."""
    self.LOCKS[self].release()

unpulled ¤

unpulled(remote: str = 'origin') -> dict[str, int]

Number of unpulled commits, per branch.

Source code in src/devboard/_internal/projects.py
109
110
111
112
113
114
115
def unpulled(self, remote: str = "origin") -> dict[str, int]:
    """Number of unpulled commits, per branch."""
    result = {}
    for branch in self.repo.branches:
        with contextlib.suppress(GitCommandError):
            result[branch.name] = len(list(self.repo.iter_commits(f"{branch.name}..{remote}/{branch.name}")))
    return result

unpushed ¤

unpushed(remote: str = 'origin') -> dict[str, int]

Number of unpushed commits, per branch.

Source code in src/devboard/_internal/projects.py
101
102
103
104
105
106
107
def unpushed(self, remote: str = "origin") -> dict[str, int]:
    """Number of unpushed commits, per branch."""
    result = {}
    for branch in self.repo.branches:
        with contextlib.suppress(GitCommandError):
            result[branch.name] = len(list(self.repo.iter_commits(f"{remote}/{branch.name}..{branch.name}")))
    return result

unreleased ¤

unreleased(branch: str | None = None) -> list[Commit]

List unreleased commits.

Source code in src/devboard/_internal/projects.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def unreleased(self, branch: str | None = None) -> list[Commit]:
    """List unreleased commits."""
    commits = []
    if branch is None:
        try:
            branch = self.default_branch
        except ValueError:
            return []
    iterator = self.repo.iter_commits(branch)
    try:
        latest_tagged_commit = self.latest_tag.commit
    except IndexError:
        return list(iterator)
    for commit in iterator:
        if commit == latest_tagged_commit:
            break
        commits.append(commit)
    return commits

Row dataclass ¤

Row(table: SelectableRowsDataTable, key: RowKey)

Bases: SelectableRow

A Devboard row.

Methods:

Attributes:

app property ¤

app: App

Textual application.

checkbox property ¤

checkbox: Checkbox

Row checkbox.

data property ¤

data: list

Row data (without checkbox).

index property ¤

index: int

Row index.

key instance-attribute ¤

key: RowKey

The row key.

next property ¤

Next row (down).

previous property ¤

previous: SelectableRow

Previous row (up).

project property ¤

project: Project

Devboard project.

selected property ¤

selected: bool

Whether this row is selected.

table instance-attribute ¤

The data table containing this row.

remove ¤

remove() -> None

Remove row from the table.

Source code in src/devboard/_internal/datatable.py
94
95
96
def remove(self) -> None:
    """Remove row from the table."""
    self.table.remove_row(self.key)

select ¤

select() -> None

Select this row.

Source code in src/devboard/_internal/datatable.py
77
78
79
def select(self) -> None:
    """Select this row."""
    self.checkbox.check()

toggle_select ¤

toggle_select() -> bool

Toggle-select this row.

Source code in src/devboard/_internal/datatable.py
85
86
87
def toggle_select(self) -> bool:
    """Toggle-select this row."""
    return self.checkbox.toggle()

unselect ¤

unselect() -> None

Unselect this row.

Source code in src/devboard/_internal/datatable.py
81
82
83
def unselect(self) -> None:
    """Unselect this row."""
    self.checkbox.uncheck()

SelectableRow dataclass ¤

SelectableRow(table: SelectableRowsDataTable, key: RowKey)

A selectable row.

Methods:

Attributes:

app property ¤

app: App

Textual application.

checkbox property ¤

checkbox: Checkbox

Row checkbox.

data property ¤

data: list

Row data (without checkbox).

index property ¤

index: int

Row index.

key instance-attribute ¤

key: RowKey

The row key.

next property ¤

Next row (down).

previous property ¤

previous: SelectableRow

Previous row (up).

selected property ¤

selected: bool

Whether this row is selected.

table instance-attribute ¤

The data table containing this row.

remove ¤

remove() -> None

Remove row from the table.

Source code in src/devboard/_internal/datatable.py
94
95
96
def remove(self) -> None:
    """Remove row from the table."""
    self.table.remove_row(self.key)

select ¤

select() -> None

Select this row.

Source code in src/devboard/_internal/datatable.py
77
78
79
def select(self) -> None:
    """Select this row."""
    self.checkbox.check()

toggle_select ¤

toggle_select() -> bool

Toggle-select this row.

Source code in src/devboard/_internal/datatable.py
85
86
87
def toggle_select(self) -> bool:
    """Toggle-select this row."""
    return self.checkbox.toggle()

unselect ¤

unselect() -> None

Unselect this row.

Source code in src/devboard/_internal/datatable.py
81
82
83
def unselect(self) -> None:
    """Unselect this row."""
    self.checkbox.uncheck()

SelectableRowsDataTable ¤

Bases: DataTable

Data table with selectable rows.

Methods:

Attributes:

BINDINGS class-attribute instance-attribute ¤

BINDINGS: ClassVar = [
    Binding(
        "space",
        "toggle_select_row",
        "Toggle select",
        show=False,
    ),
    Binding(
        "ctrl+a, *",
        "toggle_select_all",
        "Toggle select all",
        show=False,
    ),
    Binding(
        "exclamation_mark",
        "reverse_select",
        "Reverse select",
        show=False,
    ),
    Binding(
        "shift+up",
        "toggle_select_up",
        "Expand select up",
        show=False,
    ),
    Binding(
        "shift+down",
        "toggle_select_down",
        "Expand select down",
        show=False,
    ),
]

Key bindings for selecting rows.

ROW class-attribute instance-attribute ¤

The class to instantiate selectable rows.

current_row property ¤

current_row: SelectableRow

Currently selected row.

selectable_rows property ¤

selectable_rows: Iterator[SelectableRow]

Rows, as selectable ones.

selected_rows property ¤

selected_rows: Iterator[SelectableRow]

Selected rows.

action_reverse_select ¤

action_reverse_select() -> None

Reverse selection.

Source code in src/devboard/_internal/datatable.py
171
172
173
174
175
def action_reverse_select(self) -> None:
    """Reverse selection."""
    for row in self.selectable_rows:
        row.toggle_select()
    self.force_refresh()

action_toggle_select_all ¤

action_toggle_select_all() -> None

Toggle-select all rows.

Source code in src/devboard/_internal/datatable.py
160
161
162
163
164
165
166
167
168
169
def action_toggle_select_all(self) -> None:
    """Toggle-select all rows."""
    rows = list(self.selectable_rows)
    if all(row.selected for row in rows):
        for row in rows:
            row.unselect()
    else:
        for row in rows:
            row.select()
    self.force_refresh()

action_toggle_select_down ¤

action_toggle_select_down() -> None

Toggle selection down.

Source code in src/devboard/_internal/datatable.py
189
190
191
192
193
194
195
196
197
198
199
def action_toggle_select_down(self) -> None:
    """Toggle selection down."""
    try:
        row = self.current_row
        next_row = row.next
    except CellDoesNotExist:
        pass
    else:
        next_row.toggle_select()
        self.move_cursor(row=next_row.index)
        self.force_refresh()

action_toggle_select_row ¤

action_toggle_select_row() -> None

Toggle-select current row.

Source code in src/devboard/_internal/datatable.py
151
152
153
154
155
156
157
158
def action_toggle_select_row(self) -> None:
    """Toggle-select current row."""
    try:
        row = self.current_row
    except CellDoesNotExist:
        return
    row.toggle_select()
    self.force_refresh()

action_toggle_select_up ¤

action_toggle_select_up() -> None

Toggle selection up.

Source code in src/devboard/_internal/datatable.py
177
178
179
180
181
182
183
184
185
186
187
def action_toggle_select_up(self) -> None:
    """Toggle selection up."""
    try:
        row = self.current_row
        previous_row = row.previous
    except CellDoesNotExist:
        pass
    else:
        previous_row.toggle_select()
        self.move_cursor(row=previous_row.index)
        self.force_refresh()

add_rows ¤

add_rows(rows: Iterable[Iterable]) -> list[RowKey]

Add rows.

Automatically insert a column with checkboxes in position 0.

Source code in src/devboard/_internal/datatable.py
131
132
133
134
135
136
def add_rows(self, rows: Iterable[Iterable]) -> list[RowKey]:
    """Add rows.

    Automatically insert a column with checkboxes in position 0.
    """
    return super().add_rows((Checkbox(), *row) for row in rows)

clear ¤

clear(columns: bool = True) -> SelectableRowsDataTable

Clear rows and optionally columns.

When clearing columns, automatically re-add a column for checkboxes.

Source code in src/devboard/_internal/datatable.py
138
139
140
141
142
143
144
145
146
def clear(self, columns: bool = True) -> SelectableRowsDataTable:  # noqa: FBT001,FBT002
    """Clear rows and optionally columns.

    When clearing columns, automatically re-add a column for checkboxes.
    """
    super().clear(columns)
    if columns:
        self.add_column("", key="checkbox")
    return self

force_refresh ¤

force_refresh() -> None

Force refresh table.

Source code in src/devboard/_internal/datatable.py
204
205
206
207
208
209
def force_refresh(self) -> None:
    """Force refresh table."""
    # HACK: Without such increment, the table is refreshed
    # only when focus changes to another column.
    self._update_count += 1
    self.refresh()

Status dataclass ¤

Status(
    added: list[Path],
    deleted: list[Path],
    modified: list[Path],
    renamed: list[Path],
    typechanged: list[Path],
    untracked: list[Path],
)

Git status data.

Attributes:

added instance-attribute ¤

added: list[Path]

Added files.

deleted instance-attribute ¤

deleted: list[Path]

Deleted files.

modified instance-attribute ¤

modified: list[Path]

Modified files.

renamed instance-attribute ¤

renamed: list[Path]

Renamed files.

typechanged instance-attribute ¤

typechanged: list[Path]

Type-changed files.

untracked instance-attribute ¤

untracked: list[Path]

Untracked files.

get_parser ¤

get_parser() -> ArgumentParser

Return the CLI argument parser.

Returns:

Source code in src/devboard/_internal/cli.py
33
34
35
36
37
38
39
40
41
42
43
44
def get_parser() -> argparse.ArgumentParser:
    """Return the CLI argument parser.

    Returns:
        An argparse parser.
    """
    parser = argparse.ArgumentParser(prog="devboard")
    parser.add_argument("--show-config-dir", action="store_true", help="Show Devboard's configuration directory.")
    parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug._get_version()}")
    parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
    parser.add_argument("board", nargs="?", default=None, help="Board name or path.")
    return parser

main ¤

main(args: list[str] | None = None) -> int

Run the main program.

This function is executed when you type devboard or python -m devboard.

Parameters:

  • args (list[str] | None, default: None ) –

    Arguments passed from the command line.

Returns:

  • int

    An exit code.

Source code in src/devboard/_internal/cli.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def main(args: list[str] | None = None) -> int:
    """Run the main program.

    This function is executed when you type `devboard` or `python -m devboard`.

    Parameters:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    parser = get_parser()
    opts = parser.parse_args(args=args)
    if opts.show_config_dir:
        print(user_config_dir(appname="devboard"))
        return 0
    app = Devboard(board=opts.board)
    app.run()
    return 0