Repository navigation
Expand file tree
/
Copy pathmake_ankiaddon.py
More file actions
165 lines (140 loc) · 5.32 KB
/
Copy pathmake_ankiaddon.py
File metadata and controls
165 lines (140 loc) · 5.32 KB
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
161
162
163
164
165
import os
import argparse
import sys
import zipfile
from datetime import datetime
from pathlib import Path
from bump import (
bump_version as bump_patch_version,
read_current_version,
sync_version,
validate_version,
)
# Configuration
ADDON_NAME = "AI_Hints"
ADDON_DIR = "addon"
def load_gitignore_patterns(root_dir: Path) -> list[str]:
gitignore_path = root_dir / ".gitignore"
if not gitignore_path.exists():
return []
patterns = []
with gitignore_path.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
patterns.append(line)
return patterns
def is_ignored(path: Path, root_dir: Path, patterns: list[str]) -> bool:
try:
rel = path.relative_to(root_dir).as_posix()
except ValueError:
return False
if ".git" in Path(rel).parts:
return True
p = Path(rel)
def _hit(pat: str) -> bool:
q = pat.rstrip("/")
return p.match(q) or p.match(q + "/*")
return any(_hit(pat) for pat in patterns)
def artifact_names(
addon_name: str,
version: str,
when: datetime | None = None,
) -> tuple[str, str]:
dt = when or datetime.today()
timestamp = dt.strftime("%Y%m%d%H%M")
base = f"{addon_name}_v{version}_{timestamp}"
return f"{base}.zip", f"{base}.ankiaddon"
def bump_version(addon_path: Path | None = None) -> int:
target = addon_path or Path(ADDON_DIR)
return bump_patch_version(target)
def resolve_build_version(
addon_path: Path,
explicit_version: str | None = None,
) -> str:
if explicit_version is None:
code = bump_version(addon_path)
if code != 0:
raise RuntimeError("Failed to bump version.")
return read_current_version(addon_path)
version = validate_version(explicit_version)
sync_version(version, addon_path)
print(f"Using explicit version: {version}")
return version
def create_ankiaddon(explicit_version: str | None = None, clean: bool = False) -> int:
# Get the project root and addon directory
root_dir = Path(__file__).resolve().parent
addon_path = root_dir / ADDON_DIR
if not addon_path.exists():
print(f"Error: {ADDON_DIR} directory not found.")
return 1
try:
build_version = resolve_build_version(addon_path, explicit_version)
except Exception as e:
print(f"Error: Could not prepare build version: {e}")
return 1
zip_name, final_name = artifact_names(ADDON_NAME, build_version)
# Preserve older release packages by default. Use --clean only when
# intentionally removing existing root-level .ankiaddon files.
if clean:
for f in root_dir.glob("*.ankiaddon"):
try:
os.remove(f)
except Exception:
pass
# Load gitignore patterns to filter files dynamically
gitignore_patterns = load_gitignore_patterns(root_dir)
print(f"Creating {final_name} from {ADDON_DIR} using .gitignore rules...")
with zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Walk through the addon directory specifically
for root, dirs, files in os.walk(addon_path):
# Prune directories in-place using gitignore
pruned_dirs = []
for d in dirs:
dir_path = Path(root) / d
if not is_ignored(dir_path, root_dir, gitignore_patterns):
pruned_dirs.append(d)
dirs[:] = pruned_dirs
for file in files:
file_path = Path(root) / file
# Check if file matches gitignore rules
if is_ignored(file_path, root_dir, gitignore_patterns) and file != "config.json":
continue
# Exclude local runtime state, configuration, and log files
if file in ['meta.json', 'batch_state.json', 'blacklist.json'] or file.endswith('.log') or '.log.' in file:
continue
# Calculate the path relative to the 'addon/' folder
# so that __init__.py is at the root of the zip
archive_name = file_path.relative_to(addon_path)
zipf.write(file_path, archive_name)
# Rename to .ankiaddon
if os.path.exists(final_name):
os.remove(final_name)
os.rename(zip_name, final_name)
print(f"Successfully created: {final_name}")
return 0
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description=(
"Create .ankiaddon package. "
"If no version is provided, patch version is auto-bumped via bump.py. "
"Existing packages are preserved unless --clean is specified."
)
)
parser.add_argument(
"version",
nargs="?",
help="Optional explicit version (major.minor[.patch]) to set before packaging.",
)
parser.add_argument(
"--clean",
action="store_true",
help="Delete existing root-level .ankiaddon packages before building.",
)
return parser.parse_args(argv[1:])
def main(argv: list[str]) -> int:
args = parse_args(argv)
return create_ankiaddon(args.version, clean=args.clean)
if __name__ == "__main__":
raise SystemExit(main(sys.argv))