Files
PAT/source/build.py
T

195 lines
6.5 KiB
Python
Executable File

#! /usr/bin/env python3
"""
Build markdown files from different sources
"""
import os
import sys
import re
import argparse
from bs4 import BeautifulSoup
from subprocess import run, PIPE, CalledProcessError
import config
class FileBuilder:
"""
build markdown files.
"""
def __init__(self):
self.github = "https://github.com/OliverLew/PAT/blob/master"
def yaml_frontmatter(self, date=None, title=None):
"""
create the yaml front matter for markdown files
"""
tags = config.tag[self.c]
categories = config.category[self.c]
# Check validity
if re.match(r'', date) is None:
print("{}{}:".format(self.c, self.i))
print("date should be in the pattern of 'YYYY-MM-DD HH:MM:SS +/-TTTT'")
print("date is now: {}".format(date))
exit(1)
if not isinstance(title, str):
print("{}{}:".format(self.c, self.i))
print("title should be string")
print("title is now: {}".format(title))
exit(1)
if not isinstance(categories, str):
print("{}{}:".format(self.c, self.i))
print("categories should be a string")
print("categories is now: {}".format(categories))
exit(1)
if not isinstance(tags, list):
print("{}{}:".format(self.c, self.i))
print("tags should be a list")
print("tags is now: {}".format(tags))
exit(1)
frontmatter = "---\n"
frontmatter += "layout: post\n"
frontmatter += "date: {}\n".format(date)
frontmatter += "title: \"{}\"\n".format(title)
frontmatter += "categories: {}\n".format(categories)
frontmatter += "tags: [{}]\n".format(', '.join(tags))
frontmatter += "permalink: {}/{:04}.html\n".format(categories, self.i)
frontmatter += "---\n\n"
return frontmatter
def _processkatex(self, html):
"""
replace all the 'katex' class spans with simple latex string
"""
soup = BeautifulSoup(html, "html.parser")
katex_spans = soup.find_all("span", class_="katex")
for katex_span in katex_spans:
katex_span.mrow.decompose()
mathstr = katex_span.find("math")
mathjax = soup.new_tag("span")
mathjax.string = "${}$".format(mathstr.string)
katex_span.replace_with(mathjax)
return str(soup)
def _filename(self):
return os.path.join(config.md_dir, "{}{:04}.md".format(self.c, self.i))
def read_html(self):
"""
Open the html file and read into lines
"""
html = os.path.join(config.html_dir, "{}{}.html".format(self.c, self.i))
lines = open(html).readlines()
title = "{level} {index}. {title} {lang}".format(
level=config.cat_string[self.c],
index=self.i,
title=lines[0].rstrip(),
lang="(C语言实现)")
content = self._processkatex("".join(lines[1:]))
return title, content
def read_code(self):
"""
read code file from master branch
"""
code_rel_path = os.path.join(config.code_dir[self.c],
"{}.c".format(self.i))
github_file_url = os.path.join(self.github, code_rel_path)
raw_code = run(["git", "show", "master:" + code_rel_path], check=True,
stdout=PIPE, stderr=PIPE).stdout.decode("utf-8")
code = raw_code[raw_code.index("#include"):]
return code, github_file_url
def read_expl(self):
"""
read explanation.
The file contains the date at the first line and a blank line after it.
Hence the return has two parts.
"""
expl_file = os.path.join(config.analysis_dir,
"{}{}.md".format(self.c, self.i))
expl = open(expl_file).readlines()
if len(expl) < 3:
print(expl_file + ": ")
print("analysis file should be at least 3 lines long:")
print("date, blank, and content")
exit(1)
date = expl[0].strip('\n')
if re.match(r'', date) is None:
print("date should be in the pattern of")
print("'YYYY-MM-DD HH:MM:SS +/-TTTT'")
exit(1)
return date, "".join(expl[2:])
def __build(self):
"""
write everything to a final markdown file
"""
filename = self.filename()
title, problem_div = self.read_html()
code, code_url = self.read_code()
date, expl = self.read_expl()
yaml = self.yaml_frontmatter(date, title)
print("Building {}".format(filename))
with open(filename, 'w') as f:
# write the yaml front matter
f.write(yaml)
# write problem content
f.write("## 题目\n\n{% raw %}")
for line in problem_div:
if config.quote_text is True:
f.write("> ")
f.write(line)
f.write("{% endraw %}\n\n")
# write explanation
f.write("## 思路\n\n{}\n".format(expl))
# write code
f.write("## 代码\n\n")
f.write("[最新代码@github]({}),欢迎交流\n".format(code_url))
f.write("```c\n{{% raw %}}{}{{% endraw %}}\n```".format(code))
def build(self, c, i):
"""
build a file with error handling
"""
self.c = c
self.i = i
try:
self.__build()
except FileNotFoundError:
pass
except CalledProcessError:
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="""A script to combine the
html problem content, markdown file and the source code into a final
markdown file for jekyll blog""")
parser.add_argument('ids', nargs="+",
metavar="<problem-id>",
help="[abt][0-9]{4}, or 'all' for all problems.")
args = parser.parse_args()
builder = FileBuilder()
if not os.path.exists(config.md_dir):
os.mkdir(config.md_dir)
if 'all' in args.ids:
for c in config.indexes.keys():
for i in config.indexes[c]:
builder.build(c, i)
else:
for ID in args.ids:
if re.match(r"[abt]\d{4}", ID):
category = ID[0]
index = int(ID[1:])
if index in config.indexes[category]:
builder.build(category, index)