"""Configuration module for codemcp.
This module provides access to user configuration stored in one of these locations:
1. $CODEMCP_CONFIG_DIR/codemcprc if $CODEMCP_CONFIG_DIR is defined
2. $XDG_CONFIG_HOME/codemcp/codemcprc if $XDG_CONFIG_HOME is defined
3. $HOME/.codemcprc
The configuration is stored in TOML format.
"""
import os
from pathlib import Path
from typing import Any
import tomli
__all__ = [
"get_config_path",
"load_config",
"get_logger_verbosity",
"get_logger_path",
"get_line_endings_preference",
]
# Default configuration values
DEFAULT_CONFIG = {
"logger": {
"verbosity": "INFO", # Default logging level
"path": str(Path.home() / ".codemcp"), # Default logger path
},
"files": {
"line_endings": None, # Default to OS native or based on configs
},
}
def get_config_path() -> Path:
"""Return the path to the user's config file.
Checks the following locations in order:
1. $CODEMCP_CONFIG_DIR/codemcprc if $CODEMCP_CONFIG_DIR is defined
2. $XDG_CONFIG_HOME/codemcp/codemcprc if $XDG_CONFIG_HOME is defined
3. Fallback to $HOME/.codemcprc
Returns:
Path to the config file
"""
# Check $CODEMCP_CONFIG_DIR first
if "CODEMCP_CONFIG_DIR" in os.environ:
path = Path(os.environ["CODEMCP_CONFIG_DIR"]) / "codemcprc"
if path.exists():
return path
# Check $XDG_CONFIG_HOME next
if "XDG_CONFIG_HOME" in os.environ:
path = Path(os.environ["XDG_CONFIG_HOME"]) / "codemcp" / "codemcprc"
if path.exists():
return path
# Fallback to $HOME/.codemcprc
return Path.home() / ".codemcprc"
def load_config() -> dict[str, Any]:
"""Load configuration from the config file.
Looks for the config file in the locations specified by get_config_path():
1. $CODEMCP_CONFIG_DIR/codemcprc if $CODEMCP_CONFIG_DIR is defined
2. $XDG_CONFIG_HOME/codemcp/codemcprc if $XDG_CONFIG_HOME is defined
3. Fallback to $HOME/.codemcprc
Returns:
Dict containing the merged configuration (defaults + user config).
"""
config = DEFAULT_CONFIG.copy()
config_path = get_config_path()
if config_path.exists():
try:
with open(config_path, "rb") as f:
user_config = tomli.load(f)
# Merge user config with defaults
_merge_configs(config, user_config)
except Exception as e:
print(f"Error loading config from {config_path}: {e}")
return config
def _merge_configs(base: dict[str, Any], override: dict[str, Any]) -> None:
"""Recursively merge override dict into base dict.
Args:
base: The base configuration dictionary to merge into.
override: The override configuration dictionary to merge from.
"""
for key, value in override.items():
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
# Type annotation to help the type checker understand that value is dict[str, Any]
nested_value: dict[str, Any] = value
_merge_configs(base[key], nested_value)
else:
base[key] = value
def get_logger_verbosity() -> str:
"""Get the configured logger verbosity level.
Returns:
String representing the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
"""
config = load_config()
return config["logger"]["verbosity"]
def get_logger_path() -> str:
"""Get the configured logger path.
Returns:
String representing the path where logs should be stored.
"""
config = load_config()
return config["logger"]["path"]
def get_line_endings_preference() -> str | None:
"""Get the configured line endings preference.
Returns:
String representing the preferred line endings ('CRLF' or 'LF'), or None if not specified.
"""
config = load_config()
return config["files"]["line_endings"]