Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- Live preview window to see the generated output before exporting
- Default filters for file extensions and folders (e.g., skip `.env`, `node_modules`, `.git`, etc.)
- Saves and loads user settings to/from a JSON file
- Choose export format (TXT, Markdown or JSON) and optionally include code blocks and headings
- Choose export format (TXT, Markdown, JSON, HTML or PDF) and optionally include code blocks and headings
- Optionally export only the file tree without contents
- Switch between dark and light mode, with buttons and fields adopting dark colors when the theme is set to "dark"
- Files listed in a `.gitignore` file are automatically deselected
Expand Down Expand Up @@ -59,7 +59,7 @@ The file `promptpack.py` simply launches the application.
2. **Select Files**: Opens an expandable tree of all folders and files. You can include/exclude each item via checkboxes.
- Default selections are based on the current settings.
3. **Settings**: Define default allowed extensions, excluded folders and files. Also choose:
- Export format (txt, md, json)
- Export format (txt, md, json, html, pdf)
- Include file headings
- Use code blocks for each file (Markdown only)
- Export only the file tree
Expand Down Expand Up @@ -138,7 +138,7 @@ python -m promptpack.cli ./input ./out --no-heading --no-code-block
| ----------------- | ------------------------------------------------------------------- |
| `source` | Source folder to analyze |
| `dest` | Destination folder for the generated output |
| `--format` | Export format: `txt`, `md`, `json` |
| `--format` | Export format: `txt`, `md`, `json`, `html`, `pdf` |
| `--tree-only` | Export only the folder structure, without file contents |
| `--no-heading` | Do not include headings for each file |
| `--no-code-block` | Do not wrap contents in code blocks (Markdown only) |
Expand Down
6 changes: 5 additions & 1 deletion promptpack/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ def main():
parser = argparse.ArgumentParser(description="PromptPack CLI")
parser.add_argument("source", help="cartella sorgente")
parser.add_argument("dest", help="cartella di destinazione")
parser.add_argument("--format", choices=["txt", "md", "json"], dest="format")
parser.add_argument(
"--format",
choices=["txt", "md", "json", "html", "pdf"],
dest="format",
)
parser.add_argument("--tree-only", action="store_true")
parser.add_argument("--no-heading", action="store_true")
parser.add_argument("--no-code-block", action="store_true")
Expand Down
16 changes: 14 additions & 2 deletions promptpack/gui/preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ def generate_preview_lines(app, start_folder, included_files, warn_on_limit=True
for path in sorted(included_files):
rel_path = path.relative_to(start_folder)
if app.tree_only.get():
line = f"{rel_path.as_posix()}\n"
if app.export_format.get() == "html":
line = f"<p>{rel_path.as_posix()}</p>\n"
else:
line = f"{rel_path.as_posix()}\n"
tokens = estimate_token_count(line)
if token_count + tokens > max_tokens:
break
Expand All @@ -185,10 +188,19 @@ def generate_preview_lines(app, start_folder, included_files, warn_on_limit=True
content = sanitize_sensitive_data(content)
chunk = []
if app.include_heading.get():
chunk.append(f"## {rel_path.as_posix()}\n")
if app.export_format.get() == "html":
chunk.append(f"<h2>{rel_path.as_posix()}</h2>\n")
else:
chunk.append(f"## {rel_path.as_posix()}\n")
if app.export_format.get() == "md" and app.use_code_block.get():
lang = LANG_MAP.get(path.suffix, '')
chunk.append(f"```{lang}\n{content}\n```\n")
elif app.export_format.get() == "html":
if app.use_code_block.get():
lang = LANG_MAP.get(path.suffix, '')
chunk.append(f"<pre><code class='language-{lang}'>" + content + "</code></pre>\n")
else:
chunk.append(f"<pre>{content}</pre>\n")
else:
chunk.append(f"{content}\n")
text = ''.join(chunk)
Expand Down
10 changes: 7 additions & 3 deletions promptpack/gui/settings_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,13 @@ def prompt_list(title, key):

ttk.Label(scrollable, text=app.t("output_opts"), style="Heading.TLabel").pack(padx=10, pady=(20, 5))
ttk.Label(scrollable, text=app.t("export_format")).pack(pady=(5, 0))
ttk.Radiobutton(scrollable, text="TXT", variable=app.export_format, value="txt").pack(pady=2)
ttk.Radiobutton(scrollable, text="Markdown", variable=app.export_format, value="md").pack(pady=2)
ttk.Radiobutton(scrollable, text="JSON", variable=app.export_format, value="json").pack(pady=2)
format_options = ["txt", "md", "json", "html", "pdf"]
format_menu = ttk.OptionMenu(scrollable, app.export_format, app.export_format.get(), *format_options)
format_menu.pack(pady=5)
menu_widget = format_menu["menu"]
bg = "#2d2d2d" if app.theme.get() == "dark" else "#ffffff"
fg = "#dcdcdc" if app.theme.get() == "dark" else "#000000"
menu_widget.configure(bg=bg, fg=fg, activebackground=bg, activeforeground=fg)
ttk.Checkbutton(scrollable, text=app.t("include_headings"), variable=app.include_heading).pack(pady=5)
ttk.Checkbutton(scrollable, text=app.t("use_code"), variable=app.use_code_block).pack(pady=5)
ttk.Checkbutton(scrollable, text=app.t("tree_only"), variable=app.tree_only).pack(pady=5)
Expand Down
36 changes: 34 additions & 2 deletions promptpack/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def generate_output(
lines = []
header = ""
token_count = 0
elif export_format == "html":
lines = [f"<h1>Project: {project_name} - {date_str}</h1>\n"]
header = ""
token_count = estimate_token_count(project_name)
else:
lines = []
header = f"Project: {project_name} - {date_str}\n\n"
Expand All @@ -104,6 +108,22 @@ def _flush():
if export_format == "json":
output_file = Path(dest_folder) / f"{project_name}-{date_str}-part{part}.json"
output_file.write_text(json.dumps(data, indent=2), encoding="utf-8")
elif export_format == "html":
output_file = Path(dest_folder) / f"{project_name}-{date_str}-part{part}.html"
html = "<html><body>" + "".join(lines) + "</body></html>"
output_file.write_text(html, encoding="utf-8")
elif export_format == "pdf":
try:
from fpdf import FPDF
except ImportError as exc:
raise RuntimeError("fpdf library required for PDF export") from exc
output_file = Path(dest_folder) / f"{project_name}-{date_str}-part{part}.pdf"
pdf = FPDF()
pdf.add_page()
pdf.set_font("Courier", size=12)
for line in lines:
pdf.multi_cell(0, 10, line)
pdf.output(str(output_file))
else:
suffix = "md" if export_format == "md" else "txt"
output_file = Path(dest_folder) / f"{project_name}-{date_str}-part{part}.{suffix}"
Expand All @@ -126,7 +146,10 @@ def _flush():
data["files"].append(line_data)
token_count += block_tokens
else:
line = f"{rel_path.as_posix()}\n"
if export_format == "html":
line = f"<p>{rel_path.as_posix()}</p>\n"
else:
line = f"{rel_path.as_posix()}\n"
block_tokens = estimate_token_count(line)
if token_count + block_tokens > max_tokens:
output_files.append(_flush())
Expand Down Expand Up @@ -156,10 +179,19 @@ def _flush():
else:
new_lines = []
if include_heading:
new_lines.append(f"## {rel_path.as_posix()}\n")
if export_format == "html":
new_lines.append(f"<h2>{rel_path.as_posix()}</h2>\n")
else:
new_lines.append(f"## {rel_path.as_posix()}\n")
if export_format == "md" and use_code_block:
lang = LANG_MAP.get(path.suffix, '')
new_lines.append(f"```{lang}\n{content}\n```\n\n")
elif export_format == "html":
if use_code_block:
lang = LANG_MAP.get(path.suffix, '')
new_lines.append(f"<pre><code class='language-{lang}'>" + content + "</code></pre>\n\n")
else:
new_lines.append(f"<pre>{content}</pre>\n\n")
else:
new_lines.append(f"{content}\n\n")
block = ''.join(new_lines)
Expand Down