add-list-index-columns.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. #!/usr/bin/env python3
  2. """为 web-admin 中缺少序号的 el-table 批量插入序号列。"""
  3. from __future__ import annotations
  4. import re
  5. import sys
  6. from pathlib import Path
  7. ROOT = Path(__file__).resolve().parents[1] / "src"
  8. SKIP_FILES = {
  9. ROOT / "components/bx/base/BaseTable.vue",
  10. }
  11. def find_table_spans(text: str) -> list[tuple[int, int]]:
  12. spans: list[tuple[int, int]] = []
  13. for m in re.finditer(r"<el-table(?:\s|>)", text):
  14. start = m.start()
  15. end_m = re.search(r"</el-table>", text[start:])
  16. if not end_m:
  17. continue
  18. spans.append((start, start + end_m.end()))
  19. return spans
  20. def extract_first_column(block: str) -> tuple[int, int, str] | None:
  21. table_open = re.search(r"<el-table\b[^>]*>", block)
  22. if not table_open:
  23. return None
  24. pos = table_open.end()
  25. m = re.search(r"(\s*)<el-table-column\b", block[pos:])
  26. if not m:
  27. return None
  28. col_start = pos + m.start() + len(m.group(1))
  29. rest = block[col_start:]
  30. self_close = re.match(r"(<el-table-column\b[^>]*?/>)", rest, re.DOTALL)
  31. if self_close:
  32. col_end = col_start + len(self_close.group(1))
  33. return col_start, col_end, block[col_start:col_end]
  34. open_end = rest.find(">")
  35. if open_end == -1:
  36. return None
  37. close = rest.find("</el-table-column>", open_end)
  38. if close == -1:
  39. return None
  40. col_end = col_start + close + len("</el-table-column>")
  41. return col_start, col_end, block[col_start:col_end]
  42. def leading_indent(block: str, col_start: int) -> str:
  43. line_start = block.rfind("\n", 0, col_start) + 1
  44. return re.match(r"[ \t]*", block[line_start:col_start]).group(0)
  45. def build_index_column(indent: str, paginated: bool) -> str:
  46. if paginated:
  47. return (
  48. f"{indent}<el-table-column\n"
  49. f"{indent} type=\"index\"\n"
  50. f"{indent} label=\"序号\"\n"
  51. f"{indent} width=\"68\"\n"
  52. f"{indent} align=\"center\"\n"
  53. f"{indent} header-align=\"center\"\n"
  54. f"{indent} :index=\"(index) => (pagination.page - 1) * pagination.pageSize + index + 1\"\n"
  55. f"{indent}/>"
  56. )
  57. return (
  58. f'{indent}<el-table-column type="index" label="序号" width="68" '
  59. f'align="center" header-align="center" />'
  60. )
  61. def should_use_pagination(block: str, file_text: str) -> bool:
  62. if "pagination.page" not in file_text:
  63. return False
  64. table_open = re.search(r"<el-table\b[^>]*>", block)
  65. if not table_open:
  66. return False
  67. opening = table_open.group(0)
  68. if 'size="small"' in opening or "size='small'" in opening:
  69. return False
  70. if "sub-table" in opening:
  71. return False
  72. return True
  73. def insert_index_into_block(block: str, file_text: str) -> str | None:
  74. if 'type="index"' in block or "type='index'" in block:
  75. return None
  76. first = extract_first_column(block)
  77. if not first:
  78. return None
  79. col_start, col_end, first_col = first
  80. indent = leading_indent(block, col_start)
  81. paginated = should_use_pagination(block, file_text)
  82. index_col = build_index_column(indent, paginated)
  83. is_select = re.search(r'type="(selection|radio)"', first_col) is not None
  84. if is_select:
  85. insert_at = col_end
  86. new_block = block[:insert_at] + "\n" + index_col + block[insert_at:]
  87. else:
  88. line_start = block.rfind("\n", 0, col_start) + 1
  89. new_block = block[:line_start] + index_col + "\n" + block[line_start:]
  90. return new_block
  91. def process_file(path: Path) -> bool:
  92. if path in SKIP_FILES:
  93. return False
  94. text = path.read_text(encoding="utf-8")
  95. if not re.search(r"<el-table(?:\s|>)", text):
  96. return False
  97. spans = find_table_spans(text)
  98. if not spans:
  99. return False
  100. changed = False
  101. offset = 0
  102. for start, end in spans:
  103. start += offset
  104. end += offset
  105. block = text[start:end]
  106. new_block = insert_index_into_block(block, text)
  107. if not new_block:
  108. continue
  109. text = text[:start] + new_block + text[end:]
  110. offset += len(new_block) - len(block)
  111. changed = True
  112. if changed:
  113. path.write_text(text, encoding="utf-8")
  114. return changed
  115. def main() -> int:
  116. changed_files: list[str] = []
  117. for path in sorted(ROOT.rglob("*.vue")):
  118. if process_file(path):
  119. changed_files.append(str(path.relative_to(ROOT.parents[0])))
  120. print(f"Updated {len(changed_files)} files")
  121. for f in changed_files:
  122. print(f" {f}")
  123. return 0
  124. if __name__ == "__main__":
  125. sys.exit(main())