mirror of https://github.com/patacrep/patacrep.git
Engine for LaTeX songbooks
http://www.patacrep.com
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.2 KiB
291 lines
9.2 KiB
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Build a songbook, according to parameters found in a .sb file."""
|
|
|
|
import codecs
|
|
import glob
|
|
import json
|
|
import logging
|
|
import os.path
|
|
import re
|
|
import subprocess
|
|
|
|
from songbook_core import __SHAREDIR__
|
|
from songbook_core import errors
|
|
from songbook_core.files import recursive_find
|
|
from songbook_core.index import process_sxd
|
|
from songbook_core.songs import Song, SongsList
|
|
|
|
EOL = "\n"
|
|
|
|
|
|
def parse_template(template):
|
|
"""Return the list of parameters defined in the template."""
|
|
embedded_json_pattern = re.compile(r"^%%:")
|
|
with open(template) as template_file:
|
|
code = [
|
|
line[3:-1]
|
|
for line
|
|
in template_file
|
|
if embedded_json_pattern.match(line)
|
|
]
|
|
|
|
data = json.loads(''.join(code))
|
|
parameters = dict()
|
|
for param in data:
|
|
parameters[param["name"]] = param
|
|
return parameters
|
|
|
|
|
|
# pylint: disable=too-many-return-statements
|
|
def to_value(parameter, data):
|
|
"""Convert 'data' to a LaTeX string.
|
|
|
|
Conversion is done according to the template parameter it corresponds to.
|
|
"""
|
|
if "type" not in parameter:
|
|
return data
|
|
elif parameter["type"] == "stringlist":
|
|
if "join" in parameter:
|
|
join_text = parameter["join"]
|
|
else:
|
|
join_text = ''
|
|
return join_text.join(data)
|
|
elif parameter["type"] == "color":
|
|
return data[1:]
|
|
elif parameter["type"] == "font":
|
|
return data + 'pt'
|
|
elif parameter["type"] == "enum":
|
|
return data
|
|
elif parameter["type"] == "file":
|
|
return data
|
|
elif parameter["type"] == "flag":
|
|
if "join" in parameter:
|
|
join_text = parameter["join"]
|
|
else:
|
|
join_text = ''
|
|
return join_text.join(data)
|
|
|
|
|
|
def format_declaration(name, parameter):
|
|
"""Write LaTeX code to declare a variable"""
|
|
value = ""
|
|
if "default" in parameter:
|
|
value = parameter["default"]
|
|
return (
|
|
r'\def\set@{name}#1{{\def\get{name}{{#1}}}}'.format(name=name)
|
|
+ EOL
|
|
+ format_definition(name, to_value(parameter, value))
|
|
)
|
|
|
|
|
|
def format_definition(name, value):
|
|
"""Write LaTeX code to set a value to a variable"""
|
|
return r'\set@{name}{{{value}}}'.format(name=name, value=value) + EOL
|
|
|
|
|
|
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 note deleted by this function."""
|
|
generated_extensions = [
|
|
"_auth.sbx",
|
|
"_auth.sxd",
|
|
".aux",
|
|
".log",
|
|
".out",
|
|
".sxc",
|
|
".tex",
|
|
"_title.sbx",
|
|
"_title.sxd",
|
|
]
|
|
|
|
for ext in generated_extensions:
|
|
try:
|
|
os.unlink(basename + ext)
|
|
except Exception as exception:
|
|
raise errors.CleaningError(basename + ext, exception)
|
|
|
|
|
|
def make_tex_file(songbook, output):
|
|
"""Create the LaTeX file corresponding to the .sb file given in argument."""
|
|
datadir = songbook['datadir']
|
|
name = output[:-4]
|
|
template_dir = os.path.join(datadir, 'templates')
|
|
songs = []
|
|
|
|
prefixes_tex = ""
|
|
prefixes = []
|
|
|
|
authwords_tex = ""
|
|
authwords = {"after": ["by"], "ignore": ["unknown"], "sep": ["and"]}
|
|
|
|
# parse the songbook data
|
|
if "template" in songbook:
|
|
template = songbook["template"]
|
|
del songbook["template"]
|
|
else:
|
|
template = os.path.join(__SHAREDIR__, "templates", "default.tmpl")
|
|
if "songs" in songbook:
|
|
songs = songbook["songs"]
|
|
del songbook["songs"]
|
|
if "titleprefixwords" in songbook:
|
|
prefixes = songbook["titleprefixwords"]
|
|
for prefix in songbook["titleprefixwords"]:
|
|
prefixes_tex += r"\titleprefixword{%s}" % prefix + EOL
|
|
songbook["titleprefixwords"] = prefixes_tex
|
|
if "authwords" in songbook:
|
|
# Populating default value
|
|
for key in ["after", "sep", "ignore"]:
|
|
if key not in songbook["authwords"]:
|
|
songbook["authwords"][key] = authwords[key]
|
|
# Processing authwords values
|
|
authwords = songbook["authwords"]
|
|
for key in ["after", "sep", "ignore"]:
|
|
for word in authwords[key]:
|
|
if key == "after":
|
|
authwords_tex += r"\auth%sword{%s}" % ("by", word) + EOL
|
|
else:
|
|
authwords_tex += r"\auth%sword{%s}" % (key, word) + EOL
|
|
songbook["authwords"] = authwords_tex
|
|
if "after" in authwords:
|
|
authwords["after"] = [re.compile(r"^.*%s\b(.*)" % after)
|
|
for after in authwords["after"]]
|
|
if "sep" in authwords:
|
|
authwords["sep"] = [" %s" % sep for sep in authwords["sep"]] + [","]
|
|
authwords["sep"] = [re.compile(r"^(.*)%s (.*)$" % sep)
|
|
for sep in authwords["sep"]]
|
|
|
|
if "lang" not in songbook:
|
|
songbook["lang"] = "french"
|
|
if "sort" in songbook:
|
|
sort = songbook["sort"]
|
|
del songbook["sort"]
|
|
else:
|
|
sort = [u"by", u"album", u"@title"]
|
|
Song.sort = sort
|
|
Song.prefixes = prefixes
|
|
Song.authwords = authwords
|
|
|
|
parameters = parse_template(os.path.join(template_dir, template))
|
|
|
|
# compute songslist
|
|
if songs == "all":
|
|
songs = [
|
|
os.path.relpath(filename, os.path.join(datadir, 'songs'))
|
|
for filename
|
|
in recursive_find(os.path.join(datadir, 'songs'), '*.sg')
|
|
]
|
|
songslist = SongsList(datadir, songbook["lang"])
|
|
songslist.append_list(songs)
|
|
|
|
songbook["languages"] = ",".join(songslist.languages())
|
|
|
|
# output relevant fields
|
|
out = codecs.open(output, 'w', 'utf-8')
|
|
out.write('%% This file has been automatically generated, do not edit!\n')
|
|
out.write(r'\makeatletter' + EOL)
|
|
# output automatic parameters
|
|
out.write(format_declaration("name", {"default": name}))
|
|
out.write(format_declaration("songslist", {"type": "stringlist"}))
|
|
# output template parameter command
|
|
for name, parameter in parameters.iteritems():
|
|
out.write(format_declaration(name, parameter))
|
|
# output template parameter values
|
|
for name, value in songbook.iteritems():
|
|
if name in parameters:
|
|
out.write(format_definition(
|
|
name,
|
|
to_value(parameters[name], value),
|
|
))
|
|
|
|
if len(songs) > 0:
|
|
out.write(format_definition('songslist', songslist.latex()))
|
|
out.write(r'\makeatother' + EOL)
|
|
|
|
# output template
|
|
comment_pattern = re.compile(r"^\s*%")
|
|
with codecs.open(
|
|
os.path.join(template_dir, template), 'r', 'utf-8'
|
|
) as template_file:
|
|
content = [
|
|
line
|
|
for line
|
|
in template_file
|
|
if not comment_pattern.match(line)
|
|
]
|
|
|
|
for index, line in enumerate(content):
|
|
if re.compile("getDataImgDirectory").search(line):
|
|
if os.path.abspath(os.path.join(datadir, "img")).startswith(
|
|
os.path.abspath(os.path.dirname(output))
|
|
):
|
|
imgdir = os.path.relpath(
|
|
os.path.join(datadir, "img"),
|
|
os.path.dirname(output)
|
|
)
|
|
else:
|
|
imgdir = os.path.abspath(os.path.join(datadir, "img"))
|
|
line = line.replace(r"\getDataImgDirectory", ' {%s/} ' % imgdir)
|
|
content[index] = line
|
|
|
|
out.write(u''.join(content))
|
|
out.close()
|
|
|
|
def buildsongbook(
|
|
songbook,
|
|
basename,
|
|
interactive=False,
|
|
logger=logging.getLogger()
|
|
):
|
|
"""Build a songbook
|
|
|
|
Arguments:
|
|
- songbook: Python representation of the .sb songbook configuration file.
|
|
- basename: basename of the songbook to be built.
|
|
- interactive: in False, do not expect anything from stdin.
|
|
"""
|
|
|
|
tex_file = basename + ".tex"
|
|
|
|
# Make TeX file
|
|
make_tex_file(songbook, tex_file)
|
|
|
|
if not 'TEXINPUTS' in os.environ.keys():
|
|
os.environ['TEXINPUTS'] = ''
|
|
os.environ['TEXINPUTS'] += os.pathsep + os.path.join(
|
|
__SHAREDIR__,
|
|
'latex',
|
|
)
|
|
os.environ['TEXINPUTS'] += os.pathsep + os.path.join(
|
|
songbook['datadir'],
|
|
'latex',
|
|
)
|
|
|
|
# pdflatex options
|
|
pdflatex_options = []
|
|
pdflatex_options.append("--shell-escape") # Lilypond compilation
|
|
if not interactive:
|
|
pdflatex_options.append("-halt-on-error")
|
|
|
|
# First pdflatex pass
|
|
if subprocess.call(["pdflatex"] + pdflatex_options + [tex_file]):
|
|
raise errors.LatexCompilationError(basename)
|
|
|
|
# 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")
|
|
index_file.write(idx.entries_to_str().encode('utf8'))
|
|
index_file.close()
|
|
|
|
# Second pdflatex pass
|
|
if subprocess.call(["pdflatex"] + pdflatex_options + [tex_file]):
|
|
raise errors.LatexCompilationError(basename)
|
|
|
|
# Cleaning
|
|
clean(basename)
|
|
|