Skip to content

Commit 797ce78

Browse files
author
Cimon Lucas (LCM)
committed
Harmonizing typer usage
1 parent 1d4e256 commit 797ce78

File tree

3 files changed

+119
-64
lines changed

3 files changed

+119
-64
lines changed

pdfly/cli.py

+36-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import pdfly.x2pdf
2222

2323

24-
def version_callback(value: bool) -> None:
24+
def version_callback(value: bool):
2525
import pypdf
2626

2727
if value:
@@ -43,7 +43,7 @@ def version_callback(value: bool) -> None:
4343
def common(
4444
ctx: typer.Context,
4545
version: bool = typer.Option(None, "--version", callback=version_callback),
46-
) -> None:
46+
):
4747
pass
4848

4949

@@ -54,13 +54,11 @@ def extract_images(
5454
typer.Argument(
5555
exists=True,
5656
file_okay=True,
57-
dir_okay=False,
58-
writable=False,
5957
readable=True,
6058
resolve_path=True,
6159
),
6260
]
63-
) -> None:
61+
):
6462
pdfly.extract_images.main(pdf)
6563

6664

@@ -71,14 +69,12 @@ def up2(
7169
typer.Argument(
7270
exists=True,
7371
file_okay=True,
74-
dir_okay=False,
75-
writable=False,
7672
readable=True,
7773
resolve_path=True,
7874
),
7975
],
8076
out: Path,
81-
) -> None:
77+
):
8278
pdfly.up2.main(pdf, out)
8379

8480

@@ -89,8 +85,6 @@ def cat(
8985
typer.Argument(
9086
exists=True,
9187
file_okay=True,
92-
dir_okay=False,
93-
writable=False,
9488
readable=True,
9589
resolve_path=True,
9690
),
@@ -102,7 +96,7 @@ def cat(
10296
verbose: bool = typer.Option(
10397
False, help="show page ranges as they are being read"
10498
),
105-
) -> None:
99+
):
106100
pdfly.cat.main(filename, fn_pgrgs, output, verbose)
107101

108102

@@ -113,8 +107,6 @@ def rm(
113107
typer.Argument(
114108
exists=True,
115109
file_okay=True,
116-
dir_okay=False,
117-
writable=False,
118110
readable=True,
119111
resolve_path=True,
120112
),
@@ -126,7 +118,7 @@ def rm(
126118
verbose: bool = typer.Option(
127119
False, help="show page ranges as they are being read"
128120
),
129-
) -> None:
121+
):
130122
pdfly.rm.main(filename, fn_pgrgs, output, verbose)
131123

132124

@@ -137,8 +129,6 @@ def metadata(
137129
typer.Argument(
138130
exists=True,
139131
file_okay=True,
140-
dir_okay=False,
141-
writable=False,
142132
readable=True,
143133
resolve_path=True,
144134
),
@@ -150,7 +140,7 @@ def metadata(
150140
help="output format",
151141
show_default=True,
152142
),
153-
) -> None:
143+
):
154144
pdfly.metadata.main(pdf, output)
155145

156146

@@ -161,8 +151,6 @@ def pagemeta(
161151
typer.Argument(
162152
exists=True,
163153
file_okay=True,
164-
dir_okay=False,
165-
writable=False,
166154
readable=True,
167155
resolve_path=True,
168156
),
@@ -175,7 +163,7 @@ def pagemeta(
175163
help="output format",
176164
show_default=True,
177165
),
178-
) -> None:
166+
):
179167
pdfly.pagemeta.main(
180168
pdf,
181169
page_index,
@@ -190,19 +178,17 @@ def extract_text(
190178
typer.Argument(
191179
exists=True,
192180
file_okay=True,
193-
dir_okay=False,
194-
writable=False,
195181
readable=True,
196182
resolve_path=True,
197183
),
198184
]
199-
) -> None:
185+
):
200186
"""Extract text from a PDF file."""
201187
from pypdf import PdfReader
202188

203189
reader = PdfReader(str(pdf))
204190
for page in reader.pages:
205-
print(page.extract_text())
191+
typer.echo(page.extract_text())
206192

207193

208194
@entry_point.command(name="compress", help=pdfly.compress.__doc__) # type: ignore[misc]
@@ -212,8 +198,6 @@ def compress(
212198
typer.Argument(
213199
exists=True,
214200
file_okay=True,
215-
dir_okay=False,
216-
writable=False,
217201
readable=True,
218202
resolve_path=True,
219203
),
@@ -225,13 +209,21 @@ def compress(
225209
writable=True,
226210
),
227211
],
228-
) -> None:
212+
):
229213
pdfly.compress.main(pdf, output)
230214

231215

232216
@entry_point.command(name="update-offsets", help=pdfly.update_offsets.__doc__) # type: ignore[misc]
233217
def update_offsets(
234-
file_in: Path,
218+
file_in: Annotated[
219+
Path,
220+
typer.Argument(
221+
exists=True,
222+
file_okay=True,
223+
readable=True,
224+
resolve_path=True,
225+
),
226+
],
235227
file_out: Path,
236228
encoding: str = typer.Option(
237229
"ISO-8859-1",
@@ -240,21 +232,32 @@ def update_offsets(
240232
verbose: bool = typer.Option(
241233
False, help="Show progress while processing."
242234
),
243-
) -> None:
235+
):
244236
pdfly.update_offsets.main(file_in, file_out, encoding, verbose)
245237

246238

247239
@entry_point.command(name="x2pdf", help=pdfly.x2pdf.__doc__) # type: ignore[misc]
248240
def x2pdf(
249-
x: List[Path],
241+
x: List[
242+
Annotated[
243+
Path,
244+
typer.Argument(
245+
exists=True,
246+
file_okay=True,
247+
readable=True,
248+
resolve_path=True,
249+
),
250+
]
251+
],
250252
output: Annotated[
251253
Path,
252254
typer.Option(
253255
"-o",
254256
"--output",
255-
exists=False,
256257
writable=True,
257258
),
258259
],
259-
) -> int:
260-
return pdfly.x2pdf.main(x, output)
260+
):
261+
exit_code = pdfly.x2pdf.main(x, output)
262+
if exit_code:
263+
raise typer.Exit(code=exit_code)

pdfly/x2pdf.py

+26-29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Convert one or more files to PDF. Each file is a page."""
22

3+
from io import BytesIO
34
from pathlib import Path
45
from typing import List
56

67
from fpdf import FPDF
8+
from pypdf import PdfReader, PdfWriter
79
from PIL import Image
810
from rich.console import Console
911

@@ -16,39 +18,34 @@ def px_to_mm(px: float) -> float:
1618
return mm
1719

1820

19-
def image_to_pdf(pdf: FPDF, x: Path) -> None:
20-
cover = Image.open(x)
21-
width: float
22-
height: float
23-
width, height = cover.size
24-
cover.close()
21+
def image_to_pdf(filepath: Path) -> BytesIO:
22+
with Image.open(filepath) as cover:
23+
width, height = cover.size
2524
width, height = px_to_mm(width), px_to_mm(height)
26-
25+
pdf = FPDF(unit="mm")
2726
pdf.add_page(format=(width, height))
28-
pdf.image(x, x=0, y=0)
27+
pdf.image(filepath, x=0, y=0)
28+
return BytesIO(pdf.output())
2929

3030

31-
def main(xs: List[Path], output: Path) -> int:
31+
def main(in_filepaths: List[Path], out_filepath: Path) -> int:
3232
console = Console()
33-
for x in xs:
34-
path_str = str(x).lower()
35-
if path_str.endswith(("doc", "docx", "odt")):
36-
console.print("[red]Error: Cannot convert Word documents to PDF")
37-
return 1
38-
if not x.exists():
39-
console.print(f"[red]Error: File '{x}' does not exist.")
40-
return 2
41-
if output.exists():
42-
console.print(f"[red]Error: Output file '{output}' exist.")
43-
return 3
44-
pdf = FPDF(
45-
unit="mm",
46-
)
47-
for x in xs:
48-
path_str = str(x).lower()
33+
exit_code = 0
34+
writer = PdfWriter()
35+
for filepath in in_filepaths:
36+
if filepath.name.endswith(".pdf"):
37+
for page in PdfReader(filepath).pages:
38+
writer.insert_page(page)
39+
continue
4940
try:
50-
image_to_pdf(pdf, x)
41+
pdf_bytes = image_to_pdf(filepath)
42+
new_page = PdfReader(pdf_bytes).pages[0]
43+
writer.insert_page(new_page)
5144
except Exception:
52-
console.print(f"[red]Error: Could not convert '{x}' to a PDF.")
53-
pdf.output(str(output))
54-
return 0
45+
console.print(
46+
f"[red]Error: Could not convert '{filepath}' to a PDF."
47+
)
48+
console.print_exception(extra_lines=1, max_frames=1)
49+
exit_code += 1
50+
writer.write(out_filepath)
51+
return exit_code

tests/test_x2pdf.py

+57-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99
from .conftest import run_cli
1010

1111

12-
def test_x2pdf(capsys, tmp_path: Path) -> None:
12+
def test_x2pdf_succeed_to_convert_jpg(capsys, tmp_path: Path):
1313
# Arrange
1414
output = tmp_path / "out.pdf"
15-
assert not output.exists()
1615

1716
# Act
1817
exit_code = run_cli(
@@ -29,3 +28,59 @@ def test_x2pdf(capsys, tmp_path: Path) -> None:
2928
assert exit_code == 0, captured
3029
assert captured.out == ""
3130
assert output.exists()
31+
32+
33+
def test_x2pdf_succeed_to_embed_pdfs(capsys, tmp_path: Path):
34+
# Arrange
35+
output = tmp_path / "out.pdf"
36+
37+
# Act
38+
exit_code = run_cli(
39+
[
40+
"x2pdf",
41+
"sample-files/001-trivial/minimal-document.pdf",
42+
"sample-files/002-trivial-libre-office-writer/002-trivial-libre-office-writer.pdf",
43+
"--output",
44+
str(output),
45+
]
46+
)
47+
48+
# Assert
49+
captured = capsys.readouterr()
50+
assert exit_code == 0, captured
51+
assert captured.out == ""
52+
assert output.exists()
53+
54+
55+
def test_x2pdf_fail_to_open_file(capsys, tmp_path: Path):
56+
# Arrange & Act
57+
exit_code = run_cli(
58+
[
59+
"x2pdf",
60+
"NonExistingFile",
61+
"--output",
62+
str(tmp_path / "out.pdf"),
63+
]
64+
)
65+
66+
# Assert
67+
captured = capsys.readouterr()
68+
assert exit_code == 1, captured
69+
assert "No such file or directory" in captured.out
70+
71+
72+
def test_x2pdf_fail_to_convert(capsys, tmp_path: Path):
73+
# Arrange & Act
74+
exit_code = run_cli(
75+
[
76+
"x2pdf",
77+
"README.md",
78+
"--output",
79+
str(tmp_path / "out.pdf"),
80+
]
81+
)
82+
83+
# Assert
84+
captured = capsys.readouterr()
85+
assert exit_code == 1, captured
86+
assert "Error: Could not convert 'README.md' to a PDF" in captured.out

0 commit comments

Comments
 (0)