codeannex 0.2.0__tar.gz → 0.3.0__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 (42) hide show
  1. codeannex-0.3.0/PKG-INFO +112 -0
  2. codeannex-0.3.0/README.md +81 -0
  3. codeannex-0.3.0/codeannex/__main__.py +136 -0
  4. codeannex-0.3.0/codeannex/core/__init__.py +0 -0
  5. {codeannex-0.2.0/codeannex → codeannex-0.3.0/codeannex/core}/config.py +20 -32
  6. codeannex-0.3.0/codeannex/core/pdf_builder.py +333 -0
  7. codeannex-0.3.0/codeannex/interface/__init__.py +0 -0
  8. codeannex-0.3.0/codeannex/interface/cli.py +142 -0
  9. codeannex-0.3.0/codeannex/io/__init__.py +0 -0
  10. codeannex-0.3.0/codeannex/io/file_utils.py +108 -0
  11. codeannex-0.3.0/codeannex/io/git_utils.py +45 -0
  12. codeannex-0.3.0/codeannex/renderer/__init__.py +0 -0
  13. codeannex-0.3.0/codeannex/renderer/fonts.py +195 -0
  14. codeannex-0.3.0/codeannex/renderer/highlight.py +22 -0
  15. codeannex-0.3.0/codeannex/renderer/text_utils.py +59 -0
  16. codeannex-0.3.0/codeannex.egg-info/PKG-INFO +112 -0
  17. codeannex-0.3.0/codeannex.egg-info/SOURCES.txt +23 -0
  18. {codeannex-0.2.0 → codeannex-0.3.0}/pyproject.toml +1 -1
  19. codeannex-0.2.0/PKG-INFO +0 -116
  20. codeannex-0.2.0/README.md +0 -85
  21. codeannex-0.2.0/codeannex/__main__.py +0 -249
  22. codeannex-0.2.0/codeannex/file_utils.py +0 -116
  23. codeannex-0.2.0/codeannex/fonts.py +0 -275
  24. codeannex-0.2.0/codeannex/highlight.py +0 -21
  25. codeannex-0.2.0/codeannex/pdf_builder.py +0 -492
  26. codeannex-0.2.0/codeannex/text_utils.py +0 -131
  27. codeannex-0.2.0/codeannex.egg-info/PKG-INFO +0 -116
  28. codeannex-0.2.0/codeannex.egg-info/SOURCES.txt +0 -24
  29. codeannex-0.2.0/tests/test_emoji.py +0 -119
  30. codeannex-0.2.0/tests/test_file_management.py +0 -48
  31. codeannex-0.2.0/tests/test_fonts_dynamic.py +0 -47
  32. codeannex-0.2.0/tests/test_interactive.py +0 -65
  33. codeannex-0.2.0/tests/test_pdf_layout.py +0 -94
  34. codeannex-0.2.0/tests/test_summary_hierarchy.py +0 -56
  35. codeannex-0.2.0/tests/test_text_utils.py +0 -25
  36. {codeannex-0.2.0 → codeannex-0.3.0}/LICENSE +0 -0
  37. {codeannex-0.2.0 → codeannex-0.3.0}/codeannex/__init__.py +0 -0
  38. {codeannex-0.2.0 → codeannex-0.3.0}/codeannex.egg-info/dependency_links.txt +0 -0
  39. {codeannex-0.2.0 → codeannex-0.3.0}/codeannex.egg-info/entry_points.txt +0 -0
  40. {codeannex-0.2.0 → codeannex-0.3.0}/codeannex.egg-info/requires.txt +0 -0
  41. {codeannex-0.2.0 → codeannex-0.3.0}/codeannex.egg-info/top_level.txt +0 -0
  42. {codeannex-0.2.0 → codeannex-0.3.0}/setup.cfg +0 -0
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeannex
3
+ Version: 0.3.0
4
+ Summary: Generates a professional PDF source code annex with Smart Index, Images and Emoji support.
5
+ License: MIT
6
+ Project-URL: Repository, https://github.com/tanhleno/codeannex
7
+ Keywords: pdf,source-code,documentation,annex,reportlab,emoji
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Documentation
17
+ Classifier: Topic :: Software Development :: Documentation
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: reportlab>=4.0
22
+ Requires-Dist: Pillow>=10.0
23
+ Requires-Dist: Pygments>=2.16
24
+ Requires-Dist: pdfminer.six>=20231228
25
+ Provides-Extra: svg
26
+ Requires-Dist: cairosvg>=2.7; extra == "svg"
27
+ Requires-Dist: svglib>=1.5; extra == "svg"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # 📂 codeannex
33
+
34
+ Generates a professional PDF annex from a project's source code — with syntax highlighting, a hierarchical table of contents, image rendering, and intelligent font discovery.
35
+
36
+ ## 🚀 Key Features
37
+
38
+ - **Interactive Wizard 2.0** — Organized by sections (Project, Style, Typography, Layout, Filters) with smart defaults and explicit (Y/n) prompts.
39
+ - **Git Integration & Version Tracking** — Automatically detects Repository URL, Branch, and **Commit SHA**. Supports subdirectories by intelligently ignoring Git metadata if not at the root.
40
+ - **Subdirectories First** — Improved document organization by listing subdirectories and their contents before root files.
41
+ - **Flexible File Filtering** — Multi-pattern include and exclude glob filters (e.g., `--include "src/*" --exclude "tests/*"`).
42
+ - **Intelligent Font Discovery** — Automatically finds fonts from your system (Windows, Linux, macOS) or custom paths via `--font-path`.
43
+ - **Fully Customizable UI** — Control everything: paper size (mm), margins (cm), colors (HEX), and font sizes.
44
+ - **Hierarchical Summary** — Real tree-structured Table of Contents with increasing page numbers and terminal-like connection lines.
45
+ - **Professional Design** — High-contrast line numbers, clean cover page, and smart contrast (auto-switching text between black/white based on accent brightness).
46
+
47
+ ## 🛠 Installation
48
+
49
+ The recommended way to install **codeannex** is via [pipx](https://github.com/pypa/pipx), which installs the tool in an isolated environment:
50
+
51
+ ```bash
52
+ pipx install codeannex
53
+ ```
54
+
55
+ For full SVG support (required for crisp line numbers and SVG image rendering):
56
+
57
+ ```bash
58
+ pipx install "codeannex[svg]"
59
+ ```
60
+
61
+ *Alternatively, you can use standard pip:* `pip install codeannex`
62
+
63
+ ## 📖 Usage
64
+
65
+ ### Interactive Mode (Wizard)
66
+ Simply run without arguments to start the step-by-step configuration:
67
+ ```bash
68
+ python3 -m codeannex
69
+ ```
70
+
71
+ ### Automation / CI
72
+ ```bash
73
+ python3 -m codeannex . \
74
+ --cover-title "Technical Annex" \
75
+ --primary-color "#0f4761" \
76
+ --code-size 9 \
77
+ --include "src/*" \
78
+ --exclude "*.log" \
79
+ --no-input
80
+ ```
81
+
82
+ ## ⚙️ Configuration Options
83
+
84
+ ### Git & Metadata
85
+ - `--repo-url URL` — Manual repository URL.
86
+ - `--branch NAME` — Manual branch name.
87
+ - `--no-git` — Force disable Git integration.
88
+ - `--repo-label LABEL` — Label for repo (default: "Repository Name: ").
89
+
90
+ ### File Selection
91
+ - `--include PATTERN` — Include glob pattern (can be used multiple times).
92
+ - `--exclude PATTERN` — Exclude glob pattern (can be used multiple times).
93
+
94
+ ### Design & Layout
95
+ - `--page-width MM` / `--page-height MM` — Custom paper size in mm (default: A4).
96
+ - `--margin CM` — General margin (top, bottom, left, right).
97
+ - `--primary-color HEX` — Accent color for headers and summary icons.
98
+ - `--code-size N` — Font size for code and line numbers.
99
+
100
+ ### Fonts
101
+ - `--font-path PATH` — Additional directory to search for `.ttf`/`.otf` files.
102
+ - `--title-font` / `--normal-font` / `--mono-font` — System font names.
103
+
104
+ ## 🧪 Testing
105
+
106
+ ```bash
107
+ PYTHONPATH=. pytest
108
+ ```
109
+
110
+ ## 📄 License
111
+
112
+ MIT
@@ -0,0 +1,81 @@
1
+ # 📂 codeannex
2
+
3
+ Generates a professional PDF annex from a project's source code — with syntax highlighting, a hierarchical table of contents, image rendering, and intelligent font discovery.
4
+
5
+ ## 🚀 Key Features
6
+
7
+ - **Interactive Wizard 2.0** — Organized by sections (Project, Style, Typography, Layout, Filters) with smart defaults and explicit (Y/n) prompts.
8
+ - **Git Integration & Version Tracking** — Automatically detects Repository URL, Branch, and **Commit SHA**. Supports subdirectories by intelligently ignoring Git metadata if not at the root.
9
+ - **Subdirectories First** — Improved document organization by listing subdirectories and their contents before root files.
10
+ - **Flexible File Filtering** — Multi-pattern include and exclude glob filters (e.g., `--include "src/*" --exclude "tests/*"`).
11
+ - **Intelligent Font Discovery** — Automatically finds fonts from your system (Windows, Linux, macOS) or custom paths via `--font-path`.
12
+ - **Fully Customizable UI** — Control everything: paper size (mm), margins (cm), colors (HEX), and font sizes.
13
+ - **Hierarchical Summary** — Real tree-structured Table of Contents with increasing page numbers and terminal-like connection lines.
14
+ - **Professional Design** — High-contrast line numbers, clean cover page, and smart contrast (auto-switching text between black/white based on accent brightness).
15
+
16
+ ## 🛠 Installation
17
+
18
+ The recommended way to install **codeannex** is via [pipx](https://github.com/pypa/pipx), which installs the tool in an isolated environment:
19
+
20
+ ```bash
21
+ pipx install codeannex
22
+ ```
23
+
24
+ For full SVG support (required for crisp line numbers and SVG image rendering):
25
+
26
+ ```bash
27
+ pipx install "codeannex[svg]"
28
+ ```
29
+
30
+ *Alternatively, you can use standard pip:* `pip install codeannex`
31
+
32
+ ## 📖 Usage
33
+
34
+ ### Interactive Mode (Wizard)
35
+ Simply run without arguments to start the step-by-step configuration:
36
+ ```bash
37
+ python3 -m codeannex
38
+ ```
39
+
40
+ ### Automation / CI
41
+ ```bash
42
+ python3 -m codeannex . \
43
+ --cover-title "Technical Annex" \
44
+ --primary-color "#0f4761" \
45
+ --code-size 9 \
46
+ --include "src/*" \
47
+ --exclude "*.log" \
48
+ --no-input
49
+ ```
50
+
51
+ ## ⚙️ Configuration Options
52
+
53
+ ### Git & Metadata
54
+ - `--repo-url URL` — Manual repository URL.
55
+ - `--branch NAME` — Manual branch name.
56
+ - `--no-git` — Force disable Git integration.
57
+ - `--repo-label LABEL` — Label for repo (default: "Repository Name: ").
58
+
59
+ ### File Selection
60
+ - `--include PATTERN` — Include glob pattern (can be used multiple times).
61
+ - `--exclude PATTERN` — Exclude glob pattern (can be used multiple times).
62
+
63
+ ### Design & Layout
64
+ - `--page-width MM` / `--page-height MM` — Custom paper size in mm (default: A4).
65
+ - `--margin CM` — General margin (top, bottom, left, right).
66
+ - `--primary-color HEX` — Accent color for headers and summary icons.
67
+ - `--code-size N` — Font size for code and line numbers.
68
+
69
+ ### Fonts
70
+ - `--font-path PATH` — Additional directory to search for `.ttf`/`.otf` files.
71
+ - `--title-font` / `--normal-font` / `--mono-font` — System font names.
72
+
73
+ ## 🧪 Testing
74
+
75
+ ```bash
76
+ PYTHONPATH=. pytest
77
+ ```
78
+
79
+ ## 📄 License
80
+
81
+ MIT
@@ -0,0 +1,136 @@
1
+ import io
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ from .core.config import PDFConfig
7
+ from .core.pdf_builder import ModernAnnexPDF
8
+ from .io.file_utils import get_project_files, classify_file, sort_files
9
+ from .io.git_utils import get_git_info
10
+ from .renderer.fonts import init_sprites, register_best_font, register_emoji_font, auto_register_font, ADDITIONAL_SEARCH_PATHS
11
+ from .interface.cli import parse_args, run_interactive_wizard
12
+ from reportlab.lib.units import cm
13
+
14
+
15
+ def check_emoji_font_style():
16
+ emoji_font = register_emoji_font()
17
+ if emoji_font:
18
+ print(f"✅ Emoji font registered: {emoji_font}")
19
+ print("💡 Tip: Install Google Noto fonts for authentic Google emoji style")
20
+ else:
21
+ print("⚠️ No emoji font found - emojis may not render correctly")
22
+ return emoji_font
23
+
24
+
25
+ def main():
26
+ args, unknown = parse_args()
27
+ is_interactive = len(sys.argv) <= 2 and not args.no_input
28
+ if is_interactive: run_interactive_wizard(args)
29
+
30
+ # Register additional search paths first
31
+ if args.font_path:
32
+ ADDITIONAL_SEARCH_PATHS.extend(args.font_path)
33
+
34
+ if args.normal_font: args.normal_font = auto_register_font(args.normal_font, required=True)
35
+ if args.bold_font: args.bold_font = auto_register_font(args.bold_font, required=True)
36
+ if args.title_font: args.title_font = auto_register_font(args.title_font, required=True)
37
+ if args.subtitle_font: args.subtitle_font = auto_register_font(args.subtitle_font, required=True)
38
+ if args.mono_font: args.mono_font = auto_register_font(args.mono_font, required=True)
39
+ if args.emoji_font: args.emoji_font = auto_register_font(args.emoji_font, required=True)
40
+
41
+ if unknown:
42
+ for u in unknown:
43
+ if u.startswith("-"): print(f"⚠️ Warning: Unrecognized argument ignored: {u}")
44
+
45
+ if args.check_emoji_font:
46
+ check_emoji_font_style()
47
+ return
48
+
49
+ root, output = Path(args.dir).resolve(), args.output or f"{Path(args.dir).resolve().name}_anexo_codigo.pdf"
50
+ use_git = not args.no_git
51
+ git_url, git_branch, git_sha = get_git_info(root, use_git=use_git)
52
+ repo_url, branch_name = args.repo_url or git_url, args.branch or git_branch
53
+
54
+ mono_font, is_ttf, ttf_path = register_best_font()
55
+ emoji_font = register_emoji_font(error_on_missing=not args.emoji_description)
56
+ init_sprites(is_ttf, ttf_path)
57
+
58
+ def get_margin(spec, general, default):
59
+ if spec is not None: return spec * cm
60
+ if general is not None: return general * cm
61
+ return default
62
+
63
+ from reportlab.lib.units import mm
64
+ config = PDFConfig(
65
+ project_name=args.name or root.name,
66
+ margin_left=get_margin(args.margin_left, args.margin, PDFConfig().margin_left),
67
+ margin_right=get_margin(args.margin_right, args.margin, PDFConfig().margin_right),
68
+ margin_top=get_margin(args.margin_top, args.margin, PDFConfig().margin_top),
69
+ margin_bottom=get_margin(args.margin_bottom, args.margin, PDFConfig().margin_bottom),
70
+ page_width=args.page_width * mm if args.page_width else PDFConfig().page_width,
71
+ page_height=args.page_height * mm if args.page_height else PDFConfig().page_height,
72
+ start_page_num=args.start_page,
73
+ show_project_name=args.show_project,
74
+ normal_font=args.normal_font or PDFConfig().normal_font,
75
+ bold_font=args.bold_font or PDFConfig().bold_font,
76
+ mono_font=args.mono_font or mono_font,
77
+ emoji_font=args.emoji_font or emoji_font,
78
+ emoji_description=args.emoji_description,
79
+ repo_url=repo_url,
80
+ branch_name=branch_name,
81
+ commit_sha=git_sha,
82
+ page_number_size=args.page_number_size,
83
+ title_font=args.title_font,
84
+ title_size=args.title_size,
85
+ title_color=args.title_color,
86
+ subtitle_font=args.subtitle_font,
87
+ subtitle_size=args.subtitle_size,
88
+ subtitle_color=args.subtitle_color,
89
+ normal_text_size=args.normal_size,
90
+ normal_text_color=args.normal_color,
91
+ page_number_format=args.page_number_format,
92
+ show_page_numbers=not args.no_page_numbers,
93
+ cover_title=args.cover_title,
94
+ primary_color=args.primary_color,
95
+ cover_subtitle=args.cover_subtitle,
96
+ summary_title=args.summary_title,
97
+ repo_label=args.repo_label,
98
+ project_label=args.project_label,
99
+ file_part_format=args.file_part_format,
100
+ code_font_size=args.code_size,
101
+ code_bg_color=args.code_bg,
102
+ page_bg_color=args.page_bg_color,
103
+ )
104
+
105
+ all_files = sort_files(get_project_files(root, includes=args.include, excludes=args.exclude, use_git=use_git), root)
106
+ included = []
107
+ from .core.config import IMAGE_EXTENSIONS, BINARY_EXTENSIONS
108
+ script_path, output_path = Path(__file__).resolve(), Path(output).resolve()
109
+
110
+ for fp in all_files:
111
+ try:
112
+ if fp.resolve() in (script_path, output_path): continue
113
+ except: pass
114
+ if not fp.is_file(): continue
115
+ ext = fp.suffix.lower()
116
+ if ext == ".svg": included += [(fp, "image"), (fp, "text")]
117
+ elif ext in IMAGE_EXTENSIONS: included.append((fp, "image"))
118
+ elif ext in BINARY_EXTENSIONS: continue
119
+ elif classify_file(fp) == "text": included.append((fp, "text"))
120
+
121
+ if not included:
122
+ print("❌ No compatible files found."); return
123
+
124
+ print(f"🧮 Step 1/2: Simulating layout of {len(included)} files...")
125
+ pdf_sim = ModernAnnexPDF(io.BytesIO(), root, mono_font, emoji_font, config)
126
+ pdf_sim.is_simulation = True
127
+ pdf_sim.build(included)
128
+
129
+ print("🚀 Step 2/2: Generating the final document...")
130
+ pdf_final = ModernAnnexPDF(output, root, mono_font, emoji_font, config)
131
+ pdf_final.summary_data = pdf_sim.summary_data
132
+ pdf_final.build(included)
133
+ print(f"✅ Success! Saved to: {output}")
134
+
135
+ if __name__ == "__main__":
136
+ main()
File without changes
@@ -29,64 +29,52 @@ COLOR_ACCENT = colors.HexColor("#89b4fa")
29
29
  NORMAL_FONT = "Helvetica"
30
30
  BOLD_FONT = "Helvetica-Bold"
31
31
 
32
- # ── Supported image extensions ───────────────
33
32
  IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".ico", ".gif", ".webp", ".bmp", ".svg"}
34
-
35
- # ── Binary extensions to ignore quickly ──────
36
- BINARY_EXTENSIONS = {".pdf", ".pyc", ".pyo", ".exe", ".dll", ".so", ".dylib",
37
- ".zip", ".tar", ".gz", ".rar", ".7z", ".jar", ".whl",
38
- ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx"}
39
-
33
+ BINARY_EXTENSIONS = {".pdf", ".pyc", ".pyo", ".exe", ".dll", ".so", ".dylib", ".zip", ".tar", ".gz", ".rar", ".7z", ".jar", ".whl"}
40
34
 
41
35
  @dataclass
42
36
  class PDFConfig:
43
- """PDF configuration for code annex."""
44
37
  project_name: str | None = None
45
38
  margin_left: float = _DEFAULT_MARGIN_LEFT
46
39
  margin_right: float = _DEFAULT_MARGIN_RIGHT
47
40
  margin_top: float = _DEFAULT_MARGIN_TOP
48
41
  margin_bottom: float = _DEFAULT_MARGIN_BOTTOM
42
+ page_width: float = PAGE_W
43
+ page_height: float = PAGE_H
49
44
  start_page_num: int = 1
50
45
  show_project_name: bool = False
51
46
  normal_font: str = NORMAL_FONT
52
47
  bold_font: str = BOLD_FONT
53
- mono_font: str | None = None # Will be set by register_best_font
54
- emoji_font: str | None = None # Will be set by register_emoji_font
55
- emoji_description: bool = False # Print descriptions instead of emojis
56
- repo_url: str | None = None # URL of the repository for the cover page
57
- page_number_size: int = 8 # Font size for page numbers
48
+ mono_font: str | None = None
49
+ emoji_font: str | None = None
50
+ emoji_description: bool = False
51
+ repo_url: str | None = None
52
+ branch_name: str | None = None
53
+ commit_sha: str | None = None
54
+ page_number_size: int = 8
58
55
 
59
- # Customization fields
60
56
  title_font: str | None = None
61
57
  title_size: int = 28
62
58
  title_color: str = "#1e1e2e"
63
59
  subtitle_font: str | None = None
64
60
  subtitle_size: int = 18
65
- subtitle_color: str | None = None # Defaults to title_color
61
+ subtitle_color: str | None = None
66
62
  normal_text_size: int = 10
67
- normal_text_color: str = "#4c4f69" # Default COLOR_TEXT_MAIN
68
- page_number_format: str = "{n}" # e.g. "Anexo I - {n}" or just "{n}"
63
+ normal_text_color: str = "#4c4f69"
64
+ page_number_format: str = "{n}"
69
65
  show_page_numbers: bool = True
70
66
  cover_title: str = "TECHNICAL ANNEX"
71
67
  cover_subtitle: str = "Source Code Documentation"
72
- primary_color: str = "#1e66f5" # Professional dark blue
68
+ primary_color: str = "#1e66f5"
73
69
 
74
- # Code style
75
70
  code_font_size: int = CODE_FONT_SIZE
76
- code_bg_color: str = "#1e1e2e" # Default COLOR_CODE_BG
77
- page_bg_color: str = "#ffffff" # Default COLOR_PAGE_BG
71
+ code_bg_color: str = "#1e1e2e"
72
+ page_bg_color: str = "#ffffff"
78
73
 
79
- # Labels (Internationalization)
80
74
  summary_title: str = "Summary / File Index"
81
- repo_label: str = "Repository: "
75
+ repo_label: str = "Repository Name: "
82
76
  project_label: str = "Project: "
83
- file_part_format: str = "({current}/{total})" # e.g. "(1/2)"
84
- cover_subtitle: str = "Source Code Documentation"
85
-
86
- def get_code_x(self) -> float:
87
- """Calculates the initial X position of the code."""
88
- return self.margin_left + GUTTER_W
77
+ file_part_format: str = "(part {current}/{total})"
89
78
 
90
- def get_code_w(self) -> float:
91
- """Calculates the available width for code."""
92
- return PAGE_W - self.get_code_x() - self.margin_right
79
+ def get_code_x(self) -> float: return self.margin_left + GUTTER_W
80
+ def get_code_w(self) -> float: return self.page_width - self.get_code_x() - self.margin_right