From c70fc1e7bc8e7b88da255823d50699b4e4ae0197 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 17:25:45 +0200 Subject: [PATCH] Remplacement de buildsongbook par la classe SongbookBuilder (#21) --- songbook | 14 ++- songbook_core/build.py | 229 ++++++++++++++++++++++++----------------- 2 files changed, 138 insertions(+), 105 deletions(-) diff --git a/songbook b/songbook index b5eefa51..1e9aefbc 100755 --- a/songbook +++ b/songbook @@ -12,7 +12,7 @@ import os.path import textwrap import sys -from songbook_core.build import buildsongbook, DEFAULT_STEPS +from songbook_core.build import SongbookBuilder, DEFAULT_STEPS from songbook_core import __STR_VERSION__ from songbook_core import errors @@ -105,13 +105,11 @@ def main(): songbook['datadir'] = os.path.dirname(songbook_path) try: - buildsongbook( - songbook, - basename, - steps=options.steps, - interactive=True, - logger=logger, - ) + sb_builder = SongbookBuilder(songbook, basename, logger=logger) + sb_builder.interactive = True + sb_builder.unsafe = True + + sb_builder.build_steps(options.steps) except errors.SongbookError as error: logger.error(error) sys.exit(1) diff --git a/songbook_core/build.py b/songbook_core/build.py index d832924a..330ae6a3 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -24,6 +24,18 @@ DEFAULT_AUTHWORDS = { "sep": ["and"], } DEFAULT_STEPS = ['tex', 'pdf', 'sbx', 'pdf', 'clean'] +GENERATED_EXTENSIONS = [ + "_auth.sbx", + "_auth.sxd", + ".aux", + ".log", + ".out", + ".sxc", + ".tex", + "_title.sbx", + "_title.sxd", + ] + # pylint: disable=too-few-public-methods @@ -123,102 +135,125 @@ class Songbook(object): renderer.render_tex(output, context) -def clean(basename): - """Clean (some) temporary files used during compilation. - - Depending of the LaTeX modules used in the template, there may be others - that are not deleted by this function.""" - generated_extensions = [ - "_auth.sbx", - "_auth.sxd", - ".aux", - ".log", - ".out", - ".sxc", - ".tex", - "_title.sbx", - "_title.sxd", - ] - - for ext in generated_extensions: - if os.path.isfile(basename + ext): - try: - os.unlink(basename + ext) - except Exception as exception: - raise errors.CleaningError(basename + ext, exception) - - -def buildsongbook( - raw_songbook, - basename, - steps=None, - interactive=False, - logger=logging.getLogger(), - ): - """Build a songbook - - Arguments: - - raw_songbook: Python representation of the .sb songbook configuration - file. - - steps: list of steps to perform to compile songbook. Available steps are: - - tex: build .tex file from templates; - - pdf: compile .tex using pdflatex; - - sbx: compile song and author indexes; - - clean: remove temporary files, - - any string beginning with a sharp sign (#): it is interpreted as a - command to run in a shell. - - basename: basename of the songbook to be built. - - interactive: in False, do not expect anything from stdin. - """ +class SongbookBuilder(object): + """Provide methods to compile a songbook.""" + + # Representation of the .sb songbook configuration file. + songbook = {} + # Basename of the songbook to be built. + basename = None + # if False, do not expect anything from stdin. + interactive = False + # if True, allow unsafe option, like adding the --shell-escape to pdflatex + unsafe = False + # Options to add to pdflatex + _pdflatex_options = [] + # Dictionary of functions that have been called by self._run_once(). Keys + # are function; values are return values of functions. + _called_functions = {} + + def __init__(self, raw_songbook, basename, logger): + self.songbook = Songbook(raw_songbook, basename) + self.basename = basename + self.logger = logger + + def _run_once(self, function, *args, **kwargs): + """Run function if it has not been run yet. + + If it as, return the previous return value. + """ + if function not in self._called_functions: + self._called_functions[function] = function(*args, **kwargs) + return self._called_functions[function] + + def _set_latex(self): + """Set TEXINPUTS and LaTeX options.""" + if not 'TEXINPUTS' in os.environ.keys(): + os.environ['TEXINPUTS'] = '' + os.environ['TEXINPUTS'] += os.pathsep + os.path.join( + __DATADIR__, + 'latex', + ) + os.environ['TEXINPUTS'] += os.pathsep + os.path.join( + self.songbook.config['datadir'], + 'latex', + ) - if steps is None: - steps = DEFAULT_STEPS - - songbook = Songbook(raw_songbook, basename) - if not 'TEXINPUTS' in os.environ.keys(): - os.environ['TEXINPUTS'] = '' - os.environ['TEXINPUTS'] += os.pathsep + os.path.join( - __DATADIR__, - 'latex', - ) - os.environ['TEXINPUTS'] += os.pathsep + os.path.join( - songbook.config['datadir'], - 'latex', - ) - - # pdflatex options - pdflatex_options = [] - pdflatex_options.append("--shell-escape") # Lilypond compilation - if not interactive: - pdflatex_options.append("-halt-on-error") - - # Compilation - for step in steps: - if step == 'tex': - # Building .tex file from templates - with codecs.open("{}.tex".format(basename), 'w', 'utf-8') as output: - songbook.write_tex(output) - elif step == 'pdf': - if subprocess.call(["pdflatex"] + pdflatex_options + [basename]): - raise errors.LatexCompilationError(basename) - elif step == 'sbx': - # Make index - sxd_files = glob.glob("%s_*.sxd" % basename) - for sxd_file in sxd_files: - logger.info("processing " + sxd_file) - idx = process_sxd(sxd_file) - index_file = open(sxd_file[:-3] + "sbx", "w") + if self.unsafe: + self._pdflatex_options.append("--shell-escape") + if not self.interactive: + self._pdflatex_options.append("-halt-on-error") + + def build_steps(self, steps=None): + """Perform steps on the songbook by calling relevant self.build_*() + + Arguments: + - steps: list of steps to perform to compile songbook. Available steps + are: + - tex: build .tex file from templates; + - pdf: compile .tex using pdflatex; + - sbx: compile song and author indexes; + - clean: remove temporary files, + - any string beginning with a sharp sign (#): it is interpreted as a + command to run in a shell. + """ + if not steps: + steps = DEFAULT_STEPS + + for step in steps: + if step == 'tex': + self.build_tex() + elif step == 'pdf': + self.build_pdf() + elif step == 'sbx': + self.build_sbx() + elif step == 'clean': + self.clean() + elif step.startswith("%"): + self.build_custom(step[1:]) + else: + # Unknown step name + raise errors.UnknownStep(step) + + def build_tex(self): + """Build .tex file from templates""" + self._run_once(self._set_latex) + with codecs.open( + "{}.tex".format(self.basename), 'w', 'utf-8', + ) as output: + self.songbook.write_tex(output) + + def build_pdf(self): + """Build .pdf file from .tex file""" + if subprocess.call( + ["pdflatex"] + self._pdflatex_options + [self.basename] + ): + raise errors.LatexCompilationError(self.basename) + + def build_sbx(self): + """Make index""" + sxd_files = glob.glob("%s_*.sxd" % self.basename) + for sxd_file in sxd_files: + self.logger.info("processing " + sxd_file) + idx = process_sxd(sxd_file) + with open(sxd_file[:-3] + "sbx", "w") as index_file: index_file.write(idx.entries_to_str().encode('utf8')) - index_file.close() - elif step == 'clean': - # Cleaning - clean(basename) - elif step.startswith("%"): - # Shell command - command = step[1:] - exit_code = subprocess.call(command, shell=True) - if exit_code: - raise errors.StepCommandError(command, exit_code) - else: - # Unknown step name - raise errors.UnknownStep(step) + + @staticmethod + def build_custom(command): + """Run a shell command""" + exit_code = subprocess.call(command, shell=True) + if exit_code: + raise errors.StepCommandError(command, exit_code) + + def clean(self): + """Clean (some) temporary files used during compilation. + + Depending of the LaTeX modules used in the template, there may be others + that are not deleted by this function.""" + for ext in GENERATED_EXTENSIONS: + if os.path.isfile(self.basename + ext): + try: + os.unlink(self.basename + ext) + except Exception as exception: + raise errors.CleaningError(self.basename + ext, exception)