Skip to content

Commit baf1fdc

Browse files
NewFutureCopilot
andauthored
refact(py): Refactor structure (NewFuture#481)
* 目录结构重构 * update config * rename dns to provider * patch version info * remove version replace Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add ENV GITHUB_REF_NAME to docker build --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent bdc9979 commit baf1fdc

27 files changed

Lines changed: 752 additions & 781 deletions

.github/patch.py

100644100755
Lines changed: 84 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,56 @@
11
#!/usr/bin/env python3
2-
"""
3-
自动将所有 try-except python2/3 兼容导入替换为 python3 only 导入,并显示处理日志
4-
"""
2+
53
import os
64
import re
5+
import time
6+
7+
ROOT = "."
8+
init_py_path = os.path.join(ROOT, "ddns", "__init__.py")
79

8-
ROOT = '.'
910
# 匹配 try-except 块,去除导入前缩进,保证import顶格,删除的行用空行代替
1011
PATTERN = re.compile(
11-
r'^[ \t]*try:[^\n]*python 3[^\n]*\n' # try: # python 3
12-
r'((?:[ \t]+[^\n]*\n)+?)' # python3 导入内容
13-
r'^[ \t]*except ImportError:[^\n]*\n' # except ImportError: # python 2
14-
r'((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)', # except块内容
15-
re.MULTILINE
12+
r"^[ \t]*try:[^\n]*python 3[^\n]*\n" # try: # python 3
13+
r"((?:[ \t]+[^\n]*\n)+?)" # python3 导入内容
14+
r"^[ \t]*except ImportError:[^\n]*\n" # except ImportError: # python 2
15+
r"((?:[ \t]+from[^\n]*\n|[ \t]+import[^\n]*\n)*)", # except块内容
16+
re.MULTILINE,
1617
)
1718

1819

1920
def dedent_imports_with_blank(import_block, try_block, except_block):
2021
"""
2122
保留python3导入并去除缩进,try/except及except内容用空行代替
2223
"""
23-
try_lines = try_block.count('\n')
24-
except_lines = except_block.count('\n')
25-
imports = ''.join(line.lstrip()
26-
for line in import_block.splitlines(keepends=True))
27-
return ('\n' * try_lines) + imports + ('\n' * except_lines)
24+
try_lines = try_block.count("\n")
25+
except_lines = except_block.count("\n")
26+
imports = "".join(line.lstrip() for line in import_block.splitlines(keepends=True))
27+
return ("\n" * try_lines) + imports + ("\n" * except_lines)
2828

2929

3030
def extract_pure_version(version_str):
3131
"""
3232
提取前4组数字并用点拼接,如 v1.2.3.beta4.5 -> 1.2.3.4
3333
"""
34-
import re
35-
nums = re.findall(r'\d+', version_str)
36-
return '.'.join(nums[:4]) if nums else "0.0.0"
34+
nums = re.findall(r"\d+", version_str)
35+
return ".".join(nums[:4]) if nums else "0.0.0"
3736

3837

39-
def update_nuitka_version(pyfile):
38+
def update_nuitka_version(pyfile, version=None):
4039
"""
4140
读取 __version__ 并替换 nuitka-project 版本号
4241
"""
43-
with open(pyfile, 'r', encoding='utf-8') as f:
44-
content = f.read()
45-
46-
# 提取 __version__ 变量
47-
version_match = re.search(
48-
r'__version__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
49-
if not version_match:
50-
print(f'No __version__ found in {pyfile}')
51-
return False
52-
53-
version_str = version_match.group(1)
54-
pure_version = extract_pure_version(version_str)
42+
pure_version = extract_pure_version(version)
5543

44+
with open(pyfile, "r", encoding="utf-8") as f:
45+
content = f.read()
5646
# 替换 nuitka-project 行
5747
new_content, n = re.subn(
58-
r'(# nuitka-project: --product-version=)[^\n]*',
59-
r'\g<1>' + pure_version,
60-
content
48+
r"(# nuitka-project: --product-version=)[^\n]*", r"\g<1>" + pure_version, content
6149
)
6250
if n > 0:
63-
with open(pyfile, 'w', encoding='utf-8') as f:
51+
with open(pyfile, "w", encoding="utf-8") as f:
6452
f.write(new_content)
65-
print(f'update nuitka-project version: {pure_version} in {pyfile}')
53+
print(f"update nuitka-project version: {pure_version} in {pyfile}")
6654
return True
6755
return False
6856

@@ -71,63 +59,56 @@ def add_nuitka_file_description(pyfile):
7159
"""
7260
添加 --file-description 配置,使用 __description__ 变量的值
7361
"""
74-
with open(pyfile, 'r', encoding='utf-8') as f:
62+
with open(init_py_path, "r", encoding="utf-8") as f:
7563
content = f.read()
7664

7765
# 提取 __description__ 变量的值
78-
desc_match = re.search(
79-
r'__description__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
66+
desc_match = re.search(r'__description__\s*=\s*[\'"]([^\'"]+)[\'"]', content)
8067
if not desc_match:
81-
print(f'No __description__ found in {pyfile}')
68+
print(f"No __description__ found in {init_py_path}")
8269
return False
83-
8470
description = desc_match.group(1)
85-
if not content.endswith('\n'):
86-
content += '\n'
87-
description_line = f'# nuitka-project: --file-description="{description}"\n'
88-
if description_line not in content:
89-
content += description_line
90-
91-
with open(pyfile, 'w', encoding='utf-8') as f:
92-
f.write(content)
93-
print(f'Added file-description to {pyfile}')
71+
description_line = f'\n# nuitka-project: --file-description="{description}"\n'
72+
with open(pyfile, "a", encoding="utf-8") as f:
73+
f.write(description_line)
9474
return True
9575

9676

9777
def add_nuitka_include_modules(pyfile):
9878
"""
9979
读取 dns 目录下的所有 Python 模块,并添加到 run.py 末尾
10080
"""
101-
dns_dir = os.path.join(ROOT, 'dns')
81+
dns_dir = os.path.join(ROOT, "ddns/provider")
10282
if not os.path.exists(dns_dir):
103-
print(f'DNS directory not found: {dns_dir}')
83+
print(f"DNS directory not found: {dns_dir}")
10484
return False
10585

10686
# 获取所有 Python 模块文件
10787
modules = []
10888
for filename in os.listdir(dns_dir):
109-
if filename.endswith('.py') and filename != '__init__.py':
89+
if filename.endswith(".py") and filename != "__init__.py":
11090
module_name = filename[:-3] # 去掉 .py 扩展名
111-
modules.append(f'dns.{module_name}')
91+
modules.append(f"ddns.provider.{module_name}")
11292

11393
if not modules:
114-
print('No DNS modules found')
94+
print("No DNS modules found")
11595
return False
11696

11797
# 直接在文件末尾追加配置行
118-
with open(pyfile, 'a', encoding='utf-8') as f:
98+
with open(pyfile, "a", encoding="utf-8") as f:
11999
for module in sorted(modules):
120-
f.write(f'# nuitka-project: --include-module={module}\n')
100+
f.write(f"# nuitka-project: --include-module={module}\n")
121101

122102
print(f'Added {len(modules)} DNS modules to {pyfile}: {", ".join(modules)}')
123103
return True
124104

125105

126106
def remove_python2_compatibility(pyfile):
127107
"""
108+
自动将所有 try-except python2/3 兼容导入替换为 python3 only 导入,并显示处理日志
128109
删除指定文件中的 python2 兼容代码,逐行处理
129110
"""
130-
with open(pyfile, 'r', encoding='utf-8') as f:
111+
with open(pyfile, "r", encoding="utf-8") as f:
131112
lines = f.readlines()
132113

133114
new_lines = []
@@ -136,28 +117,27 @@ def remove_python2_compatibility(pyfile):
136117
while i < len(lines):
137118
line = lines[i]
138119
# 匹配 "try: # python3" 或 "try: # python 3"
139-
if re.match(r'^[ \t]*try:[^\n]*python ?3', line):
120+
if re.match(r"^[ \t]*try:[^\n]*python ?3", line):
140121
try_block = []
141122
except_block = []
142123
i += 1
143124
# 收集try块内容
144-
while i < len(lines) and lines[i].startswith((' ', '\t')):
125+
while i < len(lines) and lines[i].startswith((" ", "\t")):
145126
try_block.append(lines[i].lstrip())
146127
i += 1
147128
# 跳过空行
148-
while i < len(lines) and lines[i].strip() == '':
129+
while i < len(lines) and lines[i].strip() == "":
149130
i += 1
150131
# 检查是否存在except块 (不检查具体错误类型,但必须包含python2或python 2)
151-
if i < len(lines) and re.match(r'^[ \t]*except[^\n]*python ?2', lines[i]):
132+
if i < len(lines) and re.match(r"^[ \t]*except[^\n]*python ?2", lines[i]):
152133
i += 1
153134
# 收集except块内容
154135
except_block = []
155-
while i < len(lines) and lines[i].startswith((' ', '\t')):
136+
while i < len(lines) and lines[i].startswith((" ", "\t")):
156137
except_block.append(lines[i])
157138
i += 1
158139
# 添加try块内容,except块用空行替代
159-
new_lines.extend(['\n'] + try_block + ['\n']
160-
* (len(except_block) + 1))
140+
new_lines.extend(["\n"] + try_block + ["\n"] * (len(except_block) + 1))
161141
changed = True
162142
else:
163143
# 没有except块,原样保留
@@ -168,30 +148,63 @@ def remove_python2_compatibility(pyfile):
168148
i += 1
169149

170150
if changed:
171-
with open(pyfile, 'w', encoding='utf-8') as f:
151+
with open(pyfile, "w", encoding="utf-8") as f:
172152
f.writelines(new_lines)
173-
print(f'Removed python2 compatibility from {pyfile}')
153+
print(f"Removed python2 compatibility from {pyfile}")
154+
155+
156+
def extract_version_from_env():
157+
"""
158+
从环境变量中提取版本号
159+
"""
160+
ref = os.environ.get("GITHUB_REF_NAME")
161+
if not ref:
162+
return time.strftime("0.0.%m%d.%H%M") # 默认版本号
163+
if ref and ref.startswith("v"):
164+
return ref[1:] # 去掉前缀 'v'
165+
return ref # 返回原始版本号
174166

175167

176168
def main():
177169
"""
178170
遍历所有py文件并替换兼容导入,同时更新nuitka版本号
179171
"""
180172
run_py_path = os.path.join(ROOT, "run.py")
181-
update_nuitka_version(run_py_path)
173+
version = extract_version_from_env()
174+
update_nuitka_version(run_py_path, version)
182175
add_nuitka_file_description(run_py_path)
183176
add_nuitka_include_modules(run_py_path)
184177

178+
# 修改__init__.py 中的 __version__
179+
180+
replace_version_in_init(version, init_py_path)
181+
185182
changed_files = 0
186183
for dirpath, _, filenames in os.walk(ROOT):
187184
for fname in filenames:
188-
if fname.endswith('.py'):
185+
if fname.endswith(".py"):
189186
fpath = os.path.join(dirpath, fname)
190187
remove_python2_compatibility(fpath)
191188
changed_files += 1
192-
print('done')
193-
print(f'Total processed files: {changed_files}')
189+
print("done")
190+
print(f"Total processed files: {changed_files}")
191+
192+
193+
def replace_version_in_init(version, init_py_path):
194+
"""
195+
替换 ddns/__init__.py 中的 __version__ 变量
196+
"""
197+
version_str = f'v{version}@{time.strftime("%Y-%m-%dT%H:%M:%S")}'
198+
with open(init_py_path, "r", encoding="utf-8") as f:
199+
content = f.read()
200+
new_content = re.sub(
201+
r'__version__\s*=\s*[\'"]([^\'"]+)[\'"]', f'__version__ = "{version_str}"', content
202+
)
203+
if new_content != content:
204+
with open(init_py_path, "w", encoding="utf-8") as f:
205+
f.write(new_content)
206+
print(f"Updated __version__ in {init_py_path} to {version_str}")
194207

195208

196-
if __name__ == '__main__':
209+
if __name__ == "__main__":
197210
main()

.github/workflows/build.yml

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,23 @@ jobs:
5858
run: ${{env.PY}} run.py || test -f config.json
5959
- name: test version
6060
run: ${{env.PY}} run.py --version
61+
- name: test run module
62+
run: ${{env.PY}} -m "ddns" -h
63+
64+
- name: test patch
65+
if: ${{ matrix.version != '2.7' }}
66+
run: python3 .github/patch.py
67+
- name: test help
68+
if: ${{ matrix.version != '2.7' }}
69+
run: python3 run.py -h
70+
- name: test run
71+
if: ${{ matrix.version != '2.7' }}
72+
run: python3 run.py || test -f config.json
73+
- name: test version
74+
run: ${{env.PY}} run.py --version
75+
- name: test run module
76+
if: ${{ matrix.version != '2.7' }}
77+
run: ${{env.PY}} -m "ddns" -h
6178

6279
pypi:
6380
runs-on: ubuntu-latest
@@ -70,9 +87,8 @@ jobs:
7087
- name: Install dependencies
7188
run: |
7289
python -m pip install --upgrade pip
73-
pip install build
74-
- run: sed -i -e 's#"doc/img/ddns.svg"#"https://ddns.newfuture.cc/doc/img/ddns.svg"#' README.md
75-
- run: sed -i -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py
90+
pip install build setuptools-scm
91+
- run: sed -i -E 's#([("])/doc/#\1https://ddns.newfuture.cc/doc/#g' README.md
7692
- name: Build package
7793
run: python -m build --sdist --wheel --outdir dist/
7894

@@ -116,11 +132,6 @@ jobs:
116132
- name: remove python2 code
117133
run: python3 .github/patch.py
118134

119-
# Prepare build version and cert
120-
- name: Replace build version
121-
run: sed -i.tmp -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py && rm run.py.tmp
122-
shell: bash
123-
124135
- name: Set up on Linux
125136
if: runner.os == 'Linux'
126137
run: sudo apt-get install -y --no-install-recommends patchelf
@@ -176,7 +187,6 @@ jobs:
176187
timeout-minutes: 8
177188
steps:
178189
- uses: actions/checkout@v4
179-
- run: sed -i -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py
180190

181191
- uses: docker/setup-buildx-action@v3
182192
- uses: docker/build-push-action@v6
@@ -188,7 +198,9 @@ jobs:
188198
tags: ddnsbin
189199
target: export
190200
outputs: type=local,dest=./output
191-
build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:${{matrix.libc}}-master
201+
build-args: |
202+
BUILDER=ghcr.io/newfuture/nuitka-builder:${{matrix.libc}}-master
203+
GITHUB_REF_NAME=${{ github.ref_name }}
192204
# 测试构建的二进制文件
193205
- name: Test built binaries
194206
run: |
@@ -238,7 +250,6 @@ jobs:
238250
}}
239251
steps:
240252
- uses: actions/checkout@v4
241-
- run: sed -i -e "s#\${BUILD_VERSION}#${{ github.ref_name }}#" -e "s/\${BUILD_DATE}/$(date --iso-8601=seconds)/" run.py
242253
- uses: docker/setup-qemu-action@v3 # 仅仅在需要时启用 QEMU 支持
243254
if: matrix.host == 'qemu'
244255
with:
@@ -252,7 +263,9 @@ jobs:
252263
push: false
253264
tags: ddns:test
254265
outputs: type=oci,dest=./multi-platform-image.tar
255-
build-args: BUILDER=ghcr.io/newfuture/nuitka-builder:master
266+
build-args: |
267+
BUILDER=ghcr.io/newfuture/nuitka-builder:master
268+
GITHUB_REF_NAME=${{ github.ref_name }}
256269
257270
# 准备测试环境
258271
- name: Prepare test environment

0 commit comments

Comments
 (0)