Last active
July 30, 2020 14:05
-
-
Save cdfmlr/d5a40f670ab71512511a63bf94d8d424 to your computer and use it in GitHub Desktop.
Complete YAML for hexo blogs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/python3 | |
# -*- coding: utf-8 -*- | |
''' | |
HEXO BLOG YAML CONFIG CHECKER/COMPLETER | |
@File : blogconfc.py | |
@Author : CDFMLR | |
@Time : 2020/07/30 21:49 | |
''' | |
import datetime | |
import os | |
import platform | |
import shutil | |
import sys | |
from enum import Enum | |
from os import path | |
import yaml | |
def read_yaml(file_path): | |
''' | |
read_yaml 从一篇文章中读取 YAML 配置,即在文章开头处两行 `---` 之间的符合 YAML 语法的配置信息。 | |
参数: file_path: 文章文件路径。 | |
返回: | |
- object: yaml.load 返回的 Python object; | |
- list : 原文件中 YAML 配置的起始、结束行(从0开始计数,闭区间); | |
- bool : 是否存在 YAML 配置。 | |
''' | |
assert os.path.isfile(file_path), f"Target file not exists: {file_path}" | |
is_yaml = False | |
yaml_content = '' | |
yaml_line = [] | |
lines_cnt = 0 | |
with open(file_path, 'r', encoding='utf-8') as f: | |
for line in f: | |
if line.strip() == '---': | |
yaml_line.append(lines_cnt) | |
if is_yaml or (lines_cnt > 1): | |
break | |
is_yaml = not is_yaml | |
if is_yaml: | |
yaml_content += line.replace('\t', ' ') | |
lines_cnt += 1 | |
return yaml.safe_load(yaml_content), yaml_line, len(yaml_line) == 2 | |
def write_yaml(file_path, yaml_data) -> None: | |
''' | |
write_yaml 向 file_path 指定的文件开头写入 yaml_data 转化成的 YAML 配置信息,YAML 信息将被前后各一行 `---` 所包裹。 | |
<em>注意:如果 文章开头已有 YAML 配置,则原有配置**不会**被自动删除或覆盖。本函数仅只是在文件开头添加新的信息!</em> | |
输入: | |
- file_path: 要写入 YAML 信息的文件路径; | |
- yaml_data: 要写入的 YAML 信息对应的 Python 对象,这个对象将被 yaml.dump 序列化为 YAML 文本. | |
返回: None | |
''' | |
yaml_text = yaml.dump(yaml_data, allow_unicode=True) | |
wrapper = '---\n' | |
yaml_wrapped = wrapper + yaml_text + wrapper | |
with open(file_path, "r+") as f: | |
file_content = f.read() | |
f.seek(0) | |
f.write(yaml_wrapped) | |
f.write(file_content) | |
def remove_lines(file_path, lines_range) -> None: | |
''' | |
remove_lines 从 file_path 指定的文件中将 lines_range 指定的行(闭区间,从0开始计数)删除。 | |
参数: | |
- file_path : 要删除行的文件路径; | |
- lines_range: 要删除的行,闭区间 [start, end],行数从0开始计. | |
返回: None | |
''' | |
file_dir = os.path.dirname(file_path) | |
file_name = os.path.basename(file_path) | |
temp_file = os.path.join(file_dir, f'.{file_name}.tmp') | |
shutil.copy(file_path, temp_file) | |
with open(temp_file, 'r') as f_in: | |
with open(file_path, 'w') as f_out: | |
f_out.writelines(line for i, line in enumerate( | |
f_in) if i not in range(lines_range[0], lines_range[1]+1)) | |
os.remove(temp_file) | |
def get_creation_time(file_path): | |
''' | |
get_creation_time 获取 file_path 文件的创建时间 | |
''' | |
if platform.platform().find('Darwin') != -1: # macOS 可以用 birthtime 获取到创建时间 | |
return os.stat(file_path).st_birthtime | |
# 其他系统就用 ctime 吧(Linux 是 change time,inode 里的那种;Windows 据说是 createion time) | |
return os.path.getctime(file_path) | |
def generate_base_conf(file_path) -> dict: | |
''' | |
generate_base_conf 为 file_path 指定的文件(文章)生成必要的 YAML 配置。 | |
必要的配置包括:title, date:该函数会从文件的元数据读取信息来设置这两项。 | |
参数: | |
- file_path: 要删除行的文件路径; | |
返回: | |
- dict: 必要的配置,可用 yaml.dump 成 YAML 的那种 | |
''' | |
# title 取文件名 a.md 中的 a | |
title = os.path.basename(file_path) | |
title_list = title.split('.') | |
if len(title_list) > 1: | |
title = ''.join(title_list[:-1]) | |
# creation_time 取文件创建时间 | |
creation_time = get_creation_time(file_path) | |
date = datetime.datetime.fromtimestamp(creation_time) | |
return {'title': title, 'date': date} | |
def md_files_gen(dir, file_filter=lambda fp: True): | |
''' | |
md_files_gen 返回一个 generator,生成 dir 目录下所有使 file_filter 返回 True 的 `.md` 文件的绝对路径。 | |
参数: | |
- dir : 遍历起点目录路径; | |
- file_filter: 文件过滤器,一个函数,接受文件路径作为参数,返回 True 代表文件应该出现在结果中,返回 False 则忽略这个文件。 | |
''' | |
for dirpath, dirnames, filenames in os.walk(dir): | |
for filename in filenames: | |
if filename.lower().endswith('.md'): | |
file_path = os.path.join(dirpath, filename) | |
if file_filter(file_path): | |
yield file_path | |
def complete_file(file_path): | |
''' | |
complete_file 对 file_path 处的博客文章(`.md` 文件)补全 YAML 配置 | |
''' | |
assert os.path.isfile(file_path) and file_path.lower().endswith('.md') | |
print(file_path, ': ', end='') | |
conf = generate_base_conf(file_path) | |
data, yaml_line, has_yaml = read_yaml(file_path) | |
if has_yaml: | |
remove_lines(file_path, yaml_line) | |
if isinstance(data, dict): | |
conf.update(data) | |
if conf == data: # 配置和原本写在文件中的没有改变 | |
print('ok') | |
else: | |
print(f'{data} -> {conf}') | |
write_yaml(file_path, conf) | |
def complete(target_path, file_filter=lambda fp: True): | |
''' | |
complete 补全 target_path 处的博客文章 YAML 配置。 | |
若 target_path 为博客文章文件(`.md` 文件),且可使 file_filter 返回 True ,则补全该文件的 YAML 配置; | |
若 target_path 为目录则补全下所有使 file_filter 返回 True 的 `.md` 文件的 YAML 配置。 | |
''' | |
if os.path.isfile(target_path) and file_filter(target_path): | |
complete_file(target_path) | |
elif os.path.isdir(target_path): | |
blogs = md_files_gen(target_path, file_filter) | |
for article in blogs: | |
complete_file(article) | |
def print_usage(program_file): | |
usage = f'''usage: {program_file} [-h] files... | |
补全 files 处的博客文章 YAML 配置。 | |
-h help: 显示该帮助 | |
''' | |
print(usage) | |
if __name__ == "__main__": | |
if len(sys.argv) <= 1: | |
print(sys.argv[0], '参数不足') | |
print_usage(sys.argv[0]) | |
exit() | |
if sys.argv[1] in ['-h', '--help', 'help']: | |
print_usage(sys.argv[0]) | |
exit() | |
for arg in sys.argv[1:]: | |
print('检查', arg, '处的博客配置:') | |
complete(arg) | |
print('配置检查与自动补全完成.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment