2026-01-09 01:22:06 -08:00
|
|
|
from typing import List, Dict, Any, Optional, Sequence
|
2025-12-11 23:21:45 -08:00
|
|
|
|
2025-12-12 21:55:38 -08:00
|
|
|
from cmdlet._shared import Cmdlet, CmdletArg
|
2025-12-29 18:42:02 -08:00
|
|
|
from SYS.config import load_config, save_config
|
2026-01-09 01:22:06 -08:00
|
|
|
from SYS import pipeline as ctx
|
|
|
|
|
from SYS.result_table import ResultTable
|
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(
|
2025-12-29 18:42:02 -08:00
|
|
|
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:
|
2025-12-29 17:05:03 -08:00
|
|
|
keys = key.split(".")
|
2025-11-25 20:09:33 -08:00
|
|
|
d = config
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
# Navigate to the parent dict
|
|
|
|
|
for k in keys[:-1]:
|
|
|
|
|
if k not in d or not isinstance(d[k], dict):
|
|
|
|
|
d[k] = {}
|
|
|
|
|
d = d[k]
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
last_key = keys[-1]
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
# Try to preserve type if key exists
|
|
|
|
|
if last_key in d:
|
|
|
|
|
current_val = d[last_key]
|
|
|
|
|
if isinstance(current_val, bool):
|
2025-12-29 17:05:03 -08:00
|
|
|
if value.lower() in ("true", "yes", "1", "on"):
|
2025-11-25 20:09:33 -08:00
|
|
|
d[last_key] = True
|
2025-12-29 17:05:03 -08:00
|
|
|
elif value.lower() in ("false", "no", "0", "off"):
|
2025-11-25 20:09:33 -08:00
|
|
|
d[last_key] = False
|
|
|
|
|
else:
|
|
|
|
|
# Fallback to boolean conversion of string (usually True for non-empty)
|
|
|
|
|
# But for config, explicit is better.
|
|
|
|
|
print(f"Warning: Could not convert '{value}' to boolean. Using string.")
|
|
|
|
|
d[last_key] = value
|
|
|
|
|
elif isinstance(current_val, int):
|
|
|
|
|
try:
|
|
|
|
|
d[last_key] = int(value)
|
|
|
|
|
except ValueError:
|
|
|
|
|
print(f"Warning: Could not convert '{value}' to int. Using string.")
|
|
|
|
|
d[last_key] = value
|
|
|
|
|
elif isinstance(current_val, float):
|
|
|
|
|
try:
|
|
|
|
|
d[last_key] = float(value)
|
|
|
|
|
except ValueError:
|
|
|
|
|
print(f"Warning: Could not convert '{value}' to float. Using string.")
|
|
|
|
|
d[last_key] = value
|
|
|
|
|
else:
|
|
|
|
|
d[last_key] = value
|
|
|
|
|
else:
|
|
|
|
|
# New key, try to infer type
|
2025-12-29 17:05:03 -08:00
|
|
|
if value.lower() in ("true", "false"):
|
|
|
|
|
d[last_key] = value.lower() == "true"
|
2025-11-25 20:09:33 -08:00
|
|
|
elif value.isdigit():
|
|
|
|
|
d[last_key] = int(value)
|
|
|
|
|
else:
|
|
|
|
|
d[last_key] = value
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
return True
|
|
|
|
|
|
2025-12-29 17:05:03 -08:00
|
|
|
|
2026-01-09 01:22:06 -08:00
|
|
|
def _extract_piped_value(result: Any) -> Optional[str]:
|
|
|
|
|
if isinstance(result, str):
|
|
|
|
|
return result.strip() if result.strip() else None
|
|
|
|
|
if isinstance(result, (int, float)):
|
|
|
|
|
return str(result)
|
|
|
|
|
if isinstance(result, dict):
|
|
|
|
|
val = result.get("value")
|
|
|
|
|
if val is not None:
|
|
|
|
|
return str(val).strip()
|
|
|
|
|
return None
|
2025-12-29 17:05:03 -08:00
|
|
|
|
|
|
|
|
|
2026-01-09 01:22:06 -08:00
|
|
|
def _extract_value_arg(args: Sequence[str]) -> Optional[str]:
|
2025-11-25 20:09:33 -08:00
|
|
|
if not args:
|
2026-01-09 01:22:06 -08:00
|
|
|
return None
|
|
|
|
|
tokens = [str(tok) for tok in args if tok is not None]
|
|
|
|
|
flags = {"-value", "--value", "-set-value", "--set-value"}
|
|
|
|
|
for idx, tok in enumerate(tokens):
|
|
|
|
|
text = tok.strip()
|
|
|
|
|
if not text:
|
|
|
|
|
continue
|
|
|
|
|
low = text.lower()
|
|
|
|
|
if low in flags and idx + 1 < len(tokens):
|
|
|
|
|
candidate = str(tokens[idx + 1]).strip()
|
|
|
|
|
if candidate:
|
|
|
|
|
return candidate
|
|
|
|
|
if "=" in low:
|
|
|
|
|
head, val = low.split("=", 1)
|
|
|
|
|
if head in flags and val:
|
|
|
|
|
return val.strip()
|
|
|
|
|
for tok in tokens:
|
|
|
|
|
text = str(tok).strip()
|
|
|
|
|
if text and not text.startswith("-"):
|
|
|
|
|
return text
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-09 01:22:06 -08:00
|
|
|
table = ResultTable("Configuration")
|
|
|
|
|
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:
|
|
|
|
|
current_config = load_config()
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
save_config(current_config)
|
|
|
|
|
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:
|
|
|
|
|
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)
|
|
|
|
|
save_config(current_config)
|
|
|
|
|
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
|