# -*- coding: utf-8 -*- # Copyright (c) 2022 Salvador E. Tropea # Copyright (c) 2022 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) import re import os import glob from .gs import GS from .error import KiPlotConfigurationError from .kiplot import config_output, get_output_dir, run_output, run_command from .misc import MISSING_TOOL, WRONG_INSTALL, WRONG_ARGUMENTS, INTERNAL_ERROR, W_NOTPDF, MISSING_FILES, W_NOMATCH from .optionable import Optionable, BaseOptions from .registrable import RegOutput from .create_pdf import create_pdf_from_pages from .macros import macros, document, output_class # noqa: F401 from . import log logger = log.get_logger() class FilesList(Optionable): def __init__(self): super().__init__() with document: self.source = '*.pdf' """ *File names to add, wildcards allowed. Use ** for recursive match. By default this pattern is applied to the output dir specified with `-d` command line option. See the `from_cwd` option """ self.from_cwd = False """ Use the current working directory instead of the dir specified by `-d` """ self.from_output = '' """ *Collect files from the selected output. When used the `source` option is ignored """ self.filter = r'.*\.pdf' """ A regular expression that source files must match """ class PDFUniteOptions(BaseOptions): def __init__(self): with document: self.output = GS.def_global_output """ *Name for the generated PDF (%i=name of the output %x=pdf) """ self.outputs = FilesList """ *[list(dict)] Which files will be included """ self.use_external_command = False """ Use the `pdfunite` tool instead of PyPDF2 Python module """ super().__init__() self._expand_ext = 'pdf' def config(self, parent): super().config(parent) if isinstance(self.outputs, type): KiPlotConfigurationError('Nothing to join') self._expand_id = parent.name def get_files(self, output, no_out_run=False): output_real = os.path.realpath(output) files = [] out_dir_cwd = os.getcwd() out_dir_default = self.expand_filename_pcb(GS.out_dir) for f in self.outputs: # Get the list of candidates files_list = None if f.from_output: out = RegOutput.get_output(f.from_output) if out is not None: config_output(out) files_list = out.get_targets(get_output_dir(out.dir, out, dry=True)) else: GS.exit_with_error(f'Unknown output `{f.from_output}` selected in {self._parent}', WRONG_ARGUMENTS) if not no_out_run: for file in files_list: if not os.path.isfile(file): # The target doesn't exist if not out._done: # The output wasn't created in this run, try running it run_output(out) if not os.path.isfile(file): # Still missing, something is wrong GS.exit_with_error('Unable to generate `{file}` from {out}', INTERNAL_ERROR) else: out_dir = out_dir_cwd if f.from_cwd else out_dir_default source = f.expand_filename_both(f.source, make_safe=False) files_list = glob.iglob(os.path.join(out_dir, source), recursive=True) # Filter and adapt them old_len = len(files) for fname in filter(re.compile(f.filter).match, files_list): fname_real = os.path.realpath(fname) # Avoid including the output if fname_real == output_real: continue files.append(fname_real) if len(files) == old_len: logger.warning(W_NOMATCH+'No match found for `{}`'.format(f.from_output if f.from_output else f.source)) return files def get_targets(self, out_dir): return [self._parent.expand_filename(out_dir, self.output)] def get_dependencies(self): output = self.get_targets(self.expand_filename_pcb(GS.out_dir))[0] files = self.get_files(output, no_out_run=True) return files def run_external(self, files, output): cmd = ['pdfunite']+files+[output] try: run_command(cmd, err_msg='Failed to invoke pdfunite command, error {ret}', err_lvl=WRONG_INSTALL) except FileNotFoundError: GS.exit_with_error('Missing `pdfunite` command, install it (poppler-utils)', MISSING_TOOL) def run(self, output): # Output file name logger.debug('Collecting files') # Collect the files files = self.get_files(output) for fn in files: with open(fn, 'rb') as f: sig = f.read(4) if sig != b'%PDF': logger.warning(W_NOTPDF+'Joining a non PDF file `{}`, will most probably fail'.format(fn)) if len(files) < 2: GS.exit_with_error(f'At least two files must be joined ({files})', MISSING_FILES) logger.debug('Generating `{}` PDF'.format(output)) if os.path.isfile(output): os.remove(output) if self.use_external_command: self.run_external(files, output) else: create_pdf_from_pages(files, output) @output_class class PDFUnite(BaseOutput): # noqa: F821 """ PDF joiner Generates a new PDF from other outputs. This is just a PDF joiner, using `pdfunite` from Poppler Utils. """ def __init__(self): super().__init__() with document: self.options = PDFUniteOptions """ *[dict] Options for the `pdfunite` output """ self._none_related = True def get_dependencies(self): return self.options.get_dependencies()