Skip the browse action's configuration list if there's only one config file.

This commit is contained in:
Dan Helfman
2026-05-20 23:10:29 -07:00
parent 4b4851e78a
commit 5075c1e539
4 changed files with 79 additions and 33 deletions
+8 -2
View File
@@ -54,14 +54,20 @@ def get_archive_files(config, repository, archive_name, list_path=None):
seen = set()
return (
(path_data['type'] if os.path.join(list_path, base_path) == path_data['path'] else 'd', base_path, path_data.get('linktarget'))
(
path_data['type']
if os.path.join(list_path, base_path) == path_data['path']
else 'd',
base_path,
path_data.get('linktarget'),
)
for path_data in borgmatic.borg.list.capture_archive_listing(
repository['path'],
archive_name,
config,
local_borg_version,
global_arguments,
list_paths=(fr're:^{list_path}/[^/]+',) if list_path else (r're:^[^/]+',),
list_paths=(rf're:^{list_path}/[^/]+',) if list_path else (r're:^[^/]+',),
local_path=local_path,
remote_path=remote_path,
)
+3 -1
View File
@@ -17,7 +17,9 @@ def run_browse(
try:
import textual
except ImportError: # pragma: no cover
raise ValueError('Unable to import the Textual library for the browse action; try installing "borgmatic[browse]"')
raise ValueError(
'Unable to import the Textual library for the browse action; try installing "borgmatic[browse]"'
)
import borgmatic.actions.browse.view
+65 -29
View File
@@ -37,7 +37,8 @@ def update_inline_loading_indicator(widget):
if isinstance(widget, textual.widgets.OptionList):
try:
widget.replace_option_prompt(
'loading-indicator', (str(widget.get_option('loading-indicator').prompt) + '.').replace('....', '')
'loading-indicator',
(str(widget.get_option('loading-indicator').prompt) + '.').replace('....', ''),
)
except textual.widgets.option_list.OptionDoesNotExist:
pass
@@ -67,38 +68,43 @@ def add_inline_loading_indicator(widget):
@textual.work(thread=True)
async def add_repository_archives(
browse_app, archives_list, config, repository, timer
):
async def add_repository_archives(browse_app, archives_list, config, repository, timer):
archives_data = borgmatic.actions.browse.controller.get_repository_archives(config, repository)
loading_option = archives_list.get_option('loading-indicator')
# Reverse the archives, so the common case of accessing the latest archive is easy because it's
# at the top.
for index, archive in enumerate(reversed(archives_data['archives'])):
label_pieces = (archive['archive'], '[dim](latest)[/dim]') if index == 0 else (archive['archive'],)
label_pieces = (
(archive['archive'], '[dim](latest)[/dim]') if index == 0 else (archive['archive'],)
)
highlighted_option = archives_list.highlighted_option
browse_app.call_from_thread(
archives_list.remove_option,
'loading-indicator'
)
browse_app.call_from_thread(archives_list.remove_option, 'loading-indicator')
browse_app.call_from_thread(
archives_list.add_options,
(
textual.widgets.option_list.Option(' '.join(label_pieces), id=archive['archive']),
loading_option,
)
),
)
archives_list.highlighted = (
archives_list.get_option_index(highlighted_option.id)
if highlighted_option and archives_list.highlighted_option_changed
else 0
)
archives_list.highlighted = archives_list.get_option_index(highlighted_option.id) if highlighted_option and archives_list.highlighted_option_changed else 0
browse_app.call_from_thread(archives_list.remove_option, 'loading-indicator')
browse_app.call_from_thread(timer.stop)
@textual.work(thread=True)
def add_archive_files(browse_app, directory_list, config, repository, archive_name, list_path, root_directory, timer):
file_type_paths = borgmatic.actions.browse.controller.get_archive_files(config, repository, archive_name, list_path)
def add_archive_files(
browse_app, directory_list, config, repository, archive_name, list_path, root_directory, timer
):
file_type_paths = borgmatic.actions.browse.controller.get_archive_files(
config, repository, archive_name, list_path
)
loading_option = directory_list.get_option('loading-indicator')
if not root_directory:
@@ -106,20 +112,29 @@ def add_archive_files(browse_app, directory_list, config, repository, archive_na
browse_app.call_from_thread(
directory_list.add_options,
(
textual.widgets.option_list.Option(f'{PATH_TYPE_ICONS[Path_type.DIRECTORY.value]} ..', id='..'),
textual.widgets.option_list.Option(
f'{PATH_TYPE_ICONS[Path_type.DIRECTORY.value]} ..', id='..'
),
loading_option,
)
),
)
for (path_type, file_path, link_target) in file_type_paths:
pieces = (PATH_TYPE_ICONS.get(path_type, '?'), file_path) + (('', link_target) if link_target else ())
for path_type, file_path, link_target in file_type_paths:
pieces = (PATH_TYPE_ICONS.get(path_type, '?'), file_path) + (
('', link_target) if link_target else ()
)
highlighted_option = directory_list.highlighted_option
sorted_options = sorted(
directory_list.options + [textual.widgets.option_list.Option(' '.join(pieces), id=file_path)],
key=lambda option: ((option.id == 'loading-indicator'), option.prompt)
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)
directory_list.highlighted = directory_list.get_option_index(highlighted_option.id) if highlighted_option and directory_list.highlighted_option_changed else 0
directory_list.highlighted = (
directory_list.get_option_index(highlighted_option.id)
if highlighted_option and directory_list.highlighted_option_changed
else 0
)
browse_app.call_from_thread(timer.stop)
browse_app.call_from_thread(directory_list.remove_option, 'loading-indicator')
@@ -127,7 +142,9 @@ def add_archive_files(browse_app, directory_list, config, repository, archive_na
@textual.work(thread=True)
def load_file_preview(browse_app, file_preview, config, repository, archive_name, file_path, timer):
file_content = borgmatic.actions.browse.controller.get_archive_file_content(config, repository, archive_name, file_path)
file_content = borgmatic.actions.browse.controller.get_archive_file_content(
config, repository, archive_name, file_path
)
browse_app.call_from_thread(timer.stop)
syntax_lexer = rich.syntax.Syntax.guess_lexer(file_path, file_content)
@@ -153,7 +170,6 @@ class Configuration_files_list(textual.widgets.OptionList):
textual.widgets.option_list.Option(f'{unexpanded_path}', id=config_path)
for config_path in configs.keys()
for unexpanded_path in (config_path.replace(home_directory, '~'),)
),
classes='panel',
)
@@ -206,7 +222,9 @@ class Archives_list(textual.widgets.OptionList):
)
def make_next_panel(self, option_id):
return Directory_list(config=self.config, repository=self.repository, archive_name=option_id)
return Directory_list(
config=self.config, repository=self.repository, archive_name=option_id
)
def on_option_list_option_highlighted(self, event):
if self.highlighted not in (None, 0):
@@ -224,7 +242,9 @@ class Directory_list(textual.widgets.OptionList):
self.highlighted_option_changed = False
super().__init__(classes='panel')
self.border_title = os.path.sep.join(self.path_components) if self.path_components else f'{archive_name}'
self.border_title = (
os.path.sep.join(self.path_components) if self.path_components else f'{archive_name}'
)
timer = add_inline_loading_indicator(self)
@@ -246,9 +266,19 @@ class Directory_list(textual.widgets.OptionList):
return Null_list()
if option.prompt.startswith(PATH_TYPE_ICONS[Path_type.DIRECTORY.value]):
return Directory_list(self.config, self.repository, self.archive_name, path_components=self.path_components + (option_id,))
return Directory_list(
self.config,
self.repository,
self.archive_name,
path_components=self.path_components + (option_id,),
)
return File_preview(self.config, self.repository, self.archive_name, file_path=os.path.sep.join(self.path_components + (option_id,)))
return File_preview(
self.config,
self.repository,
self.archive_name,
file_path=os.path.sep.join(self.path_components + (option_id,)),
)
def on_option_list_option_highlighted(self, event):
if self.highlighted not in (None, 0):
@@ -288,7 +318,9 @@ class File_preview(textual.widgets.Static):
class Carousel(textual.containers.Horizontal):
BINDINGS = [
textual.binding.Binding(key='left,h', action='previous', description='previous', priority=True),
textual.binding.Binding(
key='left,h', action='previous', description='previous', priority=True
),
]
def __init__(self, panels):
@@ -342,7 +374,7 @@ class Carousel(textual.containers.Horizontal):
'''
next_panel_index = self.panels.index(self.focused_panel) + 1
del(self.panels[next_panel_index:])
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':
@@ -391,7 +423,11 @@ class Browse_app(textual.app.App):
def compose(self):
yield textual.widgets.Header()
yield Carousel([Configuration_files_list(self.configs)])
yield Carousel(
[Configuration_files_list(self.configs)]
if len(self.configs) > 1
else [Repositories_list(tuple(self.configs.values())[0])]
)
logs_widget = Logs()
yield logs_widget
+3 -1
View File
@@ -44,7 +44,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
import apprise # noqa: PLC0415
from apprise import NotifyFormat, NotifyType # noqa: PLC0415
except ImportError: # pragma: no cover
logger.warning('Unable to import Apprise in its monitoring hook; try installing "borgmatic[Apprise]"')
logger.warning(
'Unable to import Apprise in its monitoring hook; try installing "borgmatic[Apprise]"'
)
return
state_to_notify_type = {