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.
Files changed (25) hide show
  1. {codeannex-0.4.0 → codeannex-0.4.2}/PKG-INFO +1 -1
  2. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/__main__.py +17 -4
  3. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/pdf_builder.py +12 -12
  4. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/interface/cli.py +22 -3
  5. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/git_utils.py +18 -2
  6. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/fonts.py +15 -4
  7. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/PKG-INFO +1 -1
  8. {codeannex-0.4.0 → codeannex-0.4.2}/pyproject.toml +1 -1
  9. {codeannex-0.4.0 → codeannex-0.4.2}/LICENSE +0 -0
  10. {codeannex-0.4.0 → codeannex-0.4.2}/README.md +0 -0
  11. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/__init__.py +0 -0
  12. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/__init__.py +0 -0
  13. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/core/config.py +0 -0
  14. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/interface/__init__.py +0 -0
  15. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/__init__.py +0 -0
  16. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/io/file_utils.py +0 -0
  17. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/__init__.py +0 -0
  18. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/highlight.py +0 -0
  19. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex/renderer/text_utils.py +0 -0
  20. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/SOURCES.txt +0 -0
  21. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/dependency_links.txt +0 -0
  22. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/entry_points.txt +0 -0
  23. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/requires.txt +0 -0
  24. {codeannex-0.4.0 → codeannex-0.4.2}/codeannex.egg-info/top_level.txt +0 -0
  25. {codeannex-0.4.0 → codeannex-0.4.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeannex
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Generates a professional PDF source code annex with Smart Index, Images and Emoji support.
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/tanhleno/codeannex
@@ -13,12 +13,25 @@ from reportlab.lib.units import cm
13
13
 
14
14
 
15
15
  def check_emoji_font_style():
16
- emoji_font = register_emoji_font()
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
- print("💡 Tip: Install Google Noto fonts for authentic Google emoji style")
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
- emoji_description=self.config.emoji_description)
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
- emoji_description=self.config.emoji_description)
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 widths for precise alignment
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 (Plain text color)
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 (Primary color if linked, else normal)
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 only to the name area
153
+ # 3. Apply link using the real coordinates
155
154
  if self.config.repo_url:
156
- self.c.linkURL(self.config.repo_url, (name_x, curr_y - 2, name_x + name_w, curr_y + 12), relative=0, thickness=0, border=None)
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(git_url or git_branch)
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 (short)
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: name, _ = _register_font("CustomEmoji", [dynamic_path], None)
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 {"name": "CustomEmoji", "is_registered": True, "is_google_like": False, "style": "Unknown"}
139
- return {"name": None, "is_registered": False, "is_google_like": False, "style": None}
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeannex
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: Generates a professional PDF source code annex with Smart Index, Images and Emoji support.
5
5
  License: MIT
6
6
  Project-URL: Repository, https://github.com/tanhleno/codeannex
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codeannex"
7
- version = "0.4.0"
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