Files
Medios-Macina/cmdnat/config.py

209 lines
7.1 KiB
Python
Raw Normal View History

2026-01-09 01:22:06 -08:00
from typing import List, Dict, Any, Optional, Sequence
2025-12-11 23:21:45 -08:00
2026-02-11 19:06:38 -08:00
from SYS.cmdlet_spec import Cmdlet, CmdletArg
2026-03-25 22:39:30 -07:00
from SYS.config import (
load_config,
save_config,
save_config_and_verify,
set_nested_config_value,
)
2026-01-09 01:22:06 -08:00
from SYS import pipeline as ctx
2026-01-18 10:50:42 -08:00
from SYS.result_table import Table
2026-03-25 22:39:30 -07:00
from cmdnat._parsing import (
extract_piped_value as _extract_piped_value,
extract_value_arg as _extract_value_arg,
)
2025-11-25 20:09:33 -08:00
CMDLET = Cmdlet(
name=".config",
summary="Manage configuration settings",
usage=".config [key] [value]",
2025-12-11 12:47:30 -08:00
arg=[
2025-11-25 20:09:33 -08:00
CmdletArg(
name="key",
description="Configuration key to update (dot-separated)",
required=False
),
CmdletArg(
name="value",
description="New value for the configuration key",
required=False
2025-11-25 20:09:33 -08:00
),
2025-12-29 17:05:03 -08:00
],
2025-11-25 20:09:33 -08:00
)
2025-12-29 17:05:03 -08:00
2026-01-09 01:22:06 -08:00
def flatten_config(config: Dict[str, Any], parent_key: str = "", sep: str = ".") -> List[Dict[str, Any]]:
items: List[Dict[str, Any]] = []
2025-11-25 20:09:33 -08:00
for k, v in config.items():
2026-01-09 01:22:06 -08:00
if k.startswith("_"):
2025-11-25 20:09:33 -08:00
continue
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_config(v, new_key, sep=sep))
else:
2026-01-09 01:22:06 -08:00
items.append({
"key": new_key,
"value": v,
"value_display": str(v),
"type": type(v).__name__,
})
2025-11-25 20:09:33 -08:00
return items
2025-12-29 17:05:03 -08:00
2025-11-25 20:09:33 -08:00
def set_nested_config(config: Dict[str, Any], key: str, value: str) -> bool:
2026-03-25 22:39:30 -07:00
return set_nested_config_value(config, key, value, on_error=print)
2026-01-09 01:22:06 -08:00
def _get_selected_config_key() -> Optional[str]:
try:
indices = ctx.get_last_selection() or []
except Exception:
indices = []
try:
items = ctx.get_last_result_items() or []
except Exception:
items = []
if not indices or not items:
return None
idx = indices[0]
if idx < 0 or idx >= len(items):
return None
item = items[idx]
if isinstance(item, dict):
return item.get("key")
return getattr(item, "key", None)
def _show_config_table(config_data: Dict[str, Any]) -> int:
items = flatten_config(config_data)
if not items:
print("No configuration entries available.")
return 0
items.sort(key=lambda x: x.get("key"))
2025-12-29 17:05:03 -08:00
2026-01-18 10:50:42 -08:00
table = Table("Configuration")
2026-01-09 01:22:06 -08:00
table.set_table("config")
table.set_source_command(".config", [])
2025-12-29 17:05:03 -08:00
2026-01-09 01:22:06 -08:00
for item in items:
row = table.add_row()
row.add_column("Key", item.get("key", ""))
row.add_column("Value", item.get("value_display", ""))
row.add_column("Type", item.get("type", ""))
2025-12-29 17:05:03 -08:00
2026-01-09 01:22:06 -08:00
ctx.set_last_result_table_overlay(table, items)
ctx.set_current_stage_table(table)
return 0
def _strip_value_quotes(value: str) -> str:
if not value:
return value
if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
return value[1:-1]
return value
2025-12-29 17:05:03 -08:00
2026-01-09 01:22:06 -08:00
def _run(piped_result: Any, args: List[str], config: Dict[str, Any]) -> int:
2026-01-11 00:39:17 -08:00
import sys
2026-01-23 18:40:00 -08:00
# Load configuration from the database
current_config = load_config()
2026-01-09 01:22:06 -08:00
selection_key = _get_selected_config_key()
value_from_args = _extract_value_arg(args) if selection_key else None
value_from_pipe = _extract_piped_value(piped_result)
if selection_key:
new_value = value_from_pipe or value_from_args
if not new_value:
print(
"Provide a new value via pipe or argument: @N | .config <value>"
)
return 1
new_value = _strip_value_quotes(new_value)
try:
set_nested_config(current_config, selection_key, new_value)
2026-01-31 21:32:51 -08:00
# For AllDebrid API changes, use the verified save path to ensure
# the new API key persisted to disk; otherwise fall back to normal save.
try:
key_l = str(selection_key or "").lower()
except Exception:
key_l = ""
if "alldebrid" in key_l or "all-debrid" in key_l:
try:
save_config_and_verify(current_config)
except Exception as exc:
print(f"Error saving configuration (verification failed): {exc}")
return 1
else:
save_config(current_config)
2026-01-09 01:22:06 -08:00
print(f"Updated '{selection_key}' to '{new_value}'")
return 0
except Exception as exc:
print(f"Error updating config: {exc}")
return 1
if not args:
2026-01-11 00:39:17 -08:00
# Check if we're in an interactive terminal and can launch a Textual modal
if sys.stdin.isatty() and not piped_result:
try:
2026-01-19 03:14:30 -08:00
from textual.app import App
2026-01-11 00:39:17 -08:00
from TUI.modalscreen.config_modal import ConfigModal
class ConfigApp(App):
def on_mount(self) -> None:
self.title = "Config Editor"
# We push the modal screen. It will sit on top of the main (blank) screen.
# Using a callback to exit the app when the modal is dismissed.
self.push_screen(ConfigModal(), callback=self.exit_on_close)
def exit_on_close(self, result: Any = None) -> None:
self.exit()
with ctx.suspend_live_progress():
app = ConfigApp()
app.run()
# After modal exits, show the new status table if possible
try:
from cmdlet._shared import SharedArgs
from cmdnat.status import CMDLET as STATUS_CMDLET
# We reload the config one more time because it might have changed on disk
2026-01-23 18:40:00 -08:00
fresh_config = load_config()
2026-01-11 00:39:17 -08:00
# Force refresh of shared caches (especially stores)
SharedArgs._refresh_store_choices_cache(fresh_config)
# Update the global SharedArgs choices so cmdlets pick up new stores
SharedArgs.STORE.choices = SharedArgs.get_store_choices(fresh_config, force=True)
return STATUS_CMDLET.exec(None, [], fresh_config)
except Exception:
pass
return 0
except Exception as exc:
# Fall back to table display if Textual modal fails
print(f"Note: Could not launch interactive editor ({exc}). Showing configuration table:")
return _show_config_table(current_config)
2026-01-09 01:22:06 -08:00
return _show_config_table(current_config)
key = args[0]
2025-11-25 20:09:33 -08:00
if len(args) < 2:
print(f"Error: Value required for key '{key}'")
return 1
2025-12-29 17:05:03 -08:00
2026-01-09 01:22:06 -08:00
value = _strip_value_quotes(" ".join(args[1:]))
2025-11-25 20:09:33 -08:00
try:
set_nested_config(current_config, key, value)
2026-01-23 18:40:00 -08:00
save_config(current_config)
2025-11-25 20:09:33 -08:00
print(f"Updated '{key}' to '{value}'")
return 0
2026-01-09 01:22:06 -08:00
except Exception as exc:
print(f"Error updating config: {exc}")
2025-11-25 20:09:33 -08:00
return 1
2025-12-29 17:05:03 -08:00
2025-11-25 20:09:33 -08:00
CMDLET.exec = _run