Skip to content

Commit 4bbdbf2

Browse files
Copilotoscarlevin
andauthored
Fix pelican landing page links for print/single-file targets (#1128)
* Initial plan * Fix: print targets get better links in pelican generated site When output_filename is not set for single-file output formats (PDF, EPUB, KINDLE, BRAILLE), deploy_path() now scans the output directory for the actual output file and returns a path including the filename. This allows the pelican-generated landing page to link directly to the PDF (or other single-file output) rather than the directory containing it. Co-authored-by: oscarlevin <6504596+oscarlevin@users.noreply.github.com> Agent-Logs-Url: https://github.com/PreTeXtBook/pretext-cli/sessions/870d27e1-a45a-439c-943b-f8852da66683 * format * add copilot instructions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: oscarlevin <6504596+oscarlevin@users.noreply.github.com> Co-authored-by: Oscar Levin <oscar.levin@unco.edu>
1 parent eb5bb3a commit 4bbdbf2

File tree

3 files changed

+97
-3
lines changed

3 files changed

+97
-3
lines changed

.github/copilot-instructions.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copilot Instructions for pretext-cli
2+
3+
Prefer running project commands with `poetry run` so they use the repository's
4+
configured Python environment and tool versions.
5+
6+
When working on an assigned GitHub issue and preparing to open a pull request,
7+
run code formatting before creating the PR:
8+
9+
```bash
10+
poetry run black .
11+
```
12+
13+
Only open the PR after formatting has been run successfully.
14+
15+
When changes affect behavior in `pretext/`, add or update tests in `tests/`
16+
that cover the change.
17+
18+
Prefer test-driven development whenever practical: write or update tests first,
19+
then implement the code change to satisfy them.
20+
21+
Before opening the PR, run relevant tests (or the full suite when needed):
22+
23+
```bash
24+
poetry run pytest
25+
```
26+
27+
For user-visible changes, add an entry under `[Unreleased]` in `CHANGELOG.md`
28+
using Keep a Changelog categories (Added, Changed, Fixed, Removed).

pretext/project/__init__.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,16 @@ class Format(str, Enum):
6161
CUSTOM = "custom"
6262

6363

64+
# Maps single-file output formats to their file extension, used when looking up
65+
# the actual output file to generate a better deploy link.
66+
_SINGLE_FILE_FORMAT_EXTENSIONS: t.Dict["Format", str] = {
67+
Format.PDF: ".pdf",
68+
Format.EPUB: ".epub",
69+
Format.KINDLE: ".epub",
70+
Format.BRAILLE: ".brl",
71+
}
72+
73+
6474
# The CLI only needs two values from the publication file. Therefore, this class ignores the vast majority of a publication file's contents, loading and validating only a (small) relevant subset.
6575
# Since we will want to hash the baseurl for generating qr codes, we also load it here.
6676
class PublicationSubset(
@@ -439,9 +449,19 @@ def deploy_dir_relpath(self) -> Path:
439449
return self._project.stage / self.deploy_dir_path()
440450

441451
def deploy_path(self) -> Path:
442-
if self.output_filename is None:
443-
return self.deploy_dir_path()
444-
return self.deploy_dir_path() / self.output_filename
452+
if self.output_filename is not None:
453+
return self.deploy_dir_path() / self.output_filename
454+
# For single-file output formats, look for the actual output file in
455+
# the output directory to create a better link in the pelican-generated site.
456+
ext = _SINGLE_FILE_FORMAT_EXTENSIONS.get(self.format)
457+
if ext is not None:
458+
output_dir = self.output_dir_abspath()
459+
if output_dir.exists():
460+
gen = output_dir.glob(f"*{ext}")
461+
first = next(gen, None)
462+
if first is not None and next(gen, None) is None:
463+
return self.deploy_dir_path() / first.name
464+
return self.deploy_dir_path()
445465

446466
def xsl_abspath(self) -> t.Optional[Path]:
447467
if self.xsl is None:

tests/test_project.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,52 @@ def test_deploy(tmp_path: Path) -> None:
416416
)
417417

418418

419+
def test_deploy_path(tmp_path: Path) -> None:
420+
# Test that deploy_path() returns the correct path for single-file formats
421+
# when output_filename is not specified.
422+
prj_path = tmp_path / "test_deploy_path"
423+
shutil.copytree(EXAMPLES_DIR / "projects" / "project_refactor" / "simple", prj_path)
424+
(prj_path / "project.ptx").unlink()
425+
with utils.working_directory(prj_path):
426+
project = pr.Project(ptx_version="2")
427+
428+
# For PDF target with no output_filename and no output dir: fallback to directory
429+
t_pdf = project.new_target(name="print", format="pdf", deploy_dir="print-dir")
430+
assert t_pdf.deploy_path() == Path("print-dir")
431+
432+
# Simulate a built PDF in the output directory
433+
pdf_output_dir = t_pdf.output_dir_abspath()
434+
pdf_output_dir.mkdir(parents=True, exist_ok=True)
435+
(pdf_output_dir / "main.pdf").touch()
436+
437+
# deploy_path() should now find the PDF and include it in the path
438+
assert t_pdf.deploy_path() == Path("print-dir") / "main.pdf"
439+
440+
# If output_filename is explicitly set, it should take precedence
441+
t_pdf_explicit = project.new_target(
442+
name="print-explicit",
443+
format="pdf",
444+
deploy_dir="print-dir2",
445+
output_filename="custom.pdf",
446+
)
447+
assert t_pdf_explicit.deploy_path() == Path("print-dir2") / "custom.pdf"
448+
449+
# For HTML format (directory output), deploy_path() should still return the directory
450+
t_html = project.new_target(name="web", format="html", deploy_dir="web-dir")
451+
assert t_html.deploy_path() == Path("web-dir")
452+
453+
# EPUB target with no output file: fallback to directory
454+
t_epub = project.new_target(name="epub", format="epub", deploy_dir="epub-dir")
455+
assert t_epub.deploy_path() == Path("epub-dir")
456+
457+
# Simulate a built EPUB
458+
epub_output_dir = t_epub.output_dir_abspath()
459+
epub_output_dir.mkdir(parents=True, exist_ok=True)
460+
(epub_output_dir / "book.epub").touch()
461+
462+
assert t_epub.deploy_path() == Path("epub-dir") / "book.epub"
463+
464+
419465
def test_validation(tmp_path: Path) -> None:
420466
project = pr.Project(ptx_version="2")
421467
# Verify that repeated server names cause a validation error.

0 commit comments

Comments
 (0)