289 lines
12 KiB
Python
289 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
import locale
|
|
import sys
|
|
|
|
# 设置语言环境为 UTF-8
|
|
locale.setlocale(locale.LC_ALL, '')
|
|
encoding = locale.getpreferredencoding()
|
|
if encoding.lower() != 'utf-8':
|
|
# 强制使用 UTF-8 编码
|
|
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
|
# 如果 locale 无法设置, 使用以下方式
|
|
# sys.stdout.reconfigure(encoding='utf-8')
|
|
# sys.stderr.reconfigure(encoding='utf-8')
|
|
|
|
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
|
from collections import defaultdict
|
|
import os
|
|
import json
|
|
import re
|
|
from datetime import datetime
|
|
|
|
language_codes = ["zh-Hans", "zh-Hant-HK", "zh-Hant-TW", "en", "fr", "es", "ja", "ko", "ru", "ar"]
|
|
language_code_default = "zh-Hans"
|
|
|
|
# 定义各语言的日期格式
|
|
date_formats = {
|
|
"zh-Hans": "%Y 年 %m 月 %d 日", # 简体中文
|
|
"zh-Hant-HK": "%Y 年 %m 月 %d 日", # 香港繁体中文
|
|
"zh-Hant-TW": "%Y 年 %m 月 %d 日", # 台湾繁体中文
|
|
"en": "%b %d, %Y", # 英文
|
|
"fr": "%d %B %Y", # 法文
|
|
"es": "%d de %B de %Y", # 西班牙文
|
|
"ja": "%Y 年 %m 月 %d 日", # 日文
|
|
"ko": "%Y 년 %m 월 %d 일", # 韩文
|
|
"ru": "%d %B %Y года", # 俄文
|
|
"ar": "%d %B %Y" # 阿拉伯文
|
|
}
|
|
|
|
project_root_dir = os.path.abspath(os.path.join(os.getcwd(), os.pardir))
|
|
changelog_files_source_dir = os.path.join(project_root_dir, 'app', 'src', 'main', 'assets-app', 'doc')
|
|
changelog_root_dir = os.path.join(project_root_dir, '.changelog')
|
|
readme_root_dir = os.path.join(project_root_dir, '.readme')
|
|
|
|
# 设置模板文件夹
|
|
file_loader = FileSystemLoader([
|
|
changelog_root_dir,
|
|
readme_root_dir,
|
|
])
|
|
|
|
# 创建 Jinja2 环境
|
|
env = Environment(
|
|
loader=file_loader,
|
|
undefined=StrictUndefined,
|
|
)
|
|
|
|
# 读取模板文件
|
|
template_readme = env.get_template('template_readme.md')
|
|
template_changelog = env.get_template('template_changelog.md')
|
|
|
|
language_content_map = {}
|
|
changelog_content_map = {}
|
|
changelog_data_map = defaultdict(dict)
|
|
|
|
# 读取共用 JSON 文件
|
|
with open(os.path.join(readme_root_dir, 'common.json'), 'r', encoding='utf-8') as common_json_file:
|
|
common_data = json.load(common_json_file)
|
|
|
|
# 检查并转换 common_data 中的值类型
|
|
for key, value in common_data.items():
|
|
if isinstance(value, int):
|
|
common_data[key] = str(value)
|
|
elif isinstance(value, str) and value.isdigit():
|
|
common_data[key] = int(value)
|
|
|
|
|
|
def format_date(date_str, lang_code):
|
|
if date_str == "%CURRENT_DATE%":
|
|
# 获取当前日期并格式化为 YYYY/MM/DD
|
|
date_str = datetime.now().strftime("%Y/%m/%d")
|
|
|
|
if lang_code in date_formats:
|
|
try:
|
|
date_obj = datetime.strptime(date_str, "%Y/%m/%d")
|
|
formatted_date = date_obj.strftime(date_formats[lang_code])
|
|
|
|
# 在 formatted_date 中每个非字母和非数字字符前后增加空格 (如果没有空格)
|
|
formatted_date_with_spaces = re.sub(r'(\d+)', r' \1 ', formatted_date).strip()
|
|
# 将数字与逗号之间的空格去除
|
|
formatted_date_with_spaces = re.sub(r'(\d)\s+,', r'\1,', formatted_date_with_spaces)
|
|
formatted_date_with_spaces = re.sub(r'\s+', ' ', formatted_date_with_spaces)
|
|
|
|
# 去掉前导零
|
|
formatted_date_no_leading_zeros = re.sub(r'\b0+(\d)', r'\1', formatted_date_with_spaces)
|
|
|
|
return formatted_date_no_leading_zeros
|
|
except ValueError as e:
|
|
print(f"Date format error: {e}")
|
|
return date_str
|
|
|
|
|
|
def init_languages():
|
|
if language_code_default not in language_codes:
|
|
raise ValueError(f"Default language code \"{language_code_default}\" is not in the language codes list")
|
|
|
|
for language_code in language_codes:
|
|
with open(os.path.join(readme_root_dir, f'lang_{language_code}.json'), 'r', encoding='utf-8') as language_json_file:
|
|
raw_data = json.load(language_json_file)
|
|
prefix = "$var_"
|
|
processed_data = {key[len(prefix):] if key.startswith(prefix) else key: value for key, value in raw_data.items()}
|
|
|
|
# 合并共用 JSON 数据
|
|
merged_data = {**common_data, **processed_data}
|
|
|
|
# 处理日期转换标记
|
|
for key, value in merged_data.items():
|
|
if key.startswith('var_date_'):
|
|
merged_data[key] = format_date(value, language_code)
|
|
|
|
# 计算活跃维护年数
|
|
def calc_years_from(date_str: str) -> str:
|
|
try:
|
|
since_date = datetime.strptime(date_str, "%Y/%m/%d")
|
|
days = (datetime.now() - since_date).days
|
|
return f"{days / 365:.2f}" # 始终保留两位小数
|
|
except ValueError:
|
|
return date_str # 日期格式异常时保持原值
|
|
|
|
maintenance_map = {
|
|
"data_active_maintenance_phase_tonyjiangwj_auto_js_since_date": "2019/11/21",
|
|
"data_active_maintenance_phase_supermonster003_autojs6_since_date": "2021/12/01",
|
|
"data_active_maintenance_phase_aiselp_autox_since_date": "2024/04/21",
|
|
}
|
|
|
|
# 自动遍历 merged_data, 处理所有 *_since_date 键
|
|
for since_key, since_date in maintenance_map.items():
|
|
target_key = since_key[:-len('_since_date')] # 去掉后缀
|
|
merged_data[target_key] = calc_years_from(since_date)
|
|
|
|
# 渲染动态字符串
|
|
language_content_map[language_code] = render_dynamic_strings(merged_data, merged_data)
|
|
|
|
with open(os.path.join(changelog_root_dir, f'lang_{language_code}.json'), 'r', encoding='utf-8') as changelog_json_file:
|
|
raw_data = json.load(changelog_json_file)
|
|
changelog_content = {}
|
|
changelog_data = {}
|
|
for key, value in raw_data.items():
|
|
if key != "$data":
|
|
changelog_content[key] = value
|
|
else:
|
|
changelog_data = value
|
|
|
|
# 渲染动态字符串
|
|
changelog_content_map[language_code] = render_dynamic_strings(changelog_content, changelog_content)
|
|
changelog_data_map[language_code] = render_dynamic_strings(changelog_data, changelog_content)
|
|
|
|
|
|
def extract_latest_versions(lang_code, lang_content, num_versions=3):
|
|
file_path = os.path.abspath(os.path.join(changelog_files_source_dir, f"CHANGELOG-{lang_code}.md"))
|
|
changelog_content = changelog_content_map[lang_code]
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
data = file.read()
|
|
|
|
# 正则模式匹配版本号及其更新内容
|
|
pattern = re.compile(r'(# v(\d+)\.(\d+)\.(\d+).*?)(?=# v|$)', re.DOTALL)
|
|
|
|
versions = pattern.findall(data)
|
|
|
|
# 获取最新的 num_versions 个版本
|
|
latest_versions = versions[:num_versions]
|
|
|
|
filtered_versions = []
|
|
for version, major, minor, patch in latest_versions:
|
|
# 动态生成的版本链接
|
|
version_url_fragment = f'v{major}{minor}{patch}'
|
|
|
|
# 构建版本内容的最终字符串
|
|
improvement_label = changelog_content['changelog_label_improvement']
|
|
dependency_text = lang_content['text_changelog_item_dependency']
|
|
# noinspection HttpUrlsUsage
|
|
base_url = 'http://project.autojs6.com/blob/master/app/src/main/assets-app/doc/CHANGELOG.md'
|
|
|
|
original_lines = version.strip().split("\n")
|
|
filtered_content = "\n".join(
|
|
line for line in original_lines
|
|
if not line.strip().startswith(f"* `{changelog_content['changelog_label_dependency']}`")
|
|
)
|
|
is_filtered = len(original_lines) != len(filtered_content.split("\n"))
|
|
|
|
if is_filtered:
|
|
filtered_versions.append(filtered_content)
|
|
filtered_versions.append(f'* `{improvement_label}` {dependency_text} _[`CHANGELOG.md`]({base_url}#{version_url_fragment})_\n')
|
|
else:
|
|
filtered_versions.append(f'{filtered_content}\n')
|
|
|
|
return filtered_versions
|
|
|
|
|
|
def handle_changelog_placeholder(aim_lang_code, aim_content):
|
|
histories_str = ""
|
|
for version_name, data in changelog_data_map[aim_lang_code].items():
|
|
if not isinstance(data, dict):
|
|
raise TypeError(f"Expected data to be dict, but got {type(data)} for version {version_name} in language {aim_lang_code}")
|
|
|
|
histories_str += f"# {version_name}\n\n"
|
|
histories_str += f"###### {data['released_date']}"
|
|
if 'released_hint' in data:
|
|
histories_str += f" - {data['released_hint']}"
|
|
histories_str += "\n\n"
|
|
for simple_key in ['hint', 'feature', 'fix', 'improvement', 'dependency']:
|
|
if simple_key in data:
|
|
for text in data[simple_key]:
|
|
label = changelog_content_map[aim_lang_code][f'changelog_label_{simple_key}']
|
|
histories_str += f"* `{label}` {text}\n"
|
|
histories_str += "\n"
|
|
|
|
aim_content['h3_version_histories'] = language_content_map[aim_lang_code]['h3_version_histories']
|
|
aim_content['placeholder_version_histories'] = histories_str.rstrip("\n")
|
|
|
|
|
|
def handle_readme_placeholder(aim_lang_code, aim_content):
|
|
new_array = []
|
|
for lang_code, content in language_content_map.items():
|
|
if lang_code == aim_lang_code:
|
|
new_array.append(f" - {content['$name']} [{lang_code}] # {content['text_current_lowercase']}")
|
|
else:
|
|
# noinspection HttpUrlsUsage
|
|
new_array.append(f" - [{content['$name']} [{lang_code}]](http://project.autojs6.com/blob/master/.readme/README-{lang_code}.md)")
|
|
aim_content['placeholder_ul_languages_all_supported'] = "\n".join(new_array)
|
|
|
|
aim_content['placeholder_latest_three_version_histories'] = "\n".join(extract_latest_versions(aim_lang_code, aim_content)).rstrip("\n")
|
|
|
|
aim_content['placeholder_read_more_in_changelog_md'] = f"[CHANGELOG.md](http://project.autojs6.com/blob/master/app/src/main/assets-app/doc/CHANGELOG-{aim_lang_code}.md)"
|
|
|
|
|
|
def render_dynamic_strings(data, placeholder_map):
|
|
""" Recursively render dynamic strings within the JSON data using Jinja environment """
|
|
if isinstance(data, dict):
|
|
return {key: render_dynamic_strings(value, placeholder_map) for key, value in data.items()}
|
|
elif isinstance(data, list):
|
|
return [render_dynamic_strings(item, placeholder_map) for item in data]
|
|
elif isinstance(data, str):
|
|
template = env.from_string(data)
|
|
return template.render(placeholder_map)
|
|
else:
|
|
return data
|
|
|
|
|
|
def generate_changelog_files():
|
|
for lang_code, content in changelog_content_map.items():
|
|
handle_changelog_placeholder(lang_code, content)
|
|
output = template_changelog.render(**content)
|
|
output_file = f'CHANGELOG-{lang_code}.md'
|
|
output_path = os.path.join(changelog_files_source_dir, output_file)
|
|
|
|
with open(output_path, 'w', encoding='utf-8') as file:
|
|
file.write(output)
|
|
print(f'Generated {output_file} at {output_path}')
|
|
|
|
if lang_code == language_code_default:
|
|
default_output_file = 'CHANGELOG.md'
|
|
default_output_path = os.path.join(changelog_files_source_dir, default_output_file)
|
|
with open(default_output_path, 'w', encoding='utf-8') as file:
|
|
file.write(output)
|
|
print(f'Generated {default_output_file} at {default_output_path}')
|
|
|
|
|
|
def generate_readme_files():
|
|
for lang_code, content in language_content_map.items():
|
|
handle_readme_placeholder(lang_code, content)
|
|
output = template_readme.render(**content)
|
|
output_file = f'README-{lang_code}.md'
|
|
output_path = os.path.join(project_root_dir, '.readme', output_file)
|
|
|
|
with open(output_path, 'w', encoding='utf-8') as file:
|
|
file.write(output)
|
|
print(f'Generated {output_file} at {output_path}')
|
|
|
|
if lang_code == language_code_default:
|
|
default_output_file = 'README.md'
|
|
default_output_path = os.path.join(project_root_dir, default_output_file)
|
|
with open(default_output_path, 'w', encoding='utf-8') as file:
|
|
file.write(output)
|
|
print(f'Generated {default_output_file} at {default_output_path}')
|
|
|
|
|
|
init_languages()
|
|
generate_changelog_files()
|
|
generate_readme_files() |