Linting and more file refactoring for the browse action.

This commit is contained in:
Dan Helfman
2026-05-21 17:01:07 -07:00
parent ec11e8817d
commit 9935d0ef8d
8 changed files with 148 additions and 142 deletions
+7 -7
View File
@@ -2,18 +2,18 @@ import textual.app
import textual.binding
import textual.widgets
import borgmatic.actions.browse.carousel
import borgmatic.actions.browse.logs
import borgmatic.actions.browse.widgets
class Browse_app(textual.app.App):
BINDINGS = [
BINDINGS = (
textual.binding.Binding(key='q', action='quit', description='quit'),
textual.binding.Binding(key='v', action='toggle_logs', description='view logs'),
textual.binding.Binding(
key='c', action='command_palette', description='commands', show=False
),
]
)
COMMAND_PALETTE_BINDING = 'c'
CSS = '''
.panel {
@@ -37,15 +37,15 @@ class Browse_app(textual.app.App):
def compose(self):
yield textual.widgets.Header()
yield borgmatic.actions.browse.widgets.Carousel(
[borgmatic.actions.browse.widgets.Configuration_files_list(self.configs)]
yield borgmatic.actions.browse.carousel.Carousel(
[borgmatic.actions.browse.panels.Configuration_files_list(self.configs)]
if len(self.configs) > 1
else [
borgmatic.actions.browse.widgets.Repositories_list(tuple(self.configs.values())[0])
borgmatic.actions.browse.panels.Repositories_list(next(iter(self.configs.values())))
]
)
logs_widget = borgmatic.actions.browse.widgets.Logs()
logs_widget = borgmatic.actions.browse.panels.Logs()
yield logs_widget
yield textual.widgets.Footer()
+3 -4
View File
@@ -1,13 +1,12 @@
import os
import argparse
import json
import logging
import os
import borgmatic.borg.extract
import borgmatic.borg.repo_list
import borgmatic.borg.version
logger = logging.getLogger(__name__)
@@ -46,9 +45,9 @@ def get_repository_archives(config, repository):
def get_archive_files(config, repository, archive_name, list_path=None):
with borgmatic.logger.Log_prefix(repository.get('label', repository['path'])):
if list_path:
logger.info(f"Listing archive {archive_name} at path {list_path}")
logger.info(f'Listing archive {archive_name} at path {list_path}')
else:
logger.info(f"Listing archive {archive_name}")
logger.info(f'Listing archive {archive_name}')
global_arguments = argparse.Namespace()
local_path = config.get('local_path', 'borg')
+117
View File
@@ -0,0 +1,117 @@
import os
import textual.binding
import textual.containers
import borgmatic.actions.browse.panels
import borgmatic.actions.browse.paths
def make_next_panel(focused_panel, option_id):
if isinstance(focused_panel, borgmatic.actions.browse.panels.Configuration_files_list):
return borgmatic.actions.browse.panels.Repositories_list(
config=focused_panel.configs[option_id]
)
if isinstance(focused_panel, borgmatic.actions.browse.panels.Repositories_list):
return borgmatic.actions.browse.panels.Archives_list(
config=focused_panel.config, repository=focused_panel.repositories[option_id]
)
if isinstance(focused_panel, borgmatic.actions.browse.panels.Archives_list):
return borgmatic.actions.browse.panels.Directory_list(
config=focused_panel.config, repository=focused_panel.repository, archive_name=option_id
)
if isinstance(focused_panel, borgmatic.actions.browse.panels.Directory_list):
option = focused_panel.get_option(option_id)
if option.prompt.startswith(
borgmatic.actions.browse.paths.PATH_TYPE_ICONS[
borgmatic.actions.browse.paths.Path_type.DIRECTORY.value
]
):
return borgmatic.actions.browse.panels.Directory_list(
focused_panel.config,
focused_panel.repository,
focused_panel.archive_name,
path_components=(*focused_panel.path_components, option_id),
)
return borgmatic.actions.browse.panels.File_preview(
focused_panel.config,
focused_panel.repository,
focused_panel.archive_name,
file_path=os.path.sep.join((*focused_panel.path_components, option_id)),
)
return None
class Carousel(textual.containers.Horizontal):
BINDINGS = (
textual.binding.Binding(
key='left,h', action='previous', description='previous', priority=True
),
)
def __init__(self, panels):
self.panels = panels
self.focused_panel = panels[0]
super().__init__()
def compose(self):
yield from self.panels
self.focused_panel.focus()
def action_previous(self):
'''
Make the previous panel into the focused panel.
'''
previous_panel_index = self.panels.index(self.focused_panel) - 1
if previous_panel_index < 0:
return
self.focused_panel.styles.display = 'none'
self.focused_panel = self.panels[previous_panel_index]
self.focused_panel.styles.display = 'block'
self.focused_panel.focus()
def action_next(self, option_id):
'''
Hide the current focused panel and create the next one.
'''
self.focused_panel.styles.display = 'none'
next_panel_index = self.panels.index(self.focused_panel) + 1
if next_panel_index < len(self.panels):
self.focused_panel = self.panels[next_panel_index]
self.focused_panel.styles.display = 'block'
else:
self.focused_panel = make_next_panel(self.focused_panel, option_id)
self.panels.append(self.focused_panel)
self.focused_panel.highlighted = 0
self.focused_panel.focus()
self.refresh(recompose=True)
def on_option_list_option_highlighted(self, event):
'''
The highlighted option has changed, so truncate any next panels.
'''
next_panel_index = self.panels.index(self.focused_panel) + 1
del self.panels[next_panel_index:]
def on_option_list_option_selected(self, event):
if event.option_list != self.focused_panel or event.option_id == 'loading-indicator':
return
if event.option_id == '..':
self.action_previous()
else:
self.action_next(event.option_id)
+3 -5
View File
@@ -1,21 +1,19 @@
import contextlib
import functools
import textual.widgets
import textual.widgets.option_list
LOADING_DOT_INTERVAL_SECONDS = 0.3
def update_inline_loading_indicator(widget):
if isinstance(widget, textual.widgets.OptionList):
try:
with contextlib.suppress(textual.widgets.option_list.OptionDoesNotExist):
widget.replace_option_prompt(
'loading-indicator',
(str(widget.get_option('loading-indicator').prompt) + '.').replace('....', ''),
)
except textual.widgets.option_list.OptionDoesNotExist:
pass
elif isinstance(widget, textual.widgets.Static):
widget.update((str(widget.content) + '.').replace('....', ''))
else:
@@ -23,7 +21,7 @@ def update_inline_loading_indicator(widget):
def add_inline_loading_indicator(widget):
loading_message = f'⏳ loading...'
loading_message = '⏳ loading...'
if isinstance(widget, textual.widgets.OptionList):
widget.clear_options()
+3 -3
View File
@@ -1,11 +1,11 @@
import contextlib
import logging
import borgmatic.logger
import textual._context
import textual.worker
import borgmatic.logger
class Rich_color_formatter(logging.Formatter):
def __init__(self, *args, **kwargs):
@@ -43,7 +43,7 @@ class Browse_log_handler(logging.Handler):
message = self.format(record)
try:
worker = textual.worker.get_current_worker()
textual.worker.get_current_worker()
self.logs_widget.app.call_from_thread(self.logs_widget.write, message)
except (RuntimeError, textual.worker.NoActiveWorker):
with contextlib.suppress(textual._context.NoActiveAppError):
@@ -7,8 +7,8 @@ import borgmatic.actions.browse.loading
import borgmatic.actions.browse.paths
import borgmatic.actions.browse.workers
OPTION_LIST_BINDINGS = textual.widgets.OptionList.BINDINGS + [
OPTION_LIST_BINDINGS = (
*textual.widgets.OptionList.BINDINGS,
textual.binding.Binding(
key='up,k', action='cursor_up', description='up', show=True, priority=True
),
@@ -19,7 +19,7 @@ OPTION_LIST_BINDINGS = textual.widgets.OptionList.BINDINGS + [
key='enter', action='select', description='select', show=True, priority=True
),
textual.binding.Binding(key='right,l', action='select', description='select', show=False),
]
)
class Configuration_files_list(textual.widgets.OptionList):
@@ -32,7 +32,7 @@ class Configuration_files_list(textual.widgets.OptionList):
super().__init__(
*(
textual.widgets.option_list.Option(f'{unexpanded_path}', id=config_path)
for config_path in configs.keys()
for config_path in configs
for unexpanded_path in (config_path.replace(home_directory, '~'),)
),
classes='panel',
@@ -80,7 +80,7 @@ class Archives_list(textual.widgets.OptionList):
)
def on_option_list_option_highlighted(self, event):
if self.highlighted not in (None, 0):
if self.highlighted not in {None, 0}:
self.highlighted_option_changed = True
@@ -120,7 +120,7 @@ class Directory_list(textual.widgets.OptionList):
)
def on_option_list_option_highlighted(self, event):
if self.highlighted not in (None, 0):
if self.highlighted not in {None, 0}:
self.highlighted_option_changed = True
@@ -156,115 +156,6 @@ class File_preview(textual.widgets.Static):
)
def make_next_panel(focused_panel, option_id):
if isinstance(focused_panel, Configuration_files_list):
return Repositories_list(config=focused_panel.configs[option_id])
if isinstance(focused_panel, Repositories_list):
return Archives_list(
config=focused_panel.config, repository=focused_panel.repositories[option_id]
)
if isinstance(focused_panel, Archives_list):
return Directory_list(
config=focused_panel.config, repository=focused_panel.repository, archive_name=option_id
)
if isinstance(focused_panel, Directory_list):
option = focused_panel.get_option(option_id)
if option.prompt.startswith(
borgmatic.actions.browse.paths.PATH_TYPE_ICONS[
borgmatic.actions.browse.paths.Path_type.DIRECTORY.value
]
):
return Directory_list(
focused_panel.config,
focused_panel.repository,
focused_panel.archive_name,
path_components=focused_panel.path_components + (option_id,),
)
return File_preview(
focused_panel.config,
focused_panel.repository,
focused_panel.archive_name,
file_path=os.path.sep.join(focused_panel.path_components + (option_id,)),
)
return None
class Carousel(textual.containers.Horizontal):
BINDINGS = [
textual.binding.Binding(
key='left,h', action='previous', description='previous', priority=True
),
]
def __init__(self, panels):
self.panels = panels
self.focused_panel = panels[0]
super().__init__()
def compose(self):
for panel in self.panels:
yield panel
self.focused_panel.focus()
def action_previous(self):
'''
Make the previous panel into the focused panel.
'''
previous_panel_index = self.panels.index(self.focused_panel) - 1
if previous_panel_index < 0:
return
self.focused_panel.styles.display = 'none'
self.focused_panel = self.panels[previous_panel_index]
self.focused_panel.styles.display = 'block'
self.focused_panel.focus()
def action_next(self, option_id):
'''
Hide the current focused panel and create the next one.
'''
self.focused_panel.styles.display = 'none'
next_panel_index = self.panels.index(self.focused_panel) + 1
if next_panel_index < len(self.panels):
self.focused_panel = self.panels[next_panel_index]
self.focused_panel.styles.display = 'block'
else:
self.focused_panel = make_next_panel(self.focused_panel, option_id)
self.panels.append(self.focused_panel)
self.focused_panel.highlighted = 0
self.focused_panel.focus()
self.refresh(recompose=True)
def on_option_list_option_highlighted(self, event):
'''
The highlighted option has changed, so truncate any next panels.
'''
next_panel_index = self.panels.index(self.focused_panel) + 1
del self.panels[next_panel_index:]
def on_option_list_option_selected(self, event):
if event.option_list != self.focused_panel or event.option_id == 'loading-indicator':
return
if event.option_id == '..':
self.action_previous()
else:
self.action_next(event.option_id)
class Logs(textual.widgets.RichLog):
def __init__(self):
super().__init__(markup=True, id='logs', classes='panel')
+2 -2
View File
@@ -15,13 +15,13 @@ def run_browse(
logging.getLogger('asyncio').setLevel(logging.WARNING)
try:
import textual
import textual # noqa: F401, PLC0415
except ImportError: # pragma: no cover
raise ValueError(
'Unable to import the Textual library for the browse action; try installing "borgmatic[browse]"'
)
import borgmatic.actions.browse.app
import borgmatic.actions.browse.app # noqa: PLC0415
app = borgmatic.actions.browse.app.Browse_app(configs)
app.run()
+7 -6
View File
@@ -1,13 +1,12 @@
import borgmatic.actions.browse.archive
import rich.syntax
import rich.text
import textual
import textual.widgets.option_list
import borgmatic.actions.browse.archive
@textual.work(thread=True)
async def add_repository_archives(browse_app, archives_list, config, repository, timer):
def add_repository_archives(browse_app, archives_list, config, repository, timer):
archives_data = borgmatic.actions.browse.archive.get_repository_archives(config, repository)
loading_option = archives_list.get_option('loading-indicator')
@@ -65,8 +64,10 @@ def add_archive_files(
)
highlighted_option = directory_list.highlighted_option
sorted_options = sorted(
directory_list.options
+ [textual.widgets.option_list.Option(' '.join(pieces), id=file_path)],
[
*directory_list.options,
textual.widgets.option_list.Option(' '.join(pieces), id=file_path),
],
key=lambda option: ((option.id == 'loading-indicator'), option.prompt),
)
browse_app.call_from_thread(directory_list.set_options, sorted_options)