"""
配置管理模块
提供类型安全的配置管理功能
"""
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Dict, Any, Optional
import json
import yaml
[文档]
@dataclass
class PathConfig:
"""路径配置"""
input_dir: Path = Path("./input")
output_dir: Path = Path("./output")
param_config_dir: Path = Path("./param_config")
log_dir: Path = Path("./logs")
def __post_init__(self):
"""确保路径是 Path 对象"""
self.input_dir = Path(self.input_dir)
self.output_dir = Path(self.output_dir)
self.param_config_dir = Path(self.param_config_dir)
self.log_dir = Path(self.log_dir)
[文档]
def ensure_dirs_exist(self):
"""确保所有目录存在"""
self.input_dir.mkdir(parents=True, exist_ok=True)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.param_config_dir.mkdir(parents=True, exist_ok=True)
self.log_dir.mkdir(parents=True, exist_ok=True)
[文档]
@dataclass
class ProcessingConfig:
"""处理配置"""
ignore_mode: bool = True
ignore_words: List[str] = field(default_factory=lambda: [""])
batch_size: int = 100
enable_progress_bar: bool = True
[文档]
@dataclass
class EngineConfig:
"""引擎配置基类"""
engine_type: str
file_extension: str
indent_size: int = 0
[文档]
def get_output_filename(self, sheet_name: str) -> str:
"""
生成输出文件名
Args:
sheet_name: 工作表名称
Returns:
str: 输出文件名
"""
return f"{sheet_name}{self.file_extension}"
[文档]
@dataclass
class RenpyConfig(EngineConfig):
"""Ren'Py 引擎配置"""
engine_type: str = "renpy"
file_extension: str = ".rpy"
indent_size: int = 4
label_indent: bool = False
default_transition: str = "dissolve"
[文档]
@dataclass
class NaninovelConfig(EngineConfig):
"""Naninovel 引擎配置"""
engine_type: str = "naninovel"
file_extension: str = ".nani"
command_prefix: str = "@"
[文档]
@dataclass
class AppConfig:
"""应用总配置"""
paths: PathConfig = field(default_factory=PathConfig)
processing: ProcessingConfig = field(default_factory=ProcessingConfig)
engine: EngineConfig = field(default_factory=NaninovelConfig)
[文档]
@classmethod
def from_file(cls, config_path: Path) -> 'AppConfig':
"""
从配置文件加载
Args:
config_path: 配置文件路径
Returns:
AppConfig: 配置对象
Raises:
ValueError: 不支持的配置文件格式
"""
config_path = Path(config_path)
if config_path.suffix == '.json':
with open(config_path, 'r', encoding='utf-8') as f:
data = json.load(f)
elif config_path.suffix in ['.yaml', '.yml']:
with open(config_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
else:
raise ValueError(f"不支持的配置文件格式: {config_path.suffix}")
return cls.from_dict(data)
[文档]
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AppConfig':
"""
从字典创建配置
Args:
data: 配置字典
Returns:
AppConfig: 配置对象
"""
paths = PathConfig(**data.get('paths', {}))
processing = ProcessingConfig(**data.get('processing', {}))
# 根据引擎类型创建对应配置
engine_data = data.get('engine', {})
# 如果只提供了 engine_type,使用默认配置
if isinstance(engine_data, str):
engine_type = engine_data
else:
engine_type = engine_data.get('engine_type', 'naninovel')
# 根据引擎类型创建默认配置,然后用用户提供的值覆盖
if engine_type == 'renpy':
engine = RenpyConfig()
# 只覆盖用户明确提供的配置项
if isinstance(engine_data, dict):
for key, value in engine_data.items():
if hasattr(engine, key) and key != 'engine_type':
setattr(engine, key, value)
elif engine_type == 'naninovel':
engine = NaninovelConfig()
if isinstance(engine_data, dict):
for key, value in engine_data.items():
if hasattr(engine, key) and key != 'engine_type':
setattr(engine, key, value)
else:
raise ValueError(f"不支持的引擎类型: {engine_type}")
return cls(paths=paths, processing=processing, engine=engine)
[文档]
def to_file(self, config_path: Path):
"""
保存到配置文件
Args:
config_path: 配置文件路径
"""
config_path = Path(config_path)
data = {
'paths': {
'input_dir': str(self.paths.input_dir),
'output_dir': str(self.paths.output_dir),
'param_config_dir': str(self.paths.param_config_dir),
'log_dir': str(self.paths.log_dir),
},
'processing': {
'ignore_mode': self.processing.ignore_mode,
'ignore_words': self.processing.ignore_words,
'batch_size': self.processing.batch_size,
'enable_progress_bar': self.processing.enable_progress_bar,
},
'engine': {
'engine_type': self.engine.engine_type,
'file_extension': self.engine.file_extension,
'indent_size': self.engine.indent_size,
}
}
# 添加引擎特定配置
if isinstance(self.engine, RenpyConfig):
data['engine']['label_indent'] = self.engine.label_indent
data['engine']['default_transition'] = self.engine.default_transition
elif isinstance(self.engine, NaninovelConfig):
data['engine']['command_prefix'] = self.engine.command_prefix
if config_path.suffix == '.json':
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
elif config_path.suffix in ['.yaml', '.yml']:
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(data, f, allow_unicode=True)
[文档]
@classmethod
def create_default(cls, engine_type: str = "naninovel") -> 'AppConfig':
"""
创建默认配置
Args:
engine_type: 引擎类型
Returns:
AppConfig: 默认配置对象
"""
if engine_type == "renpy":
engine = RenpyConfig()
elif engine_type == "naninovel":
engine = NaninovelConfig()
else:
raise ValueError(f"不支持的引擎类型: {engine_type}")
return cls(
paths=PathConfig(),
processing=ProcessingConfig(),
engine=engine
)