feat: add Gallery TUI for interactive configuration and generation
- Implemented a terminal user interface (TUI) for configuring the gallery generator using Textual. - Included functionality to manage sources with add/remove - Integrated a log pane to display generation output from subprocess calls. - Implemented save functionality to persist configuration changes.
This commit is contained in:
+34
-10
@@ -14,7 +14,10 @@ from pathlib import Path
|
||||
import argcomplete
|
||||
|
||||
from gallery import generate
|
||||
from gallery.config import ConfigManager, GalleryConfig, GallerySource, default_config_path
|
||||
from gallery.config import (
|
||||
ConfigManager, GalleryConfig, GallerySource,
|
||||
default_config_path, get_active_config_path, ensure_user_config,
|
||||
)
|
||||
|
||||
_WELCOME = """\
|
||||
Gallery - Scientific Plot Gallery Generator
|
||||
@@ -67,7 +70,7 @@ def _is_configured(config_path: Path) -> bool:
|
||||
def _source_names(prefix, parsed_args, **kwargs):
|
||||
"""Autocomplete helper: return configured source names."""
|
||||
try:
|
||||
config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else default_config_path()
|
||||
config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else get_active_config_path()
|
||||
return [s["name"] for s in ConfigManager(config_path).list_sources()]
|
||||
except Exception:
|
||||
return []
|
||||
@@ -76,7 +79,7 @@ def _source_names(prefix, parsed_args, **kwargs):
|
||||
def _config_keys(prefix, parsed_args, **kwargs):
|
||||
"""Autocomplete helper: return known dot-notation config keys."""
|
||||
try:
|
||||
config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else default_config_path()
|
||||
config_path = Path(parsed_args.config) if getattr(parsed_args, "config", None) else get_active_config_path()
|
||||
data = ConfigManager(config_path).list_all()
|
||||
keys = []
|
||||
for section, values in data.items():
|
||||
@@ -94,6 +97,7 @@ def _config_keys(prefix, parsed_args, **kwargs):
|
||||
# Parser construction (separated so TUI can reuse the structure)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="gallery",
|
||||
@@ -135,6 +139,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
cfg_sub = cfg.add_subparsers(dest="action", metavar="ACTION")
|
||||
|
||||
sub.add_parser("install-completion", help="Install shell tab-completion (bash/zsh)")
|
||||
sub.add_parser("tui", help="Launch the interactive TUI")
|
||||
|
||||
cfg_sub.add_parser("list", help="Print all config values")
|
||||
cfg_sub.add_parser("path", help="Print the resolved config file path")
|
||||
@@ -149,8 +154,9 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
p_add = cfg_sub.add_parser("add-source", help="Add a plot source")
|
||||
p_add.add_argument("--name", default=None, help="Source name (default: bottom-level directory name)")
|
||||
p_add.add_argument("--path", required=True, metavar="DIR",
|
||||
help="Path to source directory").completer = argcomplete.completers.DirectoriesCompleter()
|
||||
p_add.add_argument("--path", required=True, metavar="DIR", help="Path to source directory").completer = (
|
||||
argcomplete.completers.DirectoriesCompleter()
|
||||
)
|
||||
|
||||
p_rm = cfg_sub.add_parser("remove-source", help="Remove a plot source by name")
|
||||
p_rm.add_argument("name", help="Source name to remove").completer = _source_names
|
||||
@@ -162,8 +168,9 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
# Command handlers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _run_generate(args: argparse.Namespace) -> int:
|
||||
config_path = Path(args.config) if args.config else default_config_path()
|
||||
config_path = Path(args.config) if args.config else get_active_config_path()
|
||||
try:
|
||||
config = GalleryConfig.from_yaml(config_path)
|
||||
|
||||
@@ -179,8 +186,12 @@ def _run_generate(args: argparse.Namespace) -> int:
|
||||
else:
|
||||
source_to_update = matching
|
||||
|
||||
success = generate(config=config, clean_first=args.clean, verbose=args.verbose,
|
||||
source_to_update=source_to_update)
|
||||
success = generate(
|
||||
config=config,
|
||||
clean_first=args.clean,
|
||||
verbose=args.verbose,
|
||||
source_to_update=source_to_update,
|
||||
)
|
||||
return 0 if success else 1
|
||||
|
||||
except (FileNotFoundError, ValueError) as e:
|
||||
@@ -192,7 +203,13 @@ def _run_generate(args: argparse.Namespace) -> int:
|
||||
|
||||
|
||||
def _run_config(args: argparse.Namespace) -> int:
|
||||
config_path = Path(args.config) if args.config else default_config_path()
|
||||
if args.config:
|
||||
config_path = Path(args.config)
|
||||
elif args.action in ("set", "add-source", "remove-source"):
|
||||
# Writes go to the user config; create it from the template if needed
|
||||
config_path = ensure_user_config()
|
||||
else:
|
||||
config_path = get_active_config_path()
|
||||
mgr = ConfigManager(config_path)
|
||||
|
||||
if args.action == "path":
|
||||
@@ -200,6 +217,7 @@ def _run_config(args: argparse.Namespace) -> int:
|
||||
|
||||
elif args.action == "list":
|
||||
import yaml
|
||||
|
||||
print(yaml.dump(mgr.list_all(), default_flow_style=False).rstrip())
|
||||
|
||||
elif args.action == "sources":
|
||||
@@ -280,6 +298,7 @@ def _run_install_completion() -> int:
|
||||
# Entry point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def main():
|
||||
parser = build_parser()
|
||||
argcomplete.autocomplete(parser) # no-op when not completing; exits during completion
|
||||
@@ -291,8 +310,13 @@ def main():
|
||||
sys.exit(_run_config(args))
|
||||
elif args.command == "install-completion":
|
||||
sys.exit(_run_install_completion())
|
||||
elif args.command == "tui":
|
||||
from gallery.tui import GalleryTUI
|
||||
config_path = Path(args.config) if args.config else None
|
||||
GalleryTUI(config_path=config_path).run()
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(_WELCOME.format(config_path=default_config_path()))
|
||||
print(_WELCOME.format(config_path=get_active_config_path()))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user