Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions src/toolManager/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
Configuration and State Management for eSim Tool Manager.
Standardizes access to information.json and other settings.
"""

import json
import logging
from pathlib import Path
from typing import Dict, List, Optional, Any
from datetime import datetime

# Import local paths helper with robust fallback
try:
from .paths import get_install_state_path
except (ImportError, ValueError):
try:
from paths import get_install_state_path
except ImportError:
def get_install_state_path():
return Path(__file__).resolve().parent / "information.json"

logger = logging.getLogger(__name__)


class ConfigManager:
"""
Handles persistence of tool installation states and manager settings.
"""

def __init__(self, config_path: Optional[Path] = None, autosave: bool = True):
self.config_path = config_path or get_install_state_path()
self.autosave = autosave
self._data: Dict[str, Any] = self._load_defaults()
self.load()

def _load_defaults(self) -> Dict[str, Any]:
"""Returns the default schema for the configuration file."""
return {
"important_packages": [],
"settings": {
"last_update_check": None,
"preferred_category": "analog",
"auto_refresh": True
},
"metadata": {
"schema_version": "1.0.0",
"created_at": datetime.now().isoformat(),
"last_modified": datetime.now().isoformat()
}
}

def load(self) -> bool:
"""Loads configuration from the JSON file."""
if not self.config_path.exists():
return False

try:
with open(self.config_path, 'r', encoding='utf-8') as f:
loaded_data = json.load(f)
# Basic merge to ensure schema consistency
if isinstance(loaded_data, dict):
self._data.update(loaded_data)
return True
except (json.JSONDecodeError, IOError) as e:
logger.error(f"Failed to load config from {self.config_path}: {e}")
return False

def save(self) -> bool:
"""Saves current state to the JSON file atomically."""
try:
# Update metadata
if "metadata" not in self._data:
self._data["metadata"] = {}
self._data["metadata"]["last_modified"] = datetime.now().isoformat()

# Ensure directory exists
self.config_path.parent.mkdir(parents=True, exist_ok=True)

# Write to a temporary file first for atomicity
temp_path = self.config_path.with_suffix(".tmp")
with open(temp_path, 'w', encoding='utf-8') as f:
json.dump(self._data, f, indent=4)

# Replace original file with temporary one
# On Windows, replace() can fail if destination is open, but this is
# the standard way for atomic-like writes in Python.
temp_path.replace(self.config_path)
return True
except IOError as e:
logger.error(f"Failed to save config to {self.config_path}: {e}")
return False

def get_tool_state(self, tool_id: str) -> Optional[Dict[str, Any]]:
"""Retrieves the installation state for a specific tool."""
packages = self._data.get("important_packages", [])
for pkg in packages:
if pkg.get("package_name") == tool_id:
return pkg
return None

def update_tool_state(self, tool_id: str, version: str, status: str = "installed") -> None:
"""Updates or adds an entry for a tool's installation state."""
packages = self._data.get("important_packages", [])
found = False

for pkg in packages:
if pkg.get("package_name") == tool_id:
pkg["version"] = version
pkg["status"] = status
pkg["installed_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
found = True
break

if not found:
packages.append({
"package_name": tool_id,
"version": version,
"status": status,
"installed_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
})

self._data["important_packages"] = packages
if self.autosave:
self.save()

def get_setting(self, key: str, default: Any = None) -> Any:
"""Retrieves a specific setting from the manager configuration."""
return self._data.get("settings", {}).get(key, default)

def set_setting(self, key: str, value: Any) -> None:
"""Updates a manager setting."""
if "settings" not in self._data:
self._data["settings"] = {}
self._data["settings"][key] = value
if self.autosave:
self.save()

@property
def data(self) -> Dict[str, Any]:
"""Returns the raw configuration data."""
return self._data
32 changes: 32 additions & 0 deletions src/toolManager/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
"""
Shared constants and platform identification for eSim Tool Manager.
"""

import sys
import platform
from pathlib import Path

# Platform Identification
IS_WINDOWS = sys.platform == "win32"
IS_LINUX = sys.platform.startswith("linux")
IS_MACOS = sys.platform == "darwin"

def get_os_id() -> str:
"""Returns a standardized OS identifier string used in registry keys."""
if IS_WINDOWS:
return "win32"
if IS_LINUX:
return "linux"
if IS_MACOS:
return "darwin"
return "unknown"

# Default Windows Installation Paths (Fallbacks)
# These are used when tools are not found in the system PATH.
DEFAULT_MSYS2_PATH = Path(r"C:\msys64")
DEFAULT_ESIM_DIR = Path(r"C:\FOSSEE\eSim")

# Tool Manager Defaults
DEFAULT_INFO_FILE = "information.json"
DEFAULT_DOWNLOAD_DIR = "Download"
86 changes: 86 additions & 0 deletions src/toolManager/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
"""
Platform-aware path resolution for eSim Tool Manager.
Uses pathlib for cross-platform compatibility.
"""

import os
import sys
import platform
from pathlib import Path
from typing import Optional

try:
from .constants import (
DEFAULT_MSYS2_PATH, DEFAULT_ESIM_DIR,
DEFAULT_INFO_FILE, DEFAULT_DOWNLOAD_DIR,
IS_WINDOWS
)
except (ImportError, ValueError):
from constants import (
DEFAULT_MSYS2_PATH, DEFAULT_ESIM_DIR,
DEFAULT_INFO_FILE, DEFAULT_DOWNLOAD_DIR,
IS_WINDOWS
)


def get_toolmanager_root() -> Path:
"""Returns the absolute path to the toolManager directory."""
return Path(__file__).resolve().parent


def get_application_root() -> Path:
"""Returns the absolute path to the eSim application root (parent of src)."""
# Assuming the structure: esim_root/src/toolManager/paths.py
# This traverses up from toolManager -> src -> esim_root
return get_toolmanager_root().parent.parent


def get_download_cache_dir() -> Path:
"""Returns the directory used for caching downloads."""
path = get_toolmanager_root() / DEFAULT_DOWNLOAD_DIR
path.mkdir(parents=True, exist_ok=True)
return path


def get_install_state_path() -> Path:
"""Returns the path to the information.json state file."""
return get_toolmanager_root() / DEFAULT_INFO_FILE


def get_temp_dir() -> Path:
"""Returns a platform-appropriate temporary directory for toolManager."""
if IS_WINDOWS:
temp = Path(os.environ.get("TEMP", "C:\\temp")) / "eSimToolManager"
else:
temp = Path("/tmp/eSimToolManager")

temp.mkdir(parents=True, exist_ok=True)
return temp


def resolve_user_data_dir() -> Path:
"""
Returns the user data directory for eSim configuration.
Follows OS standards (AppData on Windows, ~/.config on Linux).
"""
if IS_WINDOWS:
return Path(os.environ.get("APPDATA", "~")).expanduser() / "eSim"
else:
return Path("~/.config/eSim").expanduser()


def get_msys2_default_root() -> Path:
"""
Returns the default MSYS2 installation path on Windows.
Used as a fallback when MSYS2 is not in the system PATH.
"""
return DEFAULT_MSYS2_PATH


def get_esim_default_install_dir() -> Path:
"""
Returns the default eSim installation path on Windows.
Used as a fallback when eSim environment variables are not set.
"""
return DEFAULT_ESIM_DIR
126 changes: 126 additions & 0 deletions src/toolManager/registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
"""
Tool Metadata Registry for eSim Tool Manager.
Consolidates tool definitions, versions, and platform support.
"""

from dataclasses import dataclass, field
from typing import List, Dict, Optional, Any
try:
from .constants import get_os_id
except (ImportError, ValueError):
from constants import get_os_id


@dataclass(frozen=True)
class ToolSpec:
"""
Metadata specification for an external tool managed by eSim.
"""
id: str
label: str
category: str # 'analog', 'digital', 'core', 'system'
versions: List[str]
default_version: str = "latest"
executables: Dict[str, str] = field(default_factory=dict) # OS id -> exe_name
env_vars: List[str] = field(default_factory=list)
description: str = ""
is_experimental: bool = False

def get_executable(self, os_id: Optional[str] = None) -> Optional[str]:
"""Returns the executable name for the specified or current OS."""
target_os = os_id or get_os_id()
return self.executables.get(target_os)


# Centralized tool definitions
TOOLS = {
"esim": ToolSpec(
id="esim",
label="eSim",
category="core",
versions=["latest", "2.4", "2.3", "2.2"],
executables={"win32": "eSim.bat", "linux": "esim", "darwin": "esim"},
description="The main eSim EDA platform."
),
"kicad": ToolSpec(
id="kicad",
label="KiCad",
category="analog",
versions=["latest", "9", "8", "7", "6"],
executables={"win32": "kicad.exe", "linux": "kicad", "darwin": "kicad"},
description="Schematic capture and PCB layout suite."
),
"ngspice": ToolSpec(
id="ngspice",
label="Ngspice",
category="analog",
versions=["latest", "42", "41", "39", "38", "37", "36", "35"],
executables={"win32": "ngspice.exe", "linux": "ngspice", "darwin": "ngspice"},
description="General-purpose circuit simulator."
),
"ghdl": ToolSpec(
id="ghdl",
label="GHDL",
category="digital",
versions=["latest", "5.1.1", "5.0.0", "4.1.0", "4.0.0"],
executables={"win32": "ghdl.exe", "linux": "ghdl", "darwin": "ghdl"},
description="VHDL simulator."
),
"verilator": ToolSpec(
id="verilator",
label="Verilator",
category="digital",
versions=["latest", "5.032", "5.026", "5.018", "5.006"],
executables={"win32": "verilator.exe", "linux": "verilator", "darwin": "verilator"},
description="Fast Verilog/SystemVerilog simulator."
),
"llvm": ToolSpec(
id="llvm",
label="LLVM",
category="digital",
versions=["latest", "19", "18", "17", "16", "15", "14", "13"],
executables={"win32": "clang.exe", "linux": "clang", "darwin": "clang"},
description="Collection of modular and reusable compiler and toolchain technologies."
),
"chocolatey": ToolSpec(
id="chocolatey",
label="Chocolatey",
category="system",
versions=["latest"],
executables={"win32": "choco.exe"},
description="Package manager for Windows."
),
}

# Categories for grouping in UI
CATEGORIES = {
"analog": ["esim", "kicad", "ngspice"],
"digital": ["esim", "kicad", "ngspice", "ghdl", "verilator", "llvm"],
}


def get_tool_metadata(tool_id: str) -> Optional[ToolSpec]:
"""Retrieves metadata for a specific tool."""
return TOOLS.get(tool_id)


def get_supported_tools() -> List[str]:
"""Returns a list of all supported tool IDs."""
return list(TOOLS.keys())


def is_tool_supported(tool_id: str) -> bool:
"""Checks if a tool is supported by the registry."""
return tool_id in TOOLS


def list_tools_by_category(category: str) -> List[ToolSpec]:
"""Returns a list of ToolSpec objects for a given category."""
tool_ids = CATEGORIES.get(category, [])
return [TOOLS[tid] for tid in tool_ids if tid in TOOLS]


def get_all_specs() -> Dict[str, ToolSpec]:
"""Returns the entire tool registry."""
return TOOLS