前言:由于我大部分时间使用 typora进行写作。在 typora中,我使用的是如下文件结构:

1
2
3
4
5
6
7
BLOG/
├── imgs/
├── article-1/
├── img1.png
├── img2.png
├── article-1.md
├── artical-2.md

此时markdown中的图片位置为:

1
![img_name](/imgs/article-1/img1.png)

我希望实现的功能是:

  1. 我不希望使用图床,因为这会改变我在本地时的写作习惯,每次打开typora都要加载一堆图片。
  2. 我不希望对markdown中的图片位置进行大量的改动,这会增加很多工作量。

此外,由于我的博客目录和我平时的写作目录是分开的,我不需要在博客目录下对markdown的图片进行预览。

因此,大道至简,直接使用默认的source作为根目录,在其下面创建imgs文件夹,形成如下文件结构:

1
2
3
4
5
6
7
8
BLOG/source/
├── imgs/
├── article-1
├── img1.png
├── img2.png
├──_posts/
├── article-1.md
├── artical-2.md

每次更新博客时,只需将对应的.md文件和图片文件夹移动至对应位置即可。

但是关于hexo路径还是有几个问题:

  1. 路径中不能含有空格,但是中文没有问题
  2. cover要使用/imgs/article-1/cover.png

因此还是需要写一段脚本,在复制到博客目录下的时候自动将空格替换为%20。既然写都写了,不如将当前日期,更新日期,图片复制也一并完成了。

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import os
import re
import shutil
from datetime import datetime
import argparse
import urllib.parse

def replace_spaces_in_image_paths(markdown_content):
# 1. 替换 Markdown 中的图片路径
image_pattern = r'!\[([^\]]*)\]\(([^)]+)\)' # 匹配 ![alt_text](/image_path)
img_html_pattern = r'(<img\s+)(src="[^"]+")([^>]*>)' # 匹配 <img src="/image_path" />
def replace_image(match):
alt_text = match.group(1)
image_path = match.group(2)
if not image_path.startswith('/'):
image_path = '/' + image_path
# URL 编码路径,保留 `/` 不被转义
encoded_path = urllib.parse.quote(image_path, safe='/')
return f'![{alt_text}](/%7Bencoded_path%7D)'

def replace_html_img(match):
src_part = match.group(2) # src="image_path"
other_attributes = match.group(3) # 其它属性部分
# 获取 src 路径
image_path = src_part.split('=')[1].strip('"')
if not image_path.startswith('/'):
image_path = '/' + image_path
# URL 编码路径,保留 `/` 不被转义
encoded_path = urllib.parse.quote(image_path, safe='/')
# 返回修改后的 <img> 标签,src 被更新,其他属性保持不变
return f'{match.group(1)}src="{encoded_path}"{other_attributes}'

markdown_content = re.sub(image_pattern, replace_image, markdown_content)
markdown_content = re.sub(img_html_pattern, replace_html_img, markdown_content)

# 2. 替换 cover: 后的路径
front_matter_pattern = r'---\n(.*?)\n---'
match = re.search(front_matter_pattern, markdown_content, re.DOTALL)

if match:
front_matter = match.group(1)

# 2. 替换 cover: 后的路径
cover_pattern = r'cover:\s*(\S.*)' # 匹配 cover: 后面的路径

def replace_cover_path(match):
cover_path = match.group(1).strip()

# 确保路径以 `/` 开头
if not cover_path.startswith('/'):
cover_path = '/' + cover_path

# URL 编码路径
encoded_cover_path = urllib.parse.quote(cover_path, safe='/')

return f'cover: {encoded_cover_path}'

# 使用正则替换
front_matter = re.sub(cover_pattern, replace_cover_path, front_matter)

# 3. 将更新后的 Front Matter 放回原文档中
markdown_content = markdown_content[:match.start(1)] + front_matter + markdown_content[match.end(1):]

return markdown_content
def auto_adding_date(markdown_content):
# 正则匹配 YAML Front Matter
front_matter_pattern = r'---\n(.*?)\n---'
match = re.search(front_matter_pattern, markdown_content, re.DOTALL)

if match:
front_matter = match.group(1)

# 获取当前日期和时间,格式:year/month/day/hour:minute:second
current_date_time = datetime.now().strftime('%Y/%m/%d/%H:%M:%S')

# 正则匹配 date 和 updated
date_pattern = r'date:\s*(\d{4}/\d{2}/\d{2}/\d{2}:\d{2}:\d{2})'
updated_pattern = r'updated:\s*(\d{4}/\d{2}/\d{2}/\d{2}:\d{2}:\d{2})'

has_date = re.search(date_pattern, front_matter)
has_updated = re.search(updated_pattern, front_matter)

if has_date:
# 仅更新 updated
if has_updated:
front_matter = re.sub(updated_pattern, f'updated: {current_date_time}', front_matter)
else:
front_matter += f'\nupdated: {current_date_time}'
else:
# 添加 date 和 updated
front_matter += f'\ndate: {current_date_time}\nupdated: {current_date_time}'

# 替换原始 Front Matter 部分
markdown_content = markdown_content[:match.start(1)] + front_matter + markdown_content[match.end(1):]

return markdown_content


def copy_images_to_blog(org_img_path, BLOG_img_path):
if os.path.exists(org_img_path) and os.path.isdir(org_img_path): # 确保 org_img_path 存在并且是文件夹
os.makedirs(BLOG_img_path, exist_ok=True) # 确保目标目录存在

for file_name in os.listdir(org_img_path): # 遍历源文件夹
src_file = os.path.join(org_img_path, file_name) # 获取源文件路径
dest_file = os.path.join(BLOG_img_path, file_name) # 目标文件路径

if os.path.isfile(src_file): # 只复制文件,避免子文件夹
shutil.copy2(src_file, dest_file) # 复制文件,保留原始元数据
print(f"Copied: {src_file} -> {dest_file}")
else:
print(f"Source folder '{org_img_path}' does not exist.")


def parse_args():
parser = argparse.ArgumentParser(description="Process a markdown file and copy images to BLOG.")
parser.add_argument("-n", "--name", required=True, help="Markdown file name (e.g., example.md)")
return parser.parse_args()


if __name__ == '__main__':
args = parse_args()
file_name = args.name # 从命令行获取文件名
# file_path = os.path.abspath(file_name) # 设为当前目录下的文件
# 如果输入的是相对路径,转为绝对路径
if not os.path.isabs(file_name):
file_path = os.path.abspath(file_name) # 设为当前目录下的文件
else:
file_path = file_name # 已经是绝对路径,不需要转换
file_base_name = os.path.splitext(os.path.basename(file_name))[0] # 去掉扩展名的文件名
# 相对路径 - 原始图片
org_img_path = os.path.join(os.path.dirname(file_path), "imgs", file_base_name)

# 绝对路径 - 目标 BLOG 目录
BLOG_root = r"path/to/BLOG"
BLOG_path = os.path.join(BLOG_root, "source", "_posts", os.path.basename(file_name))
BLOG_img_path = os.path.join(BLOG_root, "source", "imgs", file_base_name)

print(f"Processing file: {file_path}")
print(f"Org image path: {org_img_path}")
print(f"Blog post path: {BLOG_path}")
print(f"Blog image path: {BLOG_img_path}")

# 读取文件内容
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
# step1. 更新date
new_content = auto_adding_date(content)
with open(file_path, 'w', encoding='utf-8') as file:
file.write(new_content)
print(f"Markdown 文件已修改并保存为 '{file_path}'")

# step2. 替换路径中的空格
new_content = replace_spaces_in_image_paths(new_content)
with open(BLOG_path, 'w', encoding='utf-8') as file:
file.write(new_content)
print(f"Markdown 文件已修改并保存为 '{BLOG_path}'")

# step3. 复制图片文件夹
copy_images_to_blog(org_img_path, BLOG_img_path)