from typing import *
import re
import markdown
try: # 检测当前平台是否支持扩展语法
import CrossMore
EXTRA_ABLE = True
except ModuleNotFoundError:
EXTRA_ABLE = False
class Style:
"""
渲染字体样式
"""
def __init__(self, text: str):
"""
初始化
:param text: cd文本
"""
self.text = text
def underline(self):
"""
~下划线~
:return:
"""
self.text = re.sub(r'~([^~\n]+)~', r'\1', self.text)
def strikethrough(self):
"""
~~删除线~~
:return:
"""
self.text = re.sub(r'~~([^~\n]+)~~', r'\1', self.text)
def highlight(self):
"""
==高亮==
:return:
"""
self.text = re.sub(r'==([^=\n]+)==', r'\1', self.text)
def up(self):
"""
[在文本的正上方添加一行小文本]^(主要用于标拼音)
:return:
"""
self.text = re.sub(r'\[(.*?)]\^\((.*?)\)', r'\1', self.text)
def hide(self):
"""
[在指定的文本里面隐藏一段文本]-(只有鼠标放在上面才会显示隐藏文本)
:return:
"""
self.text = re.sub(r'\[(.*?)]-\((.*?)\)', r'\1', self.text)
def __call__(self, *args, **kwargs):
"""
一键运行
:param args:
:param kwargs:
:return:
"""
self.strikethrough()
self.underline()
self.highlight()
self.up()
self.hide()
return self.text
class Value:
"""
定义: {变量名} = 值
赋值: {变量或锚点名}
锚点: {#锚点名}
"""
def __init__(self, text: str):
self.text = text
self.values = {
key: value for key, value in re.findall(r'\{([^{}#]+)} ?= ?(.+?)(?=\n|$)', text)
} # 从text中提取所有变量并转换成字典
self.anchor = re.findall(r'\{#([^{}#]+)}', text)
def __call__(self, *args, **kwargs) -> Tuple[str, Dict[str, str]]:
"""
将所有变量赋值并移除变量定义
:param args:
:param kwargs:
:return: 赋值后的正文
"""
text = self.text
for item in self.anchor:
text = re.sub(r'\{#(' + item + ')}', r'', text) # 添加锚点
text = re.sub(r'\{' + item + '}', fr'{item}', text) # 添加页内链接
for k, v in self.values.items():
text = re.sub(r'\{' + k + '} ?= ?(.+?)(?=\n|$)', '', text) # 移除变量的定义
text = re.sub(r'\{' + k + '}', fr'{v}', text) # 给变量赋值
return text, self.values
class CodeBlock:
def __init__(self, text: str):
"""
找出`代码块`并移除代码标识
:param text: 输入的文本
"""
self.codes = [i for i in re.findall(r'`([^`]*)`', text) if i != ''] # 找出代码快
self.text = re.sub(r'``', '', text) # 移除代码标识`
def __call__(self, *args, **kwargs):
"""
临时移除代码块
:param args:
:param kwargs:
:return: 不含代码的文本
"""
for index, item in enumerate(self.codes): # 替换代码块为-@@-(ID)-@@-
self.text = re.sub(fr'`{re.escape(item)}`', f'\0\1{index}\1\0', self.text) # 同时转译特殊字符
return self.text
def rendering(self, values: Dict[str, str]):
"""
渲染代码
:param values: 变量字典
:return:
"""
for index, code in enumerate(self.codes):
if re.match(r'\{[^$]*}', code): # 是变量
# 给变量赋值
key = re.findall(r'\{([^}]+)}', code)[0] # 查找变量名
code = re.sub(r'\{' + key + '}', fr'{values[key]}', code)
self.codes[index] = code # 给变量赋值
if re.search(r'\n', code): # 是多行代码
head = re.findall(r'(.*?)\n', code)[0]
if head in ('', 'yaml'):
self.codes[index] = f'
{code}
'
elif head in ('shell', 'python'):
self.codes[
index] = f'{re.sub(f"({head})", "", code)}
'
elif head in ('mermaid',):
self.codes[index] = f'\(\1\)
', code) elif re.match(r'¥[^$]*¥', code): # 是数学函数(单行) if EXTRA_ABLE: expression, range_ = re.findall(r'¥([^$]*)¥(€[^$]*€)?', code)[0] # 分离表达式与范围(如果有) x_r = (-10, 10) y_r = (-20, 20) if range_ != '': # 定义了范围 ranges = range_[1:-1].split('|') if len(ranges) in (1, 2): # 定义的范围正确 x_r = tuple(int(i) for i in ranges[0].split(',')) if len(ranges) == 2: # 定义了y范围 y_r = tuple(int(i) for i in ranges[1].split(',')) self.codes[index] = CrossMore.function_drawing( # 绘制函数图像 function=lambda x: eval(expression.split('=')[1]), x_range=x_r, y_range=y_r ) else: self.codes[index] = '该平台不支持扩展语法' else: # 是突出块 self.codes[index] = f'{code}' def restore(self, new_text: str): """ 将渲染好的代码重新放回处理好的正文 :param new_text: 处理好的正文 :return: 加上代码的文章 """ for index, item in enumerate(self.codes): new_text = re.sub(f'\0\1{index}\1\0', f'{item}', new_text, flags=re.DOTALL) return new_text class Syllabus: """ 1. 找到提纲 2 找到符合若干个‘数字+点+数字’且首尾都是数字的行 每个提纲编号全文只能出现一次 """ def __init__(self, text: str): self.text = text def __call__(self, *args, **kwargs) -> str: return '\n'.join([ (lambda match, origen: re.sub(f'^({match.groups()[0]})', # 按照提纲等级添加#和锚点 fr'{"#" * len(match.groups()[0].split("."))} \1{{#' + match.groups()[0] + '}', origen) if match is not None else origen) # 对于不是提纲的行,直接返回原始字符 ((lambda x: re.match(r'^([\d.]+) ', x) # 判断是否是提纲 if not any((x.startswith('.'), # 以.开头 re.search('\. ', x) is not None, # 存在.+空格 re.search('\.{2,}', x), # 存在连续的. )) else None)(line), line) # 排除.在提纲号开头或结尾的情况 for line in self.text.splitlines() # 分割并遍历文本的每一行 ]) class Basic: def __init__(self, text: str): self.text: str = text @staticmethod def strong_annotation(text: str) -> str: """ 移除|=强注释=| :param text: 原始文本 :return: 移除强注释后的文本 """ return re.sub('\|=[\s\S]*=\|', '', text, re.DOTALL) @staticmethod def week_annotation(text: str) -> str: """ 移除 // 弱注释 :param text: 原始文本 :return: 移除弱注释后的文本 """ return re.sub('// .*?\n', '\n', text) def add_indent_to_string(input_string: str, indent_spaces: int = 4): """ 给字符串中的每一行前面加上缩进。 :param input_string: 原始字符串,可以包含多行。 :param indent_spaces: 每行前面要添加的空格数,默认为4。 :return: 带缩进的新字符串。 """ # 使用字符串的splitlines()方法分割原始字符串为行列表 lines = input_string.splitlines() # 遍历行列表,给每行前面加上相应的缩进,并重新组合成字符串 indented_string = "\n".join(f"{' ' * indent_spaces}{line}" for line in lines) return indented_string def body(text: str) -> Tuple[str, Dict[str, str]]: """ 渲染正文部分 :param text: 输入正文 :return: 输出渲染后的正文 """ text = Basic.week_annotation(text) # 移除弱注释 text = Syllabus(text)() # 渲染提纲 text, values = Value(text)() # 提取变量并赋值到文本中 text = Style(text)() # 渲染字体样式 text = markdown.markdown(text, extensions=[ 'markdown.extensions.extra', # 扩展语法 'markdown.extensions.codehilite', # 语法高亮拓展 'markdown.extensions.toc', # 自动生成目录 ]) # 渲染标准markdown return text, values def main(origen: str): # 预处理、 origen = Basic.strong_annotation(origen) # 移除强注释 code_block = CodeBlock(origen) # 获取代码内容 text = code_block() # 暂时移除代码 # 处理正文 text, values = body(text) # 后处理 code_block.rendering(values) # 渲染代码 return code_block.restore(text) # 放回代码 if __name__ == '__main__': with open('README.md', encoding='utf-8') as test: cd = main(test.read()) with open('README.html', 'w', encoding='utf-8') as html: html.write(f"""