# power/power_sync.py
import re
import shutil
from pathlib import Path
import logging
from typing import Dict, List
from datetime import datetime
import json
def setup_logger(log_dir: Path) -> logging.Logger:
"""保持原有日志函数不变"""
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / f"sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
logger = logging.getLogger("clm_sync")
if logger.handlers:
logger.handlers.clear()
logger.setLevel(logging.DEBUG)
fmt = logging.Formatter(
'%(asctime)s | %(levelname)-8s | %(funcName)s:%(lineno)d | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
fh = logging.FileHandler(log_file, encoding='utf-8')
fh.setLevel(logging.DEBUG)
fh.setFormatter(fmt)
logger.addHandler(fh)
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
ch.setFormatter(fmt)
logger.addHandler(ch)
logger.info(f"📄 日志已启动,写入文件: {log_file}")
return logger
class PowerTableSynchronizer:
def __init__(self, c_file_path: str, config_path: str = "config/config.json", dry_run: bool = False):
# 获取脚本所在目录 → F:\issue\power
script_dir = Path(__file__).parent
# 推断项目根目录 → F:\issue
project_root = script_dir.parent
# 解析配置文件路径(支持相对或绝对)
if not Path(config_path).is_absolute():
config_path = (project_root / config_path).resolve()
self.c_file_path = Path(c_file_path)
if not self.c_file_path.is_absolute():
self.c_file_path = (project_root / self.c_file_path).resolve()
self.dry_run = dry_run
self.output_dir = project_root / "output"
self.log_dir = self.output_dir / "log"
self.logger = setup_logger(self.log_dir)
# 加载配置
self.config = self._load_config(config_path)
self.platform = self.config.get("platform_rules", {})
self._validate()
def _load_config(self, config_path: Path) -> dict:
config_path = Path(config_path)
if not config_path.exists():
raise FileNotFoundError(f"❌ 配置文件不存在: {config_path}")
with open(config_path, 'r', encoding='utf-8') as f:
try:
return json.load(f)
except json.JSONDecodeError as e:
raise ValueError(f"配置文件格式错误: {e}") from e
def _validate(self):
if not self.c_file_path.exists():
msg = f"❌ 文件不存在: {self.c_file_path.resolve()}"
self.logger.error(msg)
raise FileNotFoundError(msg)
self.logger.info(f"✅ 目标 C 文件已找到: {self.c_file_path.name}")
def save_content(self, content: str):
if self.dry_run:
self.logger.info("🔍 【预览模式】跳过实际写入")
return
try:
# 创建备份
backup_path = self.c_file_path.with_suffix(self.c_file_path.suffix + ".bak")
shutil.copy2(self.c_file_path, backup_path)
self.logger.info(f"📁 已创建备份: {backup_path.name}")
# 写入新内容
with open(self.c_file_path, 'w', encoding='utf-8') as f:
f.write(content)
self.logger.info(f"✅ 成功更新文件: {self.c_file_path.name}")
except Exception as e:
self.logger.error(f"写入文件失败: {e}")
raise
# ==================== 提取区域 ====================
def _extract_section(self, content: str, section: str) -> tuple[str, int, int]:
"""
根据配置提取指定区域内容
section: 'enums' | 'power_tables' | 'country_definitions' | 'ranges'
"""
try:
anchors = self.platform["anchors"][section]
start_marker = anchors["begin"]
end_marker = anchors["end"]
except KeyError as e:
raise ValueError(f"配置错误:缺少锚点定义 {section}") from e
start_pos = content.find(start_marker)
end_pos = content.find(end_marker)
if start_pos == -1 or end_pos == -1:
missing = "起始标记" if start_pos == -1 else "结束标记"
msg = f"未找到区域 {section} 的 {missing}: {start_marker[:30]}..."
self.logger.warning(msg)
raise ValueError(msg)
inner_start = start_pos + len(start_marker)
inner_end = end_pos
extracted = content[inner_start:inner_end].strip()
self.logger.debug(f"📦 提取区域 [{section}]: {len(extracted)} 字符")
return extracted, inner_start, inner_end
def _replace_section(self, content: str, section: str, new_content: str) -> str:
"""
替换指定区域内容
"""
try:
anchors = self.platform["anchors"][section]
start_marker = anchors["begin"]
end_marker = anchors["end"]
except KeyError as e:
raise ValueError(f"配置错误:无法替换区域 {section}") from e
start_pos = content.find(start_marker)
end_pos = content.find(end_marker)
if start_pos == -1 or end_pos == -1:
raise ValueError(f"无法替换区域 {section},缺失锚点")
return content[:start_pos + len(start_marker)] + "\n" + new_content + "\n" + content[end_pos:]
# ==================== 1. 同步枚举区 ====================
def sync_locales_enum(self, content: str, new_entries: Dict[str, str]) -> str:
"""
同步 enum locale_xxx_idx 区域
new_entries: { "locale_2g_idx": "LOCALE_2G_IDX_MY_REG = 42" }
"""
self.logger.info("🔄 开始同步 LOCALE ENUMS...")
try:
block, start, end = self._extract_section(content, "enums")
except ValueError:
return content
lines = [line.rstrip() for line in block.splitlines()]
output_lines = []
current_enum = None
modified = False
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
# 匹配 enum 开始
enum_match = re.match(r"enum\s+([a-zA-Z0-9_]+)", stripped)
if enum_match:
enum_name = enum_match.group(1)
pattern = self.platform.get("locale_enum", {}).get("pattern", r"locale_(\w+)_idx")
if re.fullmatch(pattern.replace("\\", ""), enum_name):
current_enum = enum_name
output_lines.append(line)
i += 1
continue
# 匹配 enum 结束 → 插入新项
if stripped.endswith("};") and current_enum:
key = current_enum.lower()
if key in new_entries:
entry_code = new_entries[key]
last_line = output_lines[-1].rstrip()
if not (last_line.endswith(",") or last_line.endswith("{")):
output_lines[-1] = last_line + ","
output_lines.append(f" {entry_code},")
self.logger.info(f"📌 新增枚举: {key} → {entry_code}")
modified = True
output_lines.append(line)
current_enum = None
i += 1
continue
output_lines.append(line)
i += 1
if not modified:
self.logger.info("🟨 枚举区无变更")
return content
new_block = "\n".join(output_lines)
updated = self._replace_section(content, "enums", new_block)
self.logger.info("✅ 枚举区更新完成")
return updated
# ==================== 2. 同步功率表区 ====================
def sync_power_tables(self, content: str, tables: Dict[str, str]) -> str:
"""
同步功率表数组
tables: { "2g_base_MY_REG": "static const unsigned char locales_2g_base_MY_REG[] = { ... }" }
"""
self.logger.info("🔄 开始同步 POWER TABLES...")
try:
block, start, end = self._extract_section(content, "power_tables")
except ValueError:
return content
# 构建动态正则
prefix = self.platform["power_table"]["prefix"]
suffix = self.platform["power_table"]["suffix"].replace("[", "\\[").replace("]", "\\]")
pattern_template = self.platform["power_table"][
"declaration_regex"] # "static\\s+const\\s+unsigned\\s+char\\s+(%s)\\s*=\\s*\\{[^}]*\\}\\s*;"
# 提取已有数组名
array_pattern = r'static\s+const\s+unsigned\s+char\s+(' + prefix + r'[a-zA-Z0-9_]+)' + suffix + r'\s*=\s*\{[^}]*\}\s*;'
existing_names = set(re.findall(array_pattern, block, re.IGNORECASE))
all_arrays = []
# 保留原始顺序 + 去重
for match in re.finditer(array_pattern, block, re.DOTALL | re.IGNORECASE):
all_arrays.append(match.group(0))
added = []
for key, declaration in tables.items():
if key not in existing_names:
all_arrays.append(declaration.strip() + ";")
added.append(key)
self.logger.info(f"📌 新增功率表: {prefix}{key}{suffix}")
if not added:
self.logger.info("🟨 功率表区无新增")
return content
new_block = "\n\n".join(all_arrays)
updated = self._replace_section(content, "power_tables", new_block)
self.logger.info(f"✅ 功率表更新完成,新增: {', '.join(added)}")
return updated
# ==================== 3. 同步国家码区 ====================
def sync_country_definitions(self, content: str, country_entries: List[str]) -> str:
"""
同步 REGION("CC", ...) 国家码定义
"""
self.logger.info("🔄 开始同步 COUNTRY DEFINITIONS...")
try:
block, start, end = self._extract_section(content, "country_definitions")
except ValueError:
return content
macro_name = self.platform["country_definition"]["macro"]
pattern = self.platform["country_definition"]["pattern"] # REGION\("([A-Z]{2})"
existing_lines = [line.strip() for line in block.split('\n') if line.strip()]
country_map = {}
for line in existing_lines:
cc_match = re.search(pattern, line)
if cc_match:
country_map[cc_match.group(1)] = line
new_entries = []
for entry in country_entries:
cc_match = re.search(pattern, entry)
if cc_match:
cc = cc_match.group(1)
if cc not in country_map:
new_entries.append(entry.strip())
self.logger.info(f"📌 新增国家码: {cc}")
if not new_entries:
self.logger.info("🟨 国家码无新增")
return content
all_lines = existing_lines + new_entries
new_block = "\n".join(all_lines)
updated = self._replace_section(content, "country_definitions", new_block)
self.logger.info(f"✅ 国家码更新完成,新增 {len(new_entries)} 个")
return updated
# ==================== 主接口 ====================
def sync_all(self,
locale_entries: Dict[str, str],
power_tables: Dict[str, str],
country_entries: List[str]):
"""
一站式同步所有 CLM 内容
"""
self.logger.info("🚀 开始执行 CLM 数据同步任务...")
self.logger.info(f"目标文件: {self.c_file_path.name}")
self.logger.info(f"预览模式: {'是' if self.dry_run else '否'}")
try:
content = self.load_content()
content = self.sync_locales_enum(content, locale_entries)
content = self.sync_power_tables(content, power_tables)
content = self.sync_country_definitions(content, country_entries)
self.save_content(content)
self.logger.info("🎉 全部同步任务完成!")
except Exception as e:
self.logger.error(f"⛔ 同步过程中发生错误: {str(e)}")
raise
def load_content(self) -> str:
"""
读取目标 C 文件内容
"""
try:
content = self.c_file_path.read_text(encoding='utf-8')
self.logger.info(f"📄 已读取文件: {self.c_file_path.name} ({len(content)} 字符)")
return content
except Exception as e:
self.logger.error(f"❌ 读取文件失败: {e}")
raise
def _extract_locale_block(content: str, locale_name: str) -> str | None:
"""
从 tx_limit_table.c 中提取 /* Locale NAME */ 对应的数据块
返回去注释后的干净数据字符串
:param content: 原始文件内容
:param locale_name: 要提取的区域名,如 "DEFAULT"
:return: 提取出的数据块(已清理),若未找到则返回 None
"""
import re
pattern = rf'/\*\s*Locale\s+{locale_name}\s*\*/\s*([^/]*)'
match = re.search(pattern, content, re.DOTALL)
if not match:
return None
block = match.group(1).strip()
# 去掉行内注释 /* ... */
block = re.sub(r'/\*.*?\*/', '', block)
# 归一化空白字符
block = re.sub(r'\s+', ' ', block).strip()
return block
# ==================== 主函数:注入 DEFAULT 功率表(规范版)====================
def main():
"""
主函数:注入 DEFAULT 功率表
保持原有结构,仅提升路径处理的可靠性
"""
import sys
from pathlib import Path
# --- 🔧 修正:使用项目根为基准解析路径 ---
# 假设此脚本位于 power/power_sync.py
project_root = Path(__file__).parent.parent # → F:\issue
CONFIG_FILE = project_root / "config" / "config.json"
GENERATED_FILE = project_root / "output" / "tx_limit_table.c"
# 确保路径存在再继续
if not CONFIG_FILE.exists():
print(f"❌ 配置文件不存在: {CONFIG_FILE}", file=sys.stderr)
sys.exit(1)
if not GENERATED_FILE.exists():
print(f"❌ 生成的数据文件不存在: {GENERATED_FILE}", file=sys.stderr)
sys.exit(1)
try:
# 1. 读取生成文件内容
gen_content = GENERATED_FILE.read_text(encoding='utf-8')
# 2. 提取 DEFAULT 和 DEFAULT_HT 数据块
base_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT")
ht_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT_HT")
if not base_block and not ht_block:
raise ValueError("未提取到任何 DEFAULT 功率表数据")
# 3. 构造输入参数
locale_entries = {
"locale_2g_idx": "LOCALE_2G_IDX_DEFAULT"
}
power_tables = {}
if base_block:
power_tables["2g_base_DEFAULT"] = (
f"static const unsigned char locales_2g_base_DEFAULT[] = {{\n {base_block}\n}}"
)
if ht_block:
power_tables["2g_ht_DEFAULT"] = (
f"static const unsigned char locales_2g_ht_DEFAULT[] = {{\n {ht_block}\n}}"
)
country_entries = [
'REGION("CN", LOCALE_2G_IDX_DEFAULT)',
'REGION("US", LOCALE_2G_IDX_DEFAULT)',
]
# 4. 创建同步器(c_file_path 也建议用 Path 处理)
c_file_path = project_root / "input" / "wlc_clm_data_6726b0.c"
synchronizer = PowerTableSynchronizer(
c_file_path=str(c_file_path),
config_path=str(CONFIG_FILE),
dry_run=False
)
# 5. 执行同步
synchronizer.sync_all(
locale_entries=locale_entries,
power_tables=power_tables,
country_entries=country_entries
)
print("✅ 成功注入 DEFAULT 功率表!")
except Exception as e:
print(f"💥 错误: {e}", file=sys.stderr)
sys.exit(1)
很好,现在看看函数布局结构有没有问题