Browse Source

Merge pull request #25 from patacrep/songbookbuilder

Le principe de la branche semble accepté, même si des détails restent à régler.
pull/27/head
Louis 11 years ago
parent
commit
c7c1762138
  1. 16
      songbook
  2. 258
      songbook_core/build.py
  3. 2
      songbook_core/errors.py

16
songbook

@ -12,7 +12,7 @@ import os.path
import textwrap import textwrap
import sys 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 __STR_VERSION__
from songbook_core import errors from songbook_core import errors
@ -75,7 +75,7 @@ def main():
"""Main function:""" """Main function:"""
# Logging configuration # Logging configuration
logging.basicConfig(name='songbook') logging.basicConfig(name='songbook', level=logging.INFO)
logger = logging.getLogger('songbook') logger = logging.getLogger('songbook')
# set script locale to match user's # set script locale to match user's
@ -105,13 +105,11 @@ def main():
songbook['datadir'] = os.path.dirname(songbook_path) songbook['datadir'] = os.path.dirname(songbook_path)
try: try:
buildsongbook( sb_builder = SongbookBuilder(songbook, basename, logger=logger)
songbook, sb_builder.interactive = True
basename, sb_builder.unsafe = True
steps=options.steps,
interactive=True, sb_builder.build_steps(options.steps)
logger=logger,
)
except errors.SongbookError as error: except errors.SongbookError as error:
logger.error(error) logger.error(error)
sys.exit(1) sys.exit(1)

258
songbook_core/build.py

@ -5,10 +5,9 @@
import codecs import codecs
import glob import glob
import logging
import os.path import os.path
import re import re
import subprocess from subprocess import Popen, PIPE, call
from songbook_core import __DATADIR__ from songbook_core import __DATADIR__
from songbook_core import errors from songbook_core import errors
@ -24,6 +23,18 @@ DEFAULT_AUTHWORDS = {
"sep": ["and"], "sep": ["and"],
} }
DEFAULT_STEPS = ['tex', 'pdf', 'sbx', 'pdf', 'clean'] 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 # pylint: disable=too-few-public-methods
@ -47,9 +58,10 @@ class Songbook(object):
'datadir': os.path.abspath('.'), 'datadir': os.path.abspath('.'),
} }
self.songslist = None self.songslist = None
self._parse(raw_songbook) self._parse_raw(raw_songbook)
def _set_songs_default(self, config): @staticmethod
def _set_songs_default(config):
"""Set the default values for the Song() class. """Set the default values for the Song() class.
Argument: Argument:
@ -70,7 +82,7 @@ class Songbook(object):
] + [',']) ] + [','])
] ]
def _parse(self, raw_songbook): def _parse_raw(self, raw_songbook):
"""Parse raw_songbook. """Parse raw_songbook.
The principle is: some special keys have their value processed; others The principle is: some special keys have their value processed; others
@ -78,7 +90,7 @@ class Songbook(object):
""" """
self.config.update(raw_songbook) self.config.update(raw_songbook)
self.config['datadir'] = os.path.abspath(self.config['datadir']) self.config['datadir'] = os.path.abspath(self.config['datadir'])
### Some post-processing
# Compute song list # Compute song list
if self.config['content'] is None: if self.config['content'] is None:
self.config['content'] = [ self.config['content'] = [
@ -92,20 +104,24 @@ class Songbook(object):
'*.sg', '*.sg',
) )
] ]
self.songslist = SongsList(self.config['datadir'])
self.songslist.append_list(self.config['content'])
# Ensure self.config['authwords'] contains all entries # Ensure self.config['authwords'] contains all entries
for (key, value) in DEFAULT_AUTHWORDS.items(): for (key, value) in DEFAULT_AUTHWORDS.items():
if key not in self.config['authwords']: if key not in self.config['authwords']:
self.config['authwords'][key] = value 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): def write_tex(self, output):
"""Build the '.tex' file corresponding to self. """Build the '.tex' file corresponding to self.
Arguments: Arguments:
- output: a file object, in which the file will be written. - output: a file object, in which the file will be written.
""" """
self._parse_songs()
renderer = TexRenderer( renderer = TexRenderer(
self.config['template'], self.config['template'],
self.config['datadir'], self.config['datadir'],
@ -123,102 +139,134 @@ class Songbook(object):
renderer.render_tex(output, context) renderer.render_tex(output, context)
def clean(basename): class SongbookBuilder(object):
"""Clean (some) temporary files used during compilation. """Provide methods to compile a songbook."""
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.
"""
if steps is None: # if False, do not expect anything from stdin.
steps = DEFAULT_STEPS interactive = False
# if True, allow unsafe option, like adding the --shell-escape to pdflatex
songbook = Songbook(raw_songbook, basename) unsafe = False
if not 'TEXINPUTS' in os.environ.keys(): # Options to add to pdflatex
os.environ['TEXINPUTS'] = '' _pdflatex_options = []
os.environ['TEXINPUTS'] += os.pathsep + os.path.join( # Dictionary of functions that have been called by self._run_once(). Keys
__DATADIR__, # are function; values are return values of functions.
'latex', _called_functions = {}
)
os.environ['TEXINPUTS'] += os.pathsep + os.path.join( def __init__(self, raw_songbook, basename, logger=None):
songbook.config['datadir'], # Representation of the .sb songbook configuration file.
'latex', self.songbook = Songbook(raw_songbook, basename)
) # Basename of the songbook to be built.
self.basename = basename
# pdflatex options # logging object to use
pdflatex_options = [] self.logger = logger
pdflatex_options.append("--shell-escape") # Lilypond compilation
if not interactive: def _run_once(self, function, *args, **kwargs):
pdflatex_options.append("-halt-on-error") """Run function if it has not been run yet.
# Compilation If it as, return the previous return value.
for step in steps: """
if step == 'tex': if function not in self._called_functions:
# Building .tex file from templates self._called_functions[function] = function(*args, **kwargs)
with codecs.open("{}.tex".format(basename), 'w', 'utf-8') as output: return self._called_functions[function]
songbook.write_tex(output)
elif step == 'pdf': def _set_latex(self):
if subprocess.call(["pdflatex"] + pdflatex_options + [basename]): """Set TEXINPUTS and LaTeX options."""
raise errors.LatexCompilationError(basename) if not 'TEXINPUTS' in os.environ.keys():
elif step == 'sbx': os.environ['TEXINPUTS'] = ''
# Make index os.environ['TEXINPUTS'] += os.pathsep + os.path.join(
sxd_files = glob.glob("%s_*.sxd" % basename) __DATADIR__,
for sxd_file in sxd_files: 'latex',
logger.info("processing " + sxd_file) )
idx = process_sxd(sxd_file) os.environ['TEXINPUTS'] += os.pathsep + os.path.join(
index_file = open(sxd_file[:-3] + "sbx", "w") self.songbook.config['datadir'],
'latex',
)
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"""
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"""
self._run_once(self._set_latex)
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):
"""Make index"""
sxd_files = glob.glob("%s_*.sxd" % self.basename)
for sxd_file in sxd_files:
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')) index_file.write(idx.entries_to_str().encode('utf8'))
index_file.close()
elif step == 'clean': @staticmethod
# Cleaning def build_custom(command):
clean(basename) """Run a shell command"""
elif step.startswith("%"): exit_code = call(command, shell=True)
# Shell command if exit_code:
command = step[1:] raise errors.StepCommandError(command, exit_code)
exit_code = subprocess.call(command, shell=True)
if exit_code: def clean(self):
raise errors.StepCommandError(command, exit_code) """Clean (some) temporary files used during compilation.
else:
# Unknown step name Depending of the LaTeX modules used in the template, there may be others
raise errors.UnknownStep(step) 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)

2
songbook_core/errors.py

@ -13,7 +13,7 @@ class SongbookError(Exception):
class TemplateError(SongbookError): class TemplateError(SongbookError):
"""Error during template generation""" """Error during template generation"""
def __init__(self, original, message = None): def __init__(self, original, message=None):
super(TemplateError, self).__init__() super(TemplateError, self).__init__()
self.original = original self.original = original
self.message = message self.message = message

Loading…
Cancel
Save