264 lines
7.4 KiB
Python
Executable File
264 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
from enum import Enum
|
|
import typer
|
|
import yaml
|
|
from pypdf import PdfReader, PdfWriter
|
|
|
|
|
|
# define directories
|
|
base_dir = Path.cwd()
|
|
config_dir = base_dir / "config"
|
|
content_dir = base_dir / "content"
|
|
cover_dir = base_dir / "cover"
|
|
output_dir = base_dir / "output"
|
|
|
|
# define executables and options
|
|
pandoc = "/usr/bin/pandoc"
|
|
pandoc_opts = []
|
|
typst = "/snap/bin/typst"
|
|
typst_opts = ["compile", "--font-path", "static/font", "--root", "."]
|
|
|
|
# helper class for available output formats
|
|
class Format(str, Enum):
|
|
pdf = "pdf"
|
|
png = "png"
|
|
|
|
# instantiate typer app
|
|
app = typer.Typer(no_args_is_help=True)
|
|
|
|
|
|
# ##################
|
|
# INTERNAL FUNCTIONS
|
|
# ##################
|
|
|
|
def _get_input_files(format: Format) -> list:
|
|
# read filenames from file if such a file exists
|
|
list_file = config_dir / f"source-files.{format.value}.txt"
|
|
if list_file.is_file():
|
|
with open(list_file, "r") as file:
|
|
return [line.strip() for line in file]
|
|
# use all files otherwise; ignore tredition flag
|
|
else:
|
|
paths = content_dir.glob('**/*.md')
|
|
return list(map(str, sorted(paths)))
|
|
|
|
|
|
def _run_command(executable: str, infiles: list, ex_opts: list = [], outfile: str = ""):
|
|
# put command together
|
|
outfile = [outfile] if outfile else []
|
|
command = [executable] + ex_opts + infiles + outfile
|
|
print(f"command is {command}")
|
|
|
|
# run shell command
|
|
try:
|
|
stdout = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Ciritical: Command failed with return code {e.returncode}.")
|
|
print(e.output)
|
|
raise typer.Exit(code=e.returncode)
|
|
else:
|
|
print(f"Success: Command finished with return code 0.")
|
|
print(stdout)
|
|
|
|
|
|
def _clean_dir(path: Path):
|
|
for file in path.iterdir():
|
|
# if file: delete
|
|
if file.is_file():
|
|
file.unlink()
|
|
# if dir: delete recursively
|
|
else:
|
|
# clean dir recursively
|
|
_clean_dir(file)
|
|
# delete dir unless it's the output_dir
|
|
if file != output_dir:
|
|
file.rmdir()
|
|
|
|
|
|
def _add_presuffix(path: Path|str, presuffix: str) -> Path:
|
|
# cast past to Path
|
|
if type(path) == str:
|
|
path = Path(path)
|
|
|
|
# add leading dot if nexessary
|
|
if presuffix[0] != ".":
|
|
presuffix = "." + presuffix
|
|
|
|
return path.parent / f"{path.stem}{presuffix}{path.suffix}"
|
|
|
|
|
|
def _merge_pdf_with_covers(frontcover: str, content: str, backcover: str):
|
|
pdflist = []
|
|
|
|
fc_path = output_dir / frontcover
|
|
if fc_path.is_file():
|
|
pdflist.append(str(fc_path))
|
|
|
|
if Path(content).is_file():
|
|
pdflist.append(content)
|
|
|
|
bc_path = output_dir / backcover
|
|
if bc_path.is_file():
|
|
pdflist.append(str(bc_path))
|
|
|
|
merger = PdfWriter()
|
|
for pdf in pdflist:
|
|
merger.append(pdf)
|
|
|
|
outfile = str(_add_presuffix(content, ".with-covers"))
|
|
merger.write(outfile)
|
|
merger.close()
|
|
|
|
|
|
# #############
|
|
# CLI COMMANDS
|
|
# #############
|
|
|
|
@app.command()
|
|
def clean():
|
|
_clean_dir(output_dir)
|
|
|
|
|
|
# format: pdf, png
|
|
# tredition: n.a.
|
|
@app.command()
|
|
def minizine(format: Format = Format.pdf):
|
|
# define executable and options
|
|
executable, executable_opts = typst, typst_opts
|
|
# executable_opts.append("--verbose")
|
|
|
|
# iterate over input files
|
|
minizine_dir = base_dir / "promo" / "minizine"
|
|
for path in minizine_dir.glob("*.typ"):
|
|
# get input file name
|
|
input_files = [str(path)]
|
|
# set output file name
|
|
output_file = str(output_dir / f"{path.stem}.{format.value}")
|
|
# run command
|
|
_run_command(executable, infiles=input_files, outfile=output_file, ex_opts=executable_opts)
|
|
|
|
|
|
# format: epub
|
|
# tredition=True: build epub w/o front-/backcover but also build tredition ebook cover
|
|
# tredition=False: build epub w/ front- and backcover
|
|
#@app.command()
|
|
def epub(tredition: bool = False, validate: bool = False):
|
|
# define pandoc defaults file
|
|
pandoc_defaults_file = str(config_dir / "pandoc-defaults.epub.yaml")
|
|
|
|
# define executable and options
|
|
executable, executable_opts = pandoc, pandoc_opts
|
|
executable_opts.append("--defaults")
|
|
executable_opts.append(pandoc_defaults_file)
|
|
executable_opts.append("--verbose")
|
|
|
|
# get input files
|
|
input_files = _get_input_files("epub")
|
|
|
|
# put together command and run it
|
|
_run_command(executable, ex_opts=executable_opts, infiles=input_files)
|
|
|
|
# validate epub
|
|
_run_command("/usr/bin/epubcheck", infiles=[str(output_dir / "papa-lach-doch-mal.epub")])
|
|
|
|
|
|
# format: pdf
|
|
# tredition=True: build PDF w/o front-/backcover; also build tredition cover (envelope)
|
|
# tredition=False: build PDF and merge with front- and backcover
|
|
@app.command()
|
|
def pdf(tredition: bool = False):
|
|
# define executable
|
|
executable, executable_opts = pandoc, pandoc_opts
|
|
|
|
# add pandoc defaults file to options
|
|
defaults_file = str(config_dir / f"pandoc-defaults.{Format.pdf}.yaml")
|
|
if Path(defaults_file).is_file():
|
|
executable_opts.append("--defaults")
|
|
executable_opts.append(defaults_file)
|
|
else:
|
|
print(f"Critical: Pandoc defaults file {defaults_file} not found.")
|
|
raise typer.Exit(code=1)
|
|
|
|
# get input files
|
|
input_files = _get_input_files(Format.pdf)
|
|
|
|
# if tredition flag is set: remove empty first and/or last page
|
|
if not tredition:
|
|
for i in (0, -1):
|
|
if input_files[i].endswith("leere seite.md"):
|
|
del input_files[i]
|
|
|
|
# get output filename (to be used later)
|
|
with open(defaults_file, "r") as file:
|
|
defaults = yaml.load(file, Loader=yaml.Loader)
|
|
outfile = defaults.get("output-file")
|
|
|
|
# add presuffix ".tredition" to output filename if flag is set
|
|
if tredition:
|
|
outfile = str(_add_presuffix(outfile, ".tredition"))
|
|
executable_opts.append("--output")
|
|
executable_opts.append(outfile)
|
|
|
|
# make PDf
|
|
_run_command(executable, ex_opts=executable_opts, infiles=input_files)
|
|
|
|
# make PDF covers
|
|
cover(tredition=tredition, format=Format.pdf, frontcover=not tredition, backcover=not tredition, envelope=tredition)
|
|
|
|
# add front-/backcover to PDF unless tredition flag is set
|
|
if not tredition:
|
|
_merge_pdf_with_covers("frontcover.pdf", outfile, "backcover.pdf")
|
|
|
|
# check metadata -> try veraPDF
|
|
pass
|
|
|
|
|
|
|
|
|
|
# format: pdf, png (only if tredition=False)
|
|
# tredition=True: build tredition ebook cover and tredition print envelope
|
|
# tredition=False: build regular front- and backcovers
|
|
@app.command()
|
|
def cover(format: Format = Format.pdf, tredition: bool = False, frontcover: bool = True, backcover: bool = True, envelope: bool = True):
|
|
# define executable and options
|
|
executable, executable_opts = typst, typst_opts
|
|
|
|
# check for nonsensical flag combination
|
|
if tredition and format == Format.png:
|
|
print("Warning: PNG can't be used as output format in combination with --tredition; setting format to PDF.")
|
|
format = Format.pdf
|
|
|
|
# select files
|
|
if tredition:
|
|
paths = cover_dir.glob("*tredition*.typ")
|
|
else:
|
|
paths = [path for path in cover_dir.glob("*.typ") if not "tredition" in path.name]
|
|
|
|
# remove input files with unset flags
|
|
if not envelope:
|
|
path = [path for path in paths if not "envelope" in path.name]
|
|
if not frontcover:
|
|
path = [path for path in paths if not "frontcover" in path.name]
|
|
if not backcover:
|
|
path = [path for path in paths if not "backcover" in path.name]
|
|
|
|
# iterate over files
|
|
for path in paths:
|
|
# get input file name
|
|
input_files = [str(path)]
|
|
# set output file name
|
|
output_file = str(output_dir / f"{path.stem}.{format.value}")
|
|
|
|
# run command
|
|
_run_command(executable, infiles=input_files, outfile=output_file, ex_opts=executable_opts)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app()
|
|
|