Skip to content

Add filename options - #5

Open
alesf wants to merge 2 commits into
b1rdmania:mainfrom
alesf:add-filename-options
Open

Add filename options#5
alesf wants to merge 2 commits into
b1rdmania:mainfrom
alesf:add-filename-options

Conversation

@alesf

@alesf alesf commented Aug 1, 2026

Copy link
Copy Markdown

Add option to select output filename format.

Closes #4

Summary by CodeRabbit

  • New Features

    • Choose output filename presets, including original name, title, author, or combined formats.
    • Create custom filenames using title, author, year, series, language, and other supported fields.
    • Filenames are sanitized and safely formatted automatically.
    • Publication years are now detected from book metadata.
  • Documentation

    • Added instructions for configuring filename formats and custom templates.
  • Tests

    • Added coverage for filename presets, templates, metadata extraction, and invalid formatting options.

alesf added 2 commits August 1, 2026 15:55
- filename presets and custom template options in the UI,
- updated backend to handle filename formatting based on user selection,
- added validation for custom filename templates,
- enhanced metadata extraction to include publication year,
- implemented unit tests for filename formatting functionality
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Filename configuration

Layer / File(s) Summary
Filename formatting engine
metadata_handler.py, test_metadata_handler.py
Adds preset and custom filename formatting, metadata fields, original filename support, sanitization, year extraction, and unit tests.
Processing endpoint integration
app.py, epub_processor.py, README.md
Validates filename settings and passes them with metadata and the original filename through ProcessingOptions to output generation.
Web filename controls
templates/index.html, static/app.js, static/style.css
Adds preset buttons, custom template input, client-side validation, request parameters, styling, and asset version updates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: casualducko

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant processFile
  participant process_sse
  participant format_filename
  participant EPUBOutput
  Browser->>processFile: Select filename format and template
  processFile->>process_sse: Send filename settings and original filename
  process_sse->>process_sse: Validate format and template
  process_sse->>format_filename: Pass settings and extracted metadata
  format_filename->>EPUBOutput: Return sanitized EPUB filename
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding configurable output filename options.
Linked Issues check ✅ Passed The PR adds an original-filename preset and passes the uploaded filename through processing, satisfying issue #4.
Out of Scope Changes check ✅ Passed The preset, custom-template, validation, and year-metadata changes directly support configurable output filenames.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@metadata_handler.py`:
- Around line 338-356: Update the custom filename validation around
parsed_template and fields so bare or whitespace-only placeholders are treated
as unsupported fields rather than filtered out. Ensure format_filename() raises
the existing ValueError for these templates before template.format runs, while
preserving validation of named fields against FILENAME_TEMPLATE_FIELDS.

In `@test_metadata_handler.py`:
- Around line 8-78: The FormatFilenameTests suite lacks regression coverage for
bare-brace custom templates. Add a test alongside
test_custom_template_rejects_formatting_options that calls
format_filename("Book", "Writer", "custom", "{}") and asserts it raises
ValueError, matching the validation behavior implemented in format_filename.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aa369e76-f806-4a5c-ba9d-fa1d1c9fcec8

📥 Commits

Reviewing files that changed from the base of the PR and between 7cf9a65 and 02fcd6c.

📒 Files selected for processing (8)
  • README.md
  • app.py
  • epub_processor.py
  • metadata_handler.py
  • static/app.js
  • static/style.css
  • templates/index.html
  • test_metadata_handler.py

Comment thread metadata_handler.py
Comment on lines +338 to +356
if not template.strip():
raise ValueError("Custom filename template cannot be empty")
parsed_template = list(string.Formatter().parse(template))
if any(format_spec or conversion for _, _, format_spec, conversion in parsed_template):
raise ValueError("Filename template fields do not support formatting options")
fields = {field_name for _, field_name, _, _ in parsed_template if field_name}
unsupported = fields - FILENAME_TEMPLATE_FIELDS
if unsupported:
names = ', '.join(sorted(unsupported))
raise ValueError(f"Unknown filename template field: {names}")
name = template.format(
title=title,
author=author,
year=year,
series=series,
series_index=series_index,
language=language,
original=original,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo files matching metadata_handler.py / app.py / epub_processor.py:"
fd -a 'metadata_handler\.py|app\.py|epub_processor\.py' . | sed 's#^\./##'

echo
echo "Python version and string.Formatter().parse behavior:"
python3 --version
python3 - <<'PY'
import string
for template in ["{}", "{ }", "{title}", "{0}", "{title[foo]}"]:
    parsed = list(string.Formatter().parse(template))
    print(template, parsed)
    fields = {field_name for _, field_name, _, _ in parsed if field_name}
    print("  filtered fields={}", fields)
PY

Repository: b1rdmania/epubkit

Length of output: 742


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "metadata_handler.py relevant section:"
sed -n '300,380p' metadata_handler.py | nl -ba -v300

echo
echo "app.py relevant sections:"
sed -n '150,190p' app.py | nl -ba -v150
rg -n "custom|filename|ValueError|IndexError|except" app.py

echo
echo "epub_processor.py relevant sections:"
sed -n '410,450p' epub_processor.py | nl -ba -v410
rg -n "custom|filename|ValueError|except Exception|Metadata" epub_processor.py

echo
echo "All occurrences of filename template logic:"
rg -n "FILENAME_TEMPLATE_FIELDS|filename template|custom filename|format\\(" metadata_handler.py app.py epub_processor.py test_metadata_handler.py

Repository: b1rdmania/epubkit

Length of output: 233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "metadata_handler.py relevant section:"
sed -n '300,380p' metadata_handler.py | awk '{printf "%5d  %s\n", NR+299, $0}'

echo
echo "app.py relevant sections and occurrences:"
sed -n '150,190p' app.py | awk '{printf "%5d  %s\n", NR+149, $0}'
rg -n "custom|filename|ValueError|IndexError|except" app.py

echo
echo "epub_processor.py relevant sections and occurrences:"
sed -n '410,450p' epub_processor.py | awk '{printf "%5d  %s\n", NR+409, $0}'
rg -n "custom|filename|ValueError|except Exception|Metadata" epub_processor.py

echo
echo "All occurrences of filename template logic:"
rg -n "FILENAME_TEMPLATE_FIELDS|filename template|custom filename|format\\(" metadata_handler.py app.py epub_processor.py test_metadata_handler.py

Repository: b1rdmania/epubkit

Length of output: 10658


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Python formatting behavior for bare placeholder templates during validation and formatting:"
python3 - <<'PY'
import string
from pathlib import Path

FILENAME_TEMPLATE_FIELDS = {'title', 'author', 'year', 'series', 'series_index', 'language', 'original'}

for template in ["{}", "{ }", "{title}"]:
    parsed = list(string.Formatter().parse(template))
    fields = {field_name for _, field_name, _, _ in parsed if field_name}
    unsupported = fields - FILENAME_TEMPLATE_FIELDS
    try:
        name = template.format(
            title="title",
            author="author",
            year="2026",
            series="series",
            series_index="1",
            language="en",
            original=Path("original.epub").name,
        )
        print(template, "parsed=", parsed, "fields=", fields, "unsupported=", unsupported, "name=", name)
    except Exception as e:
        print(template, "parsed=", parsed, "fields=", fields, "unsupported=", unsupported, "raised", type(e).__name__, str(e))

print()
print("Literal blank field as {}")
try:
    "{} {}".format(title="t", author="a")
except Exception as e:
    print(type(e).__name__, str(e))
PY

Repository: b1rdmania/epubkit

Length of output: 664


Reject bare field placeholders in custom filename templates.

string.Formatter().parse() treats {} and { } as fields with an empty or whitespace field name. The current if field_name, filter excludes empty names, so unsupported stays empty; template.format(title=...) then raises IndexError because format_filename() only supplies keyword arguments. Treat None field names as unknown fields and raise ValueError, so malformed templates are rejected before processing instead of an unhandled IndexError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@metadata_handler.py` around lines 338 - 356, Update the custom filename
validation around parsed_template and fields so bare or whitespace-only
placeholders are treated as unsupported fields rather than filtered out. Ensure
format_filename() raises the existing ValueError for these templates before
template.format runs, while preserving validation of named fields against
FILENAME_TEMPLATE_FIELDS.

Comment thread test_metadata_handler.py
Comment on lines +8 to +78
class FormatFilenameTests(unittest.TestCase):
def test_presets(self):
self.assertEqual(
format_filename("The Book", "A. Writer", "original", original_filename="upload.epub"),
"upload.epub",
)
self.assertEqual(
format_filename("The Book", "A. Writer", "title-author"),
"The Book - A. Writer.epub",
)
self.assertEqual(
format_filename("The Book", "A. Writer", "author-title"),
"A. Writer - The Book.epub",
)
self.assertEqual(
format_filename("The Book", "A. Writer", "title"),
"The Book.epub",
)

def test_custom_template_supports_metadata_and_original_name(self):
self.assertEqual(
format_filename(
"The Book",
"A/Writer",
"custom",
"{year} - {title} - {author} [{original}]",
"source.epub",
"2026",
),
"2026 - The Book - A-Writer [source].epub",
)

def test_custom_template_supports_series_and_language(self):
self.assertEqual(
format_filename(
"The Book",
"A. Writer",
"custom",
"{series} {series_index} - {title} ({language})",
series="Saga",
series_index="2",
language="en",
),
"Saga 2 - The Book (en).epub",
)

def test_missing_metadata_and_unsafe_names_fall_back_safely(self):
self.assertEqual(format_filename("", "", "title-author"), "optimized.epub")
self.assertEqual(
format_filename("", "", "original", original_filename="../../.epub"),
"optimized.epub",
)

def test_custom_template_rejects_unknown_fields(self):
with self.assertRaisesRegex(ValueError, "Unknown filename template field"):
format_filename("Book", "Writer", "custom", "{publisher}")

def test_custom_template_rejects_formatting_options(self):
with self.assertRaisesRegex(ValueError, "do not support formatting options"):
format_filename("Book", "Writer", "custom", "{title:>20}")

def test_extract_metadata_includes_publication_year(self):
opf = etree.fromstring(b"""
<package xmlns="http://www.idpf.org/2007/opf">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>Book</dc:title>
<dc:date>2026-07-31</dc:date>
</metadata>
</package>
""")
self.assertEqual(extract_metadata(etree.ElementTree(opf))["year"], "2026")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Good coverage of presets, custom templates, and fallbacks. Add a case for bare-brace templates.

The tests validate presets, series/language fields, unsafe-name fallback, and rejection of unknown fields and formatting options. None of them exercise a template with an auto-numbered placeholder such as "{}", which is the input that triggers the uncaught IndexError described in the metadata_handler.py review. Add a regression test asserting that format_filename("Book", "Writer", "custom", "{}") raises ValueError once the fix in metadata_handler.py lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test_metadata_handler.py` around lines 8 - 78, The FormatFilenameTests suite
lacks regression coverage for bare-brace custom templates. Add a test alongside
test_custom_template_rejects_formatting_options that calls
format_filename("Book", "Writer", "custom", "{}") and asserts it raises
ValueError, matching the validation behavior implemented in format_filename.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add option to keep original filename?

1 participant