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 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
@ -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
@ -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)

258
songbook_core/build.py

@ -5,10 +5,9 @@
import codecs
import glob
import logging
import os.path
import re
import subprocess
from subprocess import Popen, PIPE, call
from songbook_core import __DATADIR__
from songbook_core import errors
@ -24,6 +23,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
@ -47,9 +58,10 @@ class Songbook(object):
'datadir': os.path.abspath('.'),
}
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.
Argument:
@ -70,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
@ -78,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'] = [
@ -92,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'],
@ -123,102 +139,134 @@ 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."""
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 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=None):
# 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):
"""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 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.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 = 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)

2
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

Loading…
Cancel
Save