scripts/merge_by_toc.py (116 lines of code) (raw):
#!/usr/bin/env python3
# coding: utf8
from __future__ import print_function, unicode_literals
import re
import os
followups = []
in_toc = False
contents = []
# 定义正则表达式模式
hyper_link_pattern = re.compile(r'\[(.*?)\]\((.*?)(#.*?)?\)') # 匹配Markdown中的超链接
toc_line_pattern = re.compile(r'([\-\+]+)\s\[(.*?)\]\((.*?)(#.*?)?\)') # 匹配TOC中的行
image_link_pattern = re.compile(r'!\[(.*?)\]\((.*?)\)') # 匹配Markdown中的图片链接
level_pattern = re.compile(r'(\s*[\-\+]+)\s') # 匹配TOC中的层级
heading_patthern = re.compile(r'(^#+|\n#+)\s') # 匹配Markdown中的标题
copyable_snippet_pattern = re.compile(r'{{< copyable .* >}}') # 匹配可复制的代码段
entry_file = "TOC.md" # 入口文件
# 第1步,解析TOC文件
with open(entry_file) as fp: # 打开TOC文件
level = 0 # 初始化层级
current_level = "" # 初始化当前层级
for line in fp: # 逐行读取文件内容
if not in_toc and not line.startswith("<!-- "): # 判断是否进入TOC部分
in_toc = True # 设置标志,表示进入TOC部分
elif in_toc and not line.startswith('#') and line.strip(): # 处理TOC中的有效行
level_space_str = level_pattern.findall(line)[0][:-1] # 获取行的层级字符串
level = len(level_space_str) // 2 + 1 # 计算层级
matches = toc_line_pattern.findall(line) # 查找匹配的TOC行
if matches: # 如果有匹配的行
for match in matches: # 处理每一个匹配
fpath = match[2] # 获取文件路径
if fpath.endswith('.md'): # 如果是Markdown文件
fpath = fpath[1:] # 移除路径开头的斜杠
key = ('FILE', level, fpath) # 创建键
if key not in followups: # 如果键不在followups中
followups.append(key) # 添加键到followups
elif fpath.startswith('http'): # 如果是HTTP链接
followups.append(('TOC', level, line.strip()[2:])) # 添加HTTP链接到followups
else: # 如果没有匹配的行
name = line.strip().split(None, 1)[-1] # 获取目录项名称
key = ('TOC', level, name) # 创建键
if key not in followups: # 如果键不在followups中
followups.append(key) # 添加键到followups
else:
pass # 忽略其他行
# 第2步,获取文件标题
file_link_name = {} # 用于存储文件链接名称
title_pattern = re.compile(r'(^#+)\s.*') # 匹配标题
title_json_pattern = re.compile(r'"title":\s*"(.*?)"') # 匹配JSON中的title
for tp, lv, f in followups: # 遍历followups
if tp != 'FILE': # 如果类型不是文件,跳过
continue
try:
with open(f) as file: # 打开文件
lines = file.readlines() # 读取文件所有行
json_str = lines[2].strip() # 读取第三行的JSON字符串
print(f"json_str: {json_str}")
title_match = title_json_pattern.search(json_str) # 匹配JSON中的title
if title_match:
title = title_match.group(1) # 提取title
file_link_name[f] = title.lower().replace(' ', '-') # 存储标题链接名称
print(f"File: {f}, Title: {title}") # 打印文件名和标题
except Exception as e: # 捕获异常
print(e) # 打印异常
# 替换链接
def replace_link_wrap(chapter, name):
def replace_link(match):
full = match.group(0) # 获取完整的匹配
link_name = match.group(1) # 获取链接名称
link = match.group(2) # 获取链接地址
frag = match.group(3) # 获取链接片段
if link.startswith('http'): # 如果是HTTP链接
return full # 返回完整匹配
elif link.endswith('.md') or '.md#' in link: # 如果是Markdown文件链接
if not frag: # 如果没有片段
link = link[1:] # 去掉开头的斜杠
for fpath in file_link_name: # 遍历文件链接名称
if link == fpath: # 如果链接匹配
frag = '#' + file_link_name[fpath] # 添加片段
return '[%s](%s)' % (link_name, frag) # 返回替换后的链接
elif link.endswith('.png') or link.endswith('.jpeg') or link.endswith('.svg') or link.endswith('.gif') or link.endswith('.jpg'): # 如果是图片链接
img_link = re.sub(r'[\.\/]*images\/', 'static/images/', link, count=0, flags=0) # 替换图片路径
return '[%s](%s)' % (link_name, img_link) # 返回替换后的图片链接
else:
return full # 返回完整匹配
return hyper_link_pattern.sub(replace_link, chapter) # 替换章节中的链接
# 替换标题级别
def replace_heading_func(diff_level=0):
def replace_heading(match):
if diff_level == 0: # 如果没有级别差异
return match.group(0) # 返回原始匹配
else:
return '\n' + '#' * (match.group(0).count('#') + diff_level) + ' ' # 返回调整后的标题
return replace_heading # 返回替换函数
# 移除可复制代码段
def remove_copyable(match):
return '' # 返回空字符串,移除代码段
# 处理特殊字符的函数
def handle_special_characters(chapter):
chapter = chapter.replace("\\", "\\textbackslash{}") # 替换反斜杠
chapter = chapter.replace("~", "\\textasciitilde{}") # 替换波浪号
chapter = chapter.replace("^", "\\textasciicircum{}") # 替换脱字符
return chapter # 返回处理后的章节
# 第3步,合并文件
for type_, level, name in followups: # 遍历followups
if type_ == 'TOC': # 如果类型是TOC
contents.append("\n{} {}\n".format('#' * level, name)) # 添加目录项到内容
elif type_ == 'RAW': # 如果类型是RAW
contents.append(name) # 添加原始内容
elif type_ == 'FILE': # 如果类型是文件
try:
with open(name) as fp: # 打开文件
chapter = fp.read() # 读取文件内容
chapter = replace_link_wrap(chapter, name) # 替换链接
chapter = copyable_snippet_pattern.sub(remove_copyable, chapter) # 移除可复制代码段
# 处理特殊字符
chapter = handle_special_characters(chapter)
# 修正标题级别
diff_level = level - heading_patthern.findall(chapter)[0].count('#')
chapter = heading_patthern.sub(replace_heading_func(diff_level), chapter) # 替换标题级别
# 查找第二个---的位置
second_dash_pos = chapter.find('---', chapter.find('---') + 1)
if second_dash_pos != -1:
# 在第二个---之后插入一级标题
chapter = chapter[:second_dash_pos + 3] + f"\n\n# {file_link_name[name]}\n\n" + chapter[second_dash_pos + 3:]
contents.append(chapter) # 添加章节到内容
except Exception as e: # 捕获异常
print(e) # 打印异常
# 第4步,生成最终文档
target_doc_file = 'doc.md' # 目标文档文件名
with open(target_doc_file, 'w') as fp: # 打开目标文档文件
fp.write('\n'.join(contents)) # 写入内容到文件