import json from pathlib import Path def _candidate_config_dirs() -> list[Path]: """ Candidate config directories for editable installs and packaged wheels. """ here = Path(__file__).resolve().parent candidates = [ here / "configs", # packaged wheel layout: orisys/configs/*.json here.parent.parent / "config", # editable install from repo: repo_root/config/*.json here.parent / "config", # fallback Path.cwd() / "config", # script launched from repo root ] unique: list[Path] = [] for path in candidates: if path not in unique: unique.append(path) return unique def _get_config_dir() -> Path: for candidate in _candidate_config_dirs(): if candidate.exists() and any(candidate.glob("*.json")): return candidate # Default to the first candidate so error messages are stable. return _candidate_config_dirs()[0] CONFIG_DIR = _get_config_dir() def _config_files() -> list[Path]: if not CONFIG_DIR.exists(): return [] return sorted(CONFIG_DIR.glob("*.json")) def _load_json(path: Path) -> dict: with open(path, "r", encoding="utf-8") as f: return json.load(f) def load_config(config_name_or_path): """ Load sensor configuration from a JSON file path or config name. Config lookup order: 1. Exact file path (absolute or relative) 2. File stem under the config directory (e.g. ``ddjx01`` -> ``config/ddjx01.json``) 3. JSON field ``"name"`` matching the requested name (keeps ``prototype`` compatible) """ config_path = Path(config_name_or_path) if config_path.exists() and config_path.is_file(): if config_path.suffix.lower() != ".json": raise ValueError(f"Config file must be a JSON file: {config_path}") return _load_json(config_path) if config_path.is_absolute(): raise FileNotFoundError(f"Config file not found at absolute path: {config_path}") direct = CONFIG_DIR / f"{config_name_or_path}.json" if direct.exists(): return _load_json(direct) # Backward-compatible alias resolution: support old names via JSON "name" field. for candidate in _config_files(): try: data = _load_json(candidate) except Exception: continue if data.get("name") == config_name_or_path: return data available = [f.stem for f in _config_files()] available_str = ", ".join(available) if available else "none" raise FileNotFoundError( f"Input config file not found with name or path: {config_name_or_path}\n" f" Tried as file path: {config_path} (not found)\n" f" Tried as config file: {direct} (not found)\n" f" Available config files: {available_str}" ) def list_config(): """ List all available JSON configuration files by file stem. """ return [f.stem for f in _config_files()] def export_config(config_name=None, path="./config/exported_config.json"): """ Export a configuration to a JSON file with readable formatting. """ config = load_config(config_name) Path(path).parent.mkdir(parents=True, exist_ok=True) with open(path, "w", encoding="utf-8") as f: formatted = _format_json_value(config) f.write(formatted) f.write("\n") print(f"Config name: {config_name} exported to {path}") return config def _format_json_value(value, indent_level=0, indent_str=" "): if isinstance(value, dict): if not value: return "{}" items = [] for k, v in value.items(): key_str = json.dumps(k, ensure_ascii=False) value_str = _format_json_value(v, indent_level + 1, indent_str) items.append(f"{indent_str * (indent_level + 1)}{key_str}: {value_str}") return "{\n" + ",\n".join(items) + "\n" + indent_str * indent_level + "}" if isinstance(value, list): if not value: return "[]" items = [json.dumps(item, ensure_ascii=False) for item in value] return "[" + ", ".join(items) + "]" return json.dumps(value, ensure_ascii=False)