172 lines
6.5 KiB
Python
172 lines
6.5 KiB
Python
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
def _get_config_dir():
|
|
"""Resolve config directory, robustly handling Nuitka compiled paths."""
|
|
|
|
# 1. Start with the directory of this file
|
|
current_path = Path(__file__).parent
|
|
|
|
# 2. Check strict relative location (standard case)
|
|
# If we are in src/orisys/configs/__init__.py, configs is current_path
|
|
if (current_path / "prototype.json").exists():
|
|
return current_path
|
|
|
|
# 3. Walk up the directory tree to find a 'configs' folder containing 'prototype.json'
|
|
# This handles nested virtual paths like .../orisys/orisys/configs
|
|
# We look for a physical directory that matches our expectation
|
|
|
|
search_path = current_path
|
|
# Limit depth to avoid infinite loops, though unlikely with .parent
|
|
for _ in range(5):
|
|
# Check if 'configs' exists in this directory
|
|
candidate = search_path / "configs"
|
|
if candidate.exists() and candidate.is_dir():
|
|
if (candidate / "prototype.json").exists():
|
|
return candidate
|
|
|
|
# Also check if the current directory ITSELF is the configs dir we missed
|
|
# (e.g. if we are somehow inside configs but missed check 1)
|
|
if search_path.name == 'configs' and (search_path / "prototype.json").exists():
|
|
return search_path
|
|
|
|
if search_path.parent == search_path: # Reached root
|
|
break
|
|
search_path = search_path.parent
|
|
|
|
# 4. Try finding 'orisys' package root via sys.modules (if loaded)
|
|
try:
|
|
if 'orisys' in sys.modules:
|
|
mod = sys.modules['orisys']
|
|
if hasattr(mod, '__file__') and mod.__file__:
|
|
pkg_root = Path(mod.__file__).parent
|
|
# pkg_root is .../site-packages/orisys
|
|
candidate = pkg_root / "configs"
|
|
if candidate.exists() and (candidate / "prototype.json").exists():
|
|
return candidate
|
|
except Exception:
|
|
pass
|
|
|
|
# Fallback to current path if nothing else works
|
|
return current_path
|
|
|
|
CONFIG_DIR = _get_config_dir()
|
|
|
|
def load_config(config_name_or_path):
|
|
"""
|
|
Load sensor configuration from JSON file.
|
|
|
|
Accepts either:
|
|
1. A file path (absolute or relative) to a JSON config file
|
|
2. A config name (without .json extension) to load from package configs directory
|
|
|
|
Args:
|
|
config_name_or_path: Either:
|
|
- A file path string (e.g., "/path/to/config.json" or "./config.json")
|
|
- A config name string (e.g., "prototype") to load from package configs
|
|
|
|
Returns:
|
|
Dictionary containing the configuration
|
|
|
|
Raises:
|
|
FileNotFoundError: If the config file doesn't exist
|
|
ValueError: If the file path is not a JSON file
|
|
"""
|
|
# Convert to Path object for easier handling
|
|
config_path = Path(config_name_or_path)
|
|
|
|
# Check if it's an existing file (absolute or relative path)
|
|
if config_path.exists() and config_path.is_file():
|
|
# User provided a file path
|
|
if config_path.suffix.lower() != '.json':
|
|
raise ValueError(f"Config file must be a JSON file: {config_path}")
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
|
|
# If it's an absolute path but doesn't exist, raise error immediately
|
|
if config_path.is_absolute():
|
|
raise FileNotFoundError(f"Config file not found at absolute path: {config_path}")
|
|
|
|
# Otherwise, treat it as a config name and look in package directory
|
|
package_config_path = CONFIG_DIR / f"{config_name_or_path}.json"
|
|
|
|
if not package_config_path.exists():
|
|
# List available configs for better error message
|
|
available = [f.stem for f in CONFIG_DIR.glob("*.json")]
|
|
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 package config: {package_config_path} (not found)\n"
|
|
f" Available package configs: {available_str}"
|
|
)
|
|
|
|
with open(package_config_path, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
|
|
def list_config():
|
|
"""
|
|
List all available JSON configuration files in the configs directory.
|
|
|
|
Returns:
|
|
List of configuration names (without .json extension), sorted alphabetically.
|
|
Returns an empty list if no JSON files are found.
|
|
"""
|
|
config_files = sorted(CONFIG_DIR.glob("*.json"))
|
|
return [f.stem for f in config_files]
|
|
|
|
def export_config(config_name = None, path="./config/exported_config.json"):
|
|
"""
|
|
Export configuration to JSON file with readable formatting.
|
|
Arrays/lists are kept on a single line, while nested objects are indented.
|
|
|
|
Args:
|
|
config: Configuration dictionary to export
|
|
path: Output file path (default: "./config/exported_config.json")
|
|
|
|
Returns:
|
|
The exported config dictionary
|
|
"""
|
|
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') # Add trailing newline
|
|
|
|
print(f"Config name: {config_name} exported to {path}")
|
|
return config
|
|
|
|
def _format_json_value(value, indent_level=0, indent_str=" "):
|
|
"""
|
|
Recursively format JSON value with arrays/lists on single line.
|
|
|
|
Args:
|
|
value: The value to format (dict, list, or primitive)
|
|
indent_level: Current indentation level
|
|
indent_str: String used for indentation (default: 4 spaces)
|
|
|
|
Returns:
|
|
Formatted string representation
|
|
"""
|
|
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 + "}"
|
|
elif isinstance(value, list):
|
|
if not value:
|
|
return "[]"
|
|
# Format arrays/lists on a single line
|
|
items = [json.dumps(item, ensure_ascii=False) for item in value]
|
|
return "[" + ", ".join(items) + "]"
|
|
else:
|
|
# Primitive types (string, number, bool, None)
|
|
return json.dumps(value, ensure_ascii=False) |