sovabids.tui
============
.. py:module:: sovabids.tui
.. autoapi-nested-parse::
Terminal User Interface for sovabids.
Classes
-------
.. autoapisummary::
sovabids.tui.DirPickerScreen
sovabids.tui.SetupPane
sovabids.tui.FilesListScreen
sovabids.tui.ChannelNamesScreen
sovabids.tui.RulesPane
sovabids.tui.MappingsPane
sovabids.tui.ConvertPane
sovabids.tui.SovabidsApp
Functions
---------
.. autoapisummary::
sovabids.tui._flatten_dict
sovabids.tui.main
Module Contents
---------------
.. py:class:: DirPickerScreen(start: str = '.')
Bases: :py:obj:`textual.screen.ModalScreen`\ [\ :py:obj:`str`\ ]
Modal that lets user browse and confirm a directory.
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
DirPickerScreen {
align: center middle;
}
DirPickerScreen > Vertical {
width: 80%;
height: 80%;
border: thick $primary;
background: $surface;
padding: 1;
}
DirPickerScreen DirectoryTree {
height: 1fr;
}
DirPickerScreen Label {
height: 1;
margin-bottom: 1;
}
DirPickerScreen Horizontal {
height: 3;
align: right middle;
}
DirPickerScreen Button {
margin-left: 1;
}
"""
.. raw:: html
.. py:attribute:: _start
:value: b'.'
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_directory_tree_directory_selected(event: textual.widgets.DirectoryTree.DirectorySelected) -> None
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:class:: SetupPane
Bases: :py:obj:`textual.widgets.Static`
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
SetupPane { padding: 1 2; }
SetupPane Label { margin-top: 1; }
SetupPane .path-row { height: 3; }
SetupPane Input { width: 1fr; }
SetupPane Button { width: 14; margin-left: 1; }
SetupPane .hint { color: $text-muted; margin-bottom: 1; }
"""
.. raw:: html
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: get_values() -> dict
.. py:class:: FilesListScreen(files: list[str], pattern: str = '', mode: str = 'placeholder', fields: list[str] | None = None)
Bases: :py:obj:`textual.screen.ModalScreen`
Modal showing all matched files with extraction details on click.
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
FilesListScreen { align: center middle; }
FilesListScreen > Vertical {
width: 92%; height: 90%;
border: thick $primary; background: $surface; padding: 1;
}
FilesListScreen DataTable { height: 1fr; }
FilesListScreen #extraction-panel {
height: auto;
max-height: 40%;
border: solid $primary;
padding: 0 1;
color: $success;
overflow-y: auto;
}
FilesListScreen #extraction-panel.muted { color: $text-muted; }
FilesListScreen #extraction-panel.error { color: $error; }
FilesListScreen Horizontal { height: 3; align: right middle; }
"""
.. raw:: html
.. py:attribute:: _files
.. py:attribute:: _pattern
:value: ''
.. py:attribute:: _mode
:value: 'placeholder'
.. py:attribute:: _fields
:value: []
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_mount() -> None
.. py:method:: on_data_table_row_highlighted(event: textual.widgets.DataTable.RowHighlighted) -> None
.. py:method:: _show_extraction(filepath: str) -> None
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: on_key(event) -> None
.. py:class:: ChannelNamesScreen(filepath: str)
Bases: :py:obj:`textual.screen.ModalScreen`
Modal showing channel names and types from a file.
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
ChannelNamesScreen { align: center middle; }
ChannelNamesScreen > Vertical {
width: 70%; height: 85%;
border: thick $primary; background: $surface; padding: 1;
}
ChannelNamesScreen DataTable { height: 1fr; }
ChannelNamesScreen #ch-status { color: $text-muted; height: 1; }
ChannelNamesScreen Horizontal { height: 3; align: right middle; }
"""
.. raw:: html
.. py:attribute:: _filepath
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_mount() -> None
.. py:method:: _load_worker() -> None
.. py:method:: _populate(ch_names: list, ch_types: list) -> None
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: on_key(event) -> None
.. py:function:: _flatten_dict(d: dict, prefix: str = '') -> dict
Flatten nested dict with dot-notation keys.
.. py:class:: RulesPane
Bases: :py:obj:`textual.widgets.Static`
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
RulesPane { padding: 1 2; }
RulesPane Label { margin-top: 1; }
RulesPane .hint { color: $text-muted; }
RulesPane .pattern-examples {
color: $text-muted;
margin-bottom: 1;
padding: 0 2;
border-left: solid $primary;
}
RulesPane #ext-count { color: $text-muted; height: 1; margin-bottom: 1; }
RulesPane #preview-row { height: auto; margin-top: 1; }
RulesPane #preview-label {
padding: 1;
border: solid $primary;
color: $success;
width: 1fr;
height: auto;
min-height: 3;
}
RulesPane #preview-label.error { color: $error; }
RulesPane #preview-label.muted { color: $text-muted; }
RulesPane #show-files { width: 18; margin-left: 1; }
RulesPane Select { margin-bottom: 1; }
RulesPane Input { margin-bottom: 1; }
RulesPane .section-head { margin-top: 1; }
RulesPane RadioSet { margin-bottom: 1; }
RulesPane #regex-fields-row { display: none; height: auto; }
RulesPane #regex-fields-row.visible { display: block; }
RulesPane #regex-examples { display: none; }
RulesPane #regex-examples.visible { display: block; }
RulesPane #io-example-row { display: none; height: auto; }
RulesPane #io-examples { display: none; }
RulesPane #io-src-row { height: 3; }
RulesPane #io-src-row Input { width: 1fr; }
RulesPane #io-src-row Button { width: 18; margin-left: 1; }
RulesPane #pattern-section { height: auto; }
RulesPane .inspect-row { height: 3; margin-bottom: 1; }
RulesPane .inspect-row Button { margin-right: 1; }
"""
.. raw:: html
.. py:attribute:: _preview_lock
:type: ClassVar
.. py:attribute:: _preview_timer
:value: None
.. py:attribute:: _matched_files
:type: list[str]
:value: []
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_input_changed(event: textual.widgets.Input.Changed) -> None
.. py:method:: on_select_changed(event: textual.widgets.Select.Changed) -> None
.. py:method:: on_radio_set_changed(event: textual.widgets.RadioSet.Changed) -> None
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: _get_mode_and_fields() -> tuple[str, list[str]]
.. py:method:: _get_io_example() -> tuple[str, str]
.. py:method:: _schedule_ext_count() -> None
.. py:method:: _run_ext_count() -> None
.. py:method:: _ext_count_worker(source: str, ext: str) -> None
.. py:method:: _psd_worker(filepath: str) -> None
.. py:method:: _schedule_preview() -> None
.. py:method:: _run_preview() -> None
.. py:method:: _preview_worker(source: str, pattern: str, ext: str, mode: str, fields: list[str], io_src: str = '', io_tgt: str = '') -> None
.. py:method:: _update_show_files_btn(files: list[str]) -> None
.. py:method:: _set_preview(text: str, css_class: str) -> None
.. py:method:: _get_source() -> str
.. py:method:: get_rules() -> dict
.. py:class:: MappingsPane
Bases: :py:obj:`textual.widgets.Static`
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
MappingsPane { padding: 1 2; }
MappingsPane #mappings-status { margin-bottom: 1; color: $text-muted; }
MappingsPane DataTable { height: 1fr; }
MappingsPane Horizontal { height: 3; margin-bottom: 1; }
MappingsPane Button { margin-right: 1; }
"""
.. raw:: html
.. py:attribute:: _mappings
:type: textual.reactive.reactive[list]
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_mount() -> None
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: _generate() -> None
.. py:method:: _gen_worker(source: str, bids: str, rules: dict) -> None
.. py:method:: _populate_table(individuals: list) -> None
.. py:method:: _save_mappings_yaml() -> None
.. py:method:: _set_status(msg: str, error: bool = False) -> None
.. py:class:: ConvertPane
Bases: :py:obj:`textual.widgets.Static`
.. py:attribute:: DEFAULT_CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
ConvertPane { padding: 1 2; }
ConvertPane Horizontal { height: 3; margin-bottom: 1; }
ConvertPane Button { margin-right: 1; }
ConvertPane RichLog { height: 1fr; border: solid $primary; }
"""
.. raw:: html
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: on_button_pressed(event: textual.widgets.Button.Pressed) -> None
.. py:method:: _start_convert() -> None
.. py:method:: _convert_worker(mapping_data: dict) -> None
.. py:method:: _log(msg: str) -> None
.. py:class:: SovabidsApp
Bases: :py:obj:`textual.app.App`
sovabids TUI — EEG to BIDS conversion wizard.
.. py:attribute:: TITLE
:value: 'sovabids'
.. py:attribute:: SUB_TITLE
:value: 'EEG → BIDS conversion'
.. py:attribute:: CSS
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
TabbedContent { height: 1fr; }
TabPane { height: 1fr; }
"""
.. raw:: html
.. py:attribute:: BINDINGS
:value: [('q', 'quit', 'Quit'), ('ctrl+s', 'save_rules', 'Save rules')]
.. py:attribute:: _mapping_data
:type: dict | None
:value: None
.. py:attribute:: _pending_input_id
:type: str
:value: ''
.. py:method:: compose() -> textual.app.ComposeResult
.. py:method:: _on_dir_picked(path: str) -> None
.. py:method:: action_save_rules() -> None
.. py:function:: main() -> None