file_manager.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import os
  2. from typing import Dict, Set, Optional
  3. from pathlib import Path
  4. import logging
  5. class FileNameManager:
  6. """文件名管理器,处理文件命名和冲突"""
  7. def __init__(self, output_dir: str):
  8. self.output_dir = output_dir
  9. self.used_names: Set[str] = set()
  10. self.name_mapping: Dict[str, str] = {} # 原始名 -> 实际文件名
  11. self.logger = logging.getLogger("FileNameManager")
  12. # 扫描已存在的文件
  13. self._scan_existing_files()
  14. def _scan_existing_files(self):
  15. """扫描输出目录中已存在的文件(不包括子目录)"""
  16. if not os.path.exists(self.output_dir):
  17. return
  18. # 只扫描根目录,不扫描子目录(避免扫描备份目录)
  19. for file in os.listdir(self.output_dir):
  20. file_path = os.path.join(self.output_dir, file)
  21. if os.path.isfile(file_path) and file.endswith(('.ddl', '.md')):
  22. self.used_names.add(file)
  23. def get_safe_filename(self, schema_name: str, table_name: str, suffix: str) -> str:
  24. """
  25. 生成安全的文件名,避免冲突
  26. Args:
  27. schema_name: Schema名称
  28. table_name: 表名
  29. suffix: 文件后缀(如 .ddl 或 _detail.md)
  30. Returns:
  31. 安全的文件名
  32. """
  33. # 生成基础文件名
  34. base_name = self._generate_base_name(schema_name, table_name)
  35. # 添加后缀
  36. if suffix.startswith('.'):
  37. filename = f"{base_name}{suffix}"
  38. else:
  39. filename = f"{base_name}{suffix}"
  40. # 检查冲突并生成唯一名称
  41. unique_filename = self._ensure_unique_filename(filename)
  42. # 记录映射关系
  43. original_key = f"{schema_name}.{table_name}"
  44. self.name_mapping[original_key] = unique_filename
  45. self.used_names.add(unique_filename)
  46. return unique_filename
  47. def _generate_base_name(self, schema_name: str, table_name: str) -> str:
  48. """
  49. 生成基础文件名
  50. 规则:
  51. - public.table_name → table_name
  52. - schema.table_name → schema__table_name
  53. - 特殊字符替换: . → __, - → _, 空格 → _
  54. """
  55. if schema_name.lower() == 'public':
  56. safe_name = table_name
  57. else:
  58. safe_name = f"{schema_name}__{table_name}"
  59. # 替换特殊字符
  60. replacements = {
  61. '.': '__',
  62. '-': '_',
  63. ' ': '_',
  64. '/': '_',
  65. '\\': '_',
  66. ':': '_',
  67. '*': '_',
  68. '?': '_',
  69. '"': '_',
  70. '<': '_',
  71. '>': '_',
  72. '|': '_'
  73. }
  74. for old_char, new_char in replacements.items():
  75. safe_name = safe_name.replace(old_char, new_char)
  76. # 移除连续的下划线
  77. while '__' in safe_name:
  78. safe_name = safe_name.replace('__', '_')
  79. return safe_name
  80. def _ensure_unique_filename(self, filename: str) -> str:
  81. """确保文件名唯一性"""
  82. if filename not in self.used_names:
  83. return filename
  84. # 如果重名,添加数字后缀
  85. base, ext = os.path.splitext(filename)
  86. counter = 1
  87. while True:
  88. unique_name = f"{base}_{counter}{ext}"
  89. if unique_name not in self.used_names:
  90. self.logger.warning(f"文件名冲突,'{filename}' 重命名为 '{unique_name}'")
  91. return unique_name
  92. counter += 1
  93. def get_full_path(self, filename: str, subdirectory: Optional[str] = None) -> str:
  94. """
  95. 获取完整文件路径
  96. Args:
  97. filename: 文件名
  98. subdirectory: 子目录(如 'ddl' 或 'docs')
  99. Returns:
  100. 完整路径
  101. """
  102. if subdirectory:
  103. full_path = os.path.join(self.output_dir, subdirectory, filename)
  104. else:
  105. full_path = os.path.join(self.output_dir, filename)
  106. # 确保目录存在
  107. os.makedirs(os.path.dirname(full_path), exist_ok=True)
  108. return full_path
  109. def get_mapping_report(self) -> Dict[str, str]:
  110. """获取文件名映射报告"""
  111. return self.name_mapping.copy()
  112. def write_mapping_report(self):
  113. """写入文件名映射报告"""
  114. report_path = os.path.join(self.output_dir, "filename_mapping.txt")
  115. try:
  116. with open(report_path, 'w', encoding='utf-8') as f:
  117. f.write("# 文件名映射报告\n")
  118. f.write("# 格式: 原始表名 -> 实际文件名\n\n")
  119. for original, actual in sorted(self.name_mapping.items()):
  120. f.write(f"{original} -> {actual}\n")
  121. self.logger.info(f"文件名映射报告已保存到: {report_path}")
  122. except Exception as e:
  123. self.logger.error(f"写入文件名映射报告失败: {e}")