from typing import List, Dict, Any, Optional, Sequence from cmdlet._shared import Cmdlet, CmdletArg from SYS.config import load_config, save_config from SYS import pipeline as ctx from SYS.result_table import ResultTable CMDLET = Cmdlet( name=".config", summary="Manage configuration settings", usage=".config [key] [value]", arg=[ CmdletArg( name="key", description="Configuration key to update (dot-separated)", required=False ), CmdletArg( name="value", description="New value for the configuration key", required=False ), ], ) def flatten_config(config: Dict[str, Any], parent_key: str = "", sep: str = ".") -> List[Dict[str, Any]]: items: List[Dict[str, Any]] = [] for k, v in config.items(): if k.startswith("_"): 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: items.append({ "key": new_key, "value": v, "value_display": str(v), "type": type(v).__name__, }) return items def set_nested_config(config: Dict[str, Any], key: str, value: str) -> bool: keys = key.split(".") d = config # 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] last_key = keys[-1] # Try to preserve type if key exists if last_key in d: current_val = d[last_key] if isinstance(current_val, bool): if value.lower() in ("true", "yes", "1", "on"): d[last_key] = True elif value.lower() in ("false", "no", "0", "off"): 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 if value.lower() in ("true", "false"): d[last_key] = value.lower() == "true" elif value.isdigit(): d[last_key] = int(value) else: d[last_key] = value return True 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 def _extract_value_arg(args: Sequence[str]) -> Optional[str]: if not args: 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")) table = ResultTable("Configuration") table.set_table("config") table.set_source_command(".config", []) 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", "")) 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 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 " ) 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] if len(args) < 2: print(f"Error: Value required for key '{key}'") return 1 value = _strip_value_quotes(" ".join(args[1:])) try: set_nested_config(current_config, key, value) save_config(current_config) print(f"Updated '{key}' to '{value}'") return 0 except Exception as exc: print(f"Error updating config: {exc}") return 1 CMDLET.exec = _run