codeannex 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeannex-0.4.0 → codeannex-0.4.2}/PKG-INFO +1 -1
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/__main__.py +17 -4
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/pdf_builder.py +12 -12
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/interface/cli.py +22 -3
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/git_utils.py +18 -2
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/fonts.py +15 -4
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/PKG-INFO +1 -1
- {codeannex-0.4.0 → codeannex-0.4.2}/pyproject.toml +1 -1
- {codeannex-0.4.0 → codeannex-0.4.2}/LICENSE +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/README.md +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/__init__.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/__init__.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/config.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/interface/__init__.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/__init__.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/file_utils.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/__init__.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/highlight.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/text_utils.py +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/SOURCES.txt +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/dependency_links.txt +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/entry_points.txt +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/requires.txt +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/top_level.txt +0 -0
- {codeannex-0.4.0 → codeannex-0.4.2}/setup.cfg +0 -0
|
@@ -13,12 +13,25 @@ from reportlab.lib.units import cm
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def check_emoji_font_style():
|
|
16
|
-
|
|
16
|
+
from .renderer.fonts import get_system_font_paths
|
|
17
|
+
emoji_font, emoji_path = register_emoji_font()
|
|
17
18
|
if emoji_font:
|
|
18
19
|
print(f"✅ Emoji font registered: {emoji_font}")
|
|
19
|
-
|
|
20
|
+
if emoji_path:
|
|
21
|
+
p_lower = emoji_path.lower()
|
|
22
|
+
if "noto" in p_lower:
|
|
23
|
+
print("💡 Tip: Google Noto fonts are being used.")
|
|
24
|
+
elif "symbola" in p_lower:
|
|
25
|
+
print("💡 Tip: Symbola font is being used.")
|
|
20
26
|
else:
|
|
21
|
-
print("⚠️ No emoji font found - emojis may not render correctly")
|
|
27
|
+
print("⚠️ No emoji font found - emojis may not render correctly.")
|
|
28
|
+
print(f"💡 Recommendation: Install 'Symbola' or 'Google Noto Emoji' fonts.")
|
|
29
|
+
|
|
30
|
+
paths = get_system_font_paths()
|
|
31
|
+
print(f"\n🔍 Font search paths:")
|
|
32
|
+
for p in paths:
|
|
33
|
+
print(f" - {p}")
|
|
34
|
+
print(f"\n💡 You can add custom search paths using: --font-path /path/to/fonts")
|
|
22
35
|
return emoji_font
|
|
23
36
|
|
|
24
37
|
|
|
@@ -55,7 +68,7 @@ def main():
|
|
|
55
68
|
repo_url, branch_name = args.repo_url or git_url, args.branch or git_branch
|
|
56
69
|
|
|
57
70
|
mono_font, is_ttf, ttf_path = register_best_font()
|
|
58
|
-
emoji_font = register_emoji_font(error_on_missing=not args.emoji_description)
|
|
71
|
+
emoji_font, _ = register_emoji_font(error_on_missing=not args.emoji_description)
|
|
59
72
|
init_sprites(is_ttf, ttf_path)
|
|
60
73
|
|
|
61
74
|
def get_margin(spec, general, default):
|
|
@@ -55,12 +55,12 @@ class ModernAnnexPDF:
|
|
|
55
55
|
self.c.setTitle(f"Source code: {project_name}")
|
|
56
56
|
|
|
57
57
|
def _dtf(self, x, y, text, font, size, color=None):
|
|
58
|
-
draw_text_with_fallback(self.c, x, y, text, font, size, self.emoji_font, color,
|
|
59
|
-
|
|
58
|
+
return draw_text_with_fallback(self.c, x, y, text, font, size, self.emoji_font, color,
|
|
59
|
+
emoji_description=self.config.emoji_description)
|
|
60
60
|
|
|
61
61
|
def _dctf(self, x, y, text, font, size, color=None):
|
|
62
|
-
draw_centred_text_with_fallback(self.c, x, y, text, font, size, self.emoji_font, color,
|
|
63
|
-
|
|
62
|
+
return draw_centred_text_with_fallback(self.c, x, y, text, font, size, self.emoji_font, color,
|
|
63
|
+
emoji_description=self.config.emoji_description)
|
|
64
64
|
|
|
65
65
|
def _gsw(self, text, font, size) -> float:
|
|
66
66
|
return get_safe_string_width(text, font, size, self.emoji_font,
|
|
@@ -136,24 +136,24 @@ class ModernAnnexPDF:
|
|
|
136
136
|
label = self.config.repo_label
|
|
137
137
|
name = project_name
|
|
138
138
|
|
|
139
|
-
# Calculate
|
|
139
|
+
# Calculate total block width for initial centering
|
|
140
140
|
label_w = self._gsw(label, self.config.normal_font, 14)
|
|
141
141
|
name_w = self._gsw(name, self.config.normal_font, 14)
|
|
142
142
|
total_w = label_w + name_w
|
|
143
143
|
|
|
144
144
|
start_x = mid_x - total_w / 2
|
|
145
145
|
|
|
146
|
-
# 1. Draw the label
|
|
147
|
-
self._dtf(start_x, curr_y, label, self.config.normal_font, 14, text_color)
|
|
146
|
+
# 1. Draw the label and capture EXACT end position
|
|
147
|
+
name_x = self._dtf(start_x, curr_y, label, self.config.normal_font, 14, text_color)
|
|
148
148
|
|
|
149
|
-
# 2. Draw the name
|
|
150
|
-
name_x = start_x + label_w
|
|
149
|
+
# 2. Draw the name starting exactly where label ended
|
|
151
150
|
final_name_color = colors.HexColor(self.config.primary_color) if self.config.repo_url else text_color
|
|
152
|
-
self._dtf(name_x, curr_y, name, self.config.normal_font, 14, final_name_color)
|
|
151
|
+
end_x = self._dtf(name_x, curr_y, name, self.config.normal_font, 14, final_name_color)
|
|
153
152
|
|
|
154
|
-
# 3. Apply link
|
|
153
|
+
# 3. Apply link using the real coordinates
|
|
155
154
|
if self.config.repo_url:
|
|
156
|
-
|
|
155
|
+
# Coordinates are (x1, y1, x2, y2)
|
|
156
|
+
self.c.linkURL(self.config.repo_url, (name_x, curr_y - 2, end_x, curr_y + 12), relative=0, thickness=0, border=None)
|
|
157
157
|
|
|
158
158
|
curr_y -= 8*mm
|
|
159
159
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from ..core.config import PDFConfig
|
|
4
|
-
from ..io.git_utils import get_git_info
|
|
4
|
+
from ..io.git_utils import get_git_info, get_git_remotes
|
|
5
5
|
|
|
6
6
|
# ANSI Colors for a better CLI experience
|
|
7
7
|
BOLD = "\033[1m"
|
|
@@ -82,7 +82,8 @@ def run_interactive_wizard(args):
|
|
|
82
82
|
print(f"{YELLOW}Uppercase letters in prompts indicate the [default] action on Enter.{RESET}\n")
|
|
83
83
|
|
|
84
84
|
root = Path(args.dir).resolve()
|
|
85
|
-
git_url, git_branch = get_git_info(root)
|
|
85
|
+
git_url, git_branch, _ = get_git_info(root)
|
|
86
|
+
remotes = get_git_remotes(root)
|
|
86
87
|
total_steps = 6
|
|
87
88
|
|
|
88
89
|
# 1. Project Identity
|
|
@@ -90,8 +91,26 @@ def run_interactive_wizard(args):
|
|
|
90
91
|
args.name = _input_field("Project Name", args.name or root.name) or (args.name or root.name)
|
|
91
92
|
|
|
92
93
|
# 2. Repository Info
|
|
93
|
-
has_git = bool(
|
|
94
|
+
has_git = bool(remotes or git_branch)
|
|
94
95
|
if has_git:
|
|
96
|
+
if len(remotes) > 1:
|
|
97
|
+
_print_header(2, total_steps, "Repository Info", "Multiple Git remotes detected")
|
|
98
|
+
print(f" Available remotes:")
|
|
99
|
+
remote_names = list(remotes.keys())
|
|
100
|
+
for i, name in enumerate(remote_names, 1):
|
|
101
|
+
print(f" {i}. {name} ({remotes[name]})")
|
|
102
|
+
|
|
103
|
+
choice = _input_field("Select remote (number) or press Enter for origin", "1")
|
|
104
|
+
if choice.isdigit() and 1 <= int(choice) <= len(remote_names):
|
|
105
|
+
selected_remote = remote_names[int(choice)-1]
|
|
106
|
+
git_url = remotes[selected_remote]
|
|
107
|
+
elif "origin" in remotes:
|
|
108
|
+
git_url = remotes["origin"]
|
|
109
|
+
else:
|
|
110
|
+
git_url = remotes[remote_names[0]]
|
|
111
|
+
elif len(remotes) == 1:
|
|
112
|
+
git_url = list(remotes.values())[0]
|
|
113
|
+
|
|
95
114
|
_print_header(2, total_steps, "Repository Info", f"Detected: {git_branch or 'N/A'} @ {git_url or 'N/A'}")
|
|
96
115
|
if input(f" Use detected Git info? ({GREEN}Y{RESET}/n): ").strip().lower() != 'n':
|
|
97
116
|
args.branch = git_branch
|
|
@@ -31,13 +31,29 @@ def get_git_info(root: Path, use_git: bool = True) -> tuple[str | None, str | No
|
|
|
31
31
|
branch_name = res_branch.stdout.strip()
|
|
32
32
|
if branch_name == "HEAD": branch_name = None
|
|
33
33
|
|
|
34
|
-
# 3. Get commit SHA (
|
|
35
|
-
res_sha = subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=root, capture_output=True, text=True)
|
|
34
|
+
# 3. Get commit SHA (8 chars)
|
|
35
|
+
res_sha = subprocess.run(["git", "rev-parse", "--short=8", "HEAD"], cwd=root, capture_output=True, text=True)
|
|
36
36
|
if res_sha.returncode == 0: commit_sha = res_sha.stdout.strip()
|
|
37
37
|
|
|
38
38
|
except (subprocess.CalledProcessError, FileNotFoundError): pass
|
|
39
39
|
return repo_url, branch_name, commit_sha
|
|
40
40
|
|
|
41
|
+
def get_git_remotes(root: Path) -> dict[str, str]:
|
|
42
|
+
"""Returns a dictionary of remote names and URLs."""
|
|
43
|
+
try:
|
|
44
|
+
res = subprocess.run(["git", "remote", "-v"], cwd=root, capture_output=True, text=True)
|
|
45
|
+
remotes = {}
|
|
46
|
+
if res.returncode == 0:
|
|
47
|
+
for line in res.stdout.splitlines():
|
|
48
|
+
parts = line.split()
|
|
49
|
+
if len(parts) >= 2:
|
|
50
|
+
name, url = parts[0], parts[1]
|
|
51
|
+
if "(fetch)" in line or len(parts) == 2:
|
|
52
|
+
remotes[name] = url
|
|
53
|
+
return remotes
|
|
54
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
55
|
+
return {}
|
|
56
|
+
|
|
41
57
|
def get_git_files(root: Path) -> list[Path]:
|
|
42
58
|
"""Returns a list of files tracked or untracked by Git, respecting .gitignore."""
|
|
43
59
|
tracked = subprocess.run(["git", "ls-files", "-z"], cwd=root, capture_output=True, check=True).stdout.split(b"\0")
|
|
@@ -102,18 +102,24 @@ def register_best_font():
|
|
|
102
102
|
if name == "Courier": print("ℹ️ Monospace font fallback: Using standard 'Courier'.")
|
|
103
103
|
return name, name != "Courier", path
|
|
104
104
|
|
|
105
|
+
REGISTERED_EMOJI_FONT_PATH: str | None = None
|
|
106
|
+
|
|
105
107
|
def register_emoji_font(error_on_missing=False):
|
|
108
|
+
global REGISTERED_EMOJI_FONT_PATH
|
|
106
109
|
name, path = _register_font("CustomEmoji", EMOJI_SEARCH_PATHS, None)
|
|
107
110
|
if name is None:
|
|
108
111
|
dynamic_path = find_font_file("NotoEmoji") or find_font_file("Symbola")
|
|
109
|
-
if dynamic_path:
|
|
112
|
+
if dynamic_path:
|
|
113
|
+
name, path = _register_font("CustomEmoji", [dynamic_path], None)
|
|
114
|
+
|
|
115
|
+
REGISTERED_EMOJI_FONT_PATH = path
|
|
110
116
|
if name is None:
|
|
111
117
|
if error_on_missing:
|
|
112
118
|
import sys
|
|
113
119
|
print("❌ Error: No emoji font found.", file=sys.stderr)
|
|
114
120
|
sys.exit(1)
|
|
115
121
|
else: print("ℹ️ Emoji fallback: No emoji font found.")
|
|
116
|
-
return name
|
|
122
|
+
return name, path
|
|
117
123
|
|
|
118
124
|
def get_emoji_font_style(font_path: str | None) -> str | None:
|
|
119
125
|
if not font_path: return None
|
|
@@ -135,8 +141,13 @@ def is_google_like_emoji_font(font_path: str | None) -> bool:
|
|
|
135
141
|
|
|
136
142
|
def get_current_emoji_font_info() -> dict:
|
|
137
143
|
if "CustomEmoji" in pdfmetrics._fonts:
|
|
138
|
-
return {
|
|
139
|
-
|
|
144
|
+
return {
|
|
145
|
+
"name": "CustomEmoji",
|
|
146
|
+
"is_registered": True,
|
|
147
|
+
"path": REGISTERED_EMOJI_FONT_PATH,
|
|
148
|
+
"style": get_emoji_font_style(REGISTERED_EMOJI_FONT_PATH)
|
|
149
|
+
}
|
|
150
|
+
return {"name": None, "is_registered": False, "path": None, "style": None}
|
|
140
151
|
|
|
141
152
|
def is_char_supported(char: str, font_name: str) -> bool:
|
|
142
153
|
try:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codeannex"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Generates a professional PDF source code annex with Smart Index, Images and Emoji support."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|