#!/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()