From c70fc1e7bc8e7b88da255823d50699b4e4ae0197 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 17:25:45 +0200 Subject: [PATCH 1/7] 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) From eb208252b6f6017cf3077f6e71361b1a6296b06a Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 17:29:04 +0200 Subject: [PATCH 2/7] Less Pylint warnings --- songbook_core/build.py | 4 ++-- songbook_core/errors.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/songbook_core/build.py b/songbook_core/build.py index 330ae6a3..b4a77be4 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -5,7 +5,6 @@ import codecs import glob -import logging import os.path import re import subprocess @@ -61,7 +60,8 @@ class Songbook(object): self.songslist = None self._parse(raw_songbook) - def _set_songs_default(self, config): + @staticmethod + def _set_songs_default(config): """Set the default values for the Song() class. Argument: diff --git a/songbook_core/errors.py b/songbook_core/errors.py index 448814f9..6b850712 100644 --- a/songbook_core/errors.py +++ b/songbook_core/errors.py @@ -13,7 +13,7 @@ class SongbookError(Exception): class TemplateError(SongbookError): """Error during template generation""" - def __init__(self, original, message = None): + def __init__(self, original, message=None): super(TemplateError, self).__init__() self.original = original self.message = message From e4182dc4565c1849ac05c4a7f5d8d8378b3e8ac6 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 17:48:36 +0200 Subject: [PATCH 3/7] =?UTF-8?q?Ne=20fait=20l'analyse=20des=20chansons=20qu?= =?UTF-8?q?e=20si=20n=C3=A9cessaire=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- songbook_core/build.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/songbook_core/build.py b/songbook_core/build.py index b4a77be4..336f6b9b 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -58,7 +58,7 @@ class Songbook(object): 'datadir': os.path.abspath('.'), } self.songslist = None - self._parse(raw_songbook) + self._parse_raw(raw_songbook) @staticmethod def _set_songs_default(config): @@ -82,7 +82,7 @@ class Songbook(object): ] + [',']) ] - def _parse(self, raw_songbook): + def _parse_raw(self, raw_songbook): """Parse raw_songbook. The principle is: some special keys have their value processed; others @@ -90,7 +90,7 @@ class Songbook(object): """ self.config.update(raw_songbook) self.config['datadir'] = os.path.abspath(self.config['datadir']) - ### Some post-processing + # Compute song list if self.config['content'] is None: self.config['content'] = [ @@ -104,20 +104,24 @@ class Songbook(object): '*.sg', ) ] - self.songslist = SongsList(self.config['datadir']) - self.songslist.append_list(self.config['content']) # Ensure self.config['authwords'] contains all entries for (key, value) in DEFAULT_AUTHWORDS.items(): if key not in self.config['authwords']: self.config['authwords'][key] = value + def _parse_songs(self): + """Parse songs included in songbook.""" + self.songslist = SongsList(self.config['datadir']) + self.songslist.append_list(self.config['content']) + def write_tex(self, output): """Build the '.tex' file corresponding to self. Arguments: - output: a file object, in which the file will be written. """ + self._parse_songs() renderer = TexRenderer( self.config['template'], self.config['datadir'], @@ -217,7 +221,6 @@ class SongbookBuilder(object): 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: @@ -225,6 +228,7 @@ class SongbookBuilder(object): def build_pdf(self): """Build .pdf file from .tex file""" + self._run_once(self._set_latex) if subprocess.call( ["pdflatex"] + self._pdflatex_options + [self.basename] ): From 403ef4ce8ec9514560698a8c830e9babd0ec0e37 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 18:18:40 +0200 Subject: [PATCH 4/7] Documentation (#21) --- songbook_core/build.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/songbook_core/build.py b/songbook_core/build.py index 336f6b9b..662dc0b6 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -142,10 +142,6 @@ class Songbook(object): 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 @@ -157,8 +153,11 @@ class SongbookBuilder(object): _called_functions = {} def __init__(self, raw_songbook, basename, logger): + # Representation of the .sb songbook configuration file. self.songbook = Songbook(raw_songbook, basename) + # Basename of the songbook to be built. self.basename = basename + # logging object to use self.logger = logger def _run_once(self, function, *args, **kwargs): From 106a00efbdbaff555f546f1fe165957e78b67aa5 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 9 Apr 2014 18:19:05 +0200 Subject: [PATCH 5/7] Make SongbookBuilder.logger optional --- songbook_core/build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/songbook_core/build.py b/songbook_core/build.py index 662dc0b6..31f65a6c 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -152,7 +152,7 @@ class SongbookBuilder(object): # are function; values are return values of functions. _called_functions = {} - def __init__(self, raw_songbook, basename, logger): + def __init__(self, raw_songbook, basename, logger=None): # Representation of the .sb songbook configuration file. self.songbook = Songbook(raw_songbook, basename) # Basename of the songbook to be built. @@ -237,7 +237,8 @@ class SongbookBuilder(object): """Make index""" sxd_files = glob.glob("%s_*.sxd" % self.basename) for sxd_file in sxd_files: - self.logger.info("processing " + sxd_file) + if self.logger: + 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')) From dd7ceab88778adeb7f3f549e85991b2b694a1232 Mon Sep 17 00:00:00 2001 From: Luthaf Date: Wed, 9 Apr 2014 20:54:46 +0100 Subject: [PATCH 6/7] =?UTF-8?q?La=20sortie=20LaTeX=20est=20redirig=C3=A9e?= =?UTF-8?q?=20vers=20le=20logger.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- songbook | 2 +- songbook_core/build.py | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/songbook b/songbook index 1e9aefbc..20392cf4 100755 --- a/songbook +++ b/songbook @@ -75,7 +75,7 @@ def main(): """Main function:""" # Logging configuration - logging.basicConfig(name='songbook') + logging.basicConfig(name='songbook', level=logging.INFO) logger = logging.getLogger('songbook') # set script locale to match user's diff --git a/songbook_core/build.py b/songbook_core/build.py index 31f65a6c..7479b1c5 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -228,9 +228,25 @@ class SongbookBuilder(object): def build_pdf(self): """Build .pdf file from .tex file""" self._run_once(self._set_latex) - if subprocess.call( - ["pdflatex"] + self._pdflatex_options + [self.basename] - ): + from subprocess import Popen, PIPE + + #if self.logger: + # out = self.logger.stream + #else: + # out = None + + p = Popen( + ["pdflatex"] + self._pdflatex_options + [self.basename], + stdout=PIPE, + stderr=PIPE) + log = '' + line = p.stdout.readline() + while line: + log += line + line = p.stdout.readline() + self.logger.info(log) + + if p.returncode: raise errors.LatexCompilationError(self.basename) def build_sbx(self): From 9cb4f1a3d4aead616df89118d768683752c8ed32 Mon Sep 17 00:00:00 2001 From: Luthaf Date: Wed, 9 Apr 2014 21:00:13 +0100 Subject: [PATCH 7/7] Nettoyage --- songbook_core/build.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/songbook_core/build.py b/songbook_core/build.py index 7479b1c5..634e3bfa 100644 --- a/songbook_core/build.py +++ b/songbook_core/build.py @@ -7,7 +7,7 @@ import codecs import glob import os.path import re -import subprocess +from subprocess import Popen, PIPE, call from songbook_core import __DATADIR__ from songbook_core import errors @@ -228,13 +228,6 @@ class SongbookBuilder(object): def build_pdf(self): """Build .pdf file from .tex file""" self._run_once(self._set_latex) - from subprocess import Popen, PIPE - - #if self.logger: - # out = self.logger.stream - #else: - # out = None - p = Popen( ["pdflatex"] + self._pdflatex_options + [self.basename], stdout=PIPE, @@ -262,7 +255,7 @@ class SongbookBuilder(object): @staticmethod def build_custom(command): """Run a shell command""" - exit_code = subprocess.call(command, shell=True) + exit_code = call(command, shell=True) if exit_code: raise errors.StepCommandError(command, exit_code)