mirror of https://github.com/patacrep/patacrep.git
Luthaf
10 years ago
35 changed files with 1165 additions and 928 deletions
@ -0,0 +1,2 @@ |
|||
include LICENSE NEWS readme.md Requirements.txt |
|||
recursive-include patacrep/data * |
@ -0,0 +1,6 @@ |
|||
\selectlanguage{french} |
|||
\sortassong{}[by={QQ}] |
|||
\begin{intersong} |
|||
|
|||
Lorem ipsum |
|||
\end{intersong} |
@ -0,0 +1,21 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Very simple LaTeX parser |
|||
|
|||
This module uses an LALR parser to try to parse LaTeX code. LaTeX language |
|||
*cannot* be parsed by an LALR parser, so this is a very simple attemps, which |
|||
will work on simple cases, but not on complex ones. |
|||
""" |
|||
|
|||
from patacrep.latex.syntax import tex2plain |
|||
from patacrep.latex.syntax import parsesong as syntax_parsesong |
|||
from patacrep import encoding |
|||
|
|||
def parsesong(path, fileencoding=None): |
|||
"""Return a dictonary of data read from the latex file `path`. |
|||
|
|||
""" |
|||
with encoding.open_read(path, encoding=fileencoding) as songfile: |
|||
data = syntax_parsesong(songfile.read(), path) |
|||
data['@path'] = path |
|||
return data |
@ -0,0 +1,65 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Abstract Syntax Tree for LaTeX code.""" |
|||
|
|||
# pylint: disable=too-few-public-methods |
|||
|
|||
class AST: |
|||
"""Base class for the tree.""" |
|||
# pylint: disable=no-init |
|||
|
|||
metadata = None |
|||
|
|||
@classmethod |
|||
def init_metadata(cls): |
|||
"""Clear metadata |
|||
|
|||
As this attribute is a class attribute, it as to be reset at each new |
|||
parsing. |
|||
""" |
|||
cls.metadata = { |
|||
'@languages': set(), |
|||
} |
|||
|
|||
class Expression(AST): |
|||
"""LaTeX expression""" |
|||
|
|||
def __init__(self, value): |
|||
super().__init__() |
|||
self.content = [value] |
|||
|
|||
def prepend(self, value): |
|||
"""Add a value at the beginning of the content list.""" |
|||
if value is not None: |
|||
self.content.insert(0, value) |
|||
return self |
|||
|
|||
def __str__(self): |
|||
return "".join([str(item) for item in self.content]) |
|||
|
|||
class Command(AST): |
|||
"""LaTeX command""" |
|||
|
|||
def __init__(self, name, optional, mandatory): |
|||
self.name = name |
|||
self.mandatory = mandatory |
|||
self.optional = optional |
|||
|
|||
if name == r'\selectlanguage': |
|||
self.metadata['@languages'] |= set(self.mandatory) |
|||
|
|||
def __str__(self): |
|||
if self.name in [r'\emph']: |
|||
return str(self.mandatory[0]) |
|||
return "{}{}{}".format( |
|||
self.name, |
|||
"".join(["[{}]".format(item) for item in self.optional]), |
|||
"".join(["{{{}}}".format(item) for item in self.mandatory]), |
|||
) |
|||
|
|||
|
|||
class BeginSong(AST): |
|||
"""Beginsong command""" |
|||
|
|||
def __init__(self, titles, arguments): |
|||
self.titles = titles |
|||
self.arguments = arguments |
@ -0,0 +1,123 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Render `very simple` TeX commands in a simple TeX code.""" |
|||
|
|||
import logging |
|||
|
|||
LOGGER = logging.getLogger() |
|||
|
|||
MATCH = [ |
|||
# Diacritics: a |
|||
(r"\'a", "á"), |
|||
(r"\'A", "Á"), |
|||
(r"\`a", "à"), |
|||
(r"\`A", "À"), |
|||
(r"\^a", "â"), |
|||
(r"\^A", "Â"), |
|||
(r"\"a", "ä"), |
|||
(r"\"A", "Ä"), |
|||
|
|||
# Diacritics: e |
|||
(r"\'e", "é"), |
|||
(r"\'E", "É"), |
|||
(r"\`e", "è"), |
|||
(r"\`E", "È"), |
|||
(r"\^e", "ê"), |
|||
(r"\^E", "Ê"), |
|||
(r"\"e", "ë"), |
|||
(r"\"E", "Ë"), |
|||
|
|||
# Diacritics: i |
|||
(r"\'i", "í"), |
|||
(r"\'I", "Í"), |
|||
(r"\`i", "ì"), |
|||
(r"\`I", "Ì"), |
|||
(r"\^i", "î"), |
|||
(r"\^I", "Î"), |
|||
(r"\"i", "ï"), |
|||
(r"\"I", "Ï"), |
|||
(r"\'\i", "í"), |
|||
(r"\'\I", "Í"), |
|||
(r"\`\i", "ì"), |
|||
(r"\`\I", "Ì"), |
|||
(r"\^\i", "î"), |
|||
(r"\^\I", "Î"), |
|||
(r"\"\i", "ï"), |
|||
(r"\"\I", "Ï"), |
|||
|
|||
# Diacritics: o |
|||
(r"\'o", "ó"), |
|||
(r"\'O", "Ó"), |
|||
(r"\`o", "ò"), |
|||
(r"\`O", "Ò"), |
|||
(r"\^o", "ô"), |
|||
(r"\^O", "Ô"), |
|||
(r"\"o", "ö"), |
|||
(r"\"O", "Ö"), |
|||
|
|||
# Diacritics: u |
|||
(r"\'u", "ú"), |
|||
(r"\'U", "Ú"), |
|||
(r"\`u", "ù"), |
|||
(r"\`U", "Ù"), |
|||
(r"\^u", "û"), |
|||
(r"\^U", "Û"), |
|||
(r"\"u", "ü"), |
|||
(r"\"U", "Ü"), |
|||
|
|||
# Cedille |
|||
(r"\c c", "ç"), |
|||
(r"\c C", "Ç"), |
|||
|
|||
# œ, æ |
|||
(r"\oe", "œ"), |
|||
(r"\OE", "Œ"), |
|||
(r"\ae", "æ"), |
|||
(r"\AE", "Æ"), |
|||
|
|||
# Spaces |
|||
(r"\ ", " "), |
|||
(r"\,", " "), |
|||
(r"\~", " "), |
|||
|
|||
# IeC |
|||
(r"\IeC ", ""), |
|||
|
|||
# Miscallenous |
|||
(r"\dots", "…"), |
|||
(r"\%", "%"), |
|||
(r"\&", "&"), |
|||
(r"\_", "_"), |
|||
|
|||
] |
|||
|
|||
|
|||
def detex(arg): |
|||
"""Render very simple TeX commands from argument. |
|||
|
|||
Argument can be: |
|||
- a string: it is processed; |
|||
- a list, dict or set: its values are processed. |
|||
""" |
|||
if isinstance(arg, dict): |
|||
return dict([ |
|||
(key, detex(value)) |
|||
for (key, value) |
|||
in arg.items() |
|||
]) |
|||
elif isinstance(arg, list): |
|||
return [ |
|||
detex(item) |
|||
for item |
|||
in arg |
|||
] |
|||
elif isinstance(arg, set): |
|||
return set(detex(list(arg))) |
|||
elif isinstance(arg, str): |
|||
string = arg |
|||
for (latex, plain) in MATCH: |
|||
string = string.replace(latex, plain) |
|||
if '\\' in string: |
|||
LOGGER.warning("Remaining command in string '{}'.".format(string)) |
|||
return string.strip() |
|||
else: |
|||
return detex(str(arg)) |
@ -0,0 +1,151 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Very simple LaTeX lexer.""" |
|||
|
|||
import logging |
|||
import ply.lex as lex |
|||
|
|||
LOGGER = logging.getLogger() |
|||
|
|||
#pylint: disable=invalid-name |
|||
tokens = ( |
|||
'LBRACKET', |
|||
'RBRACKET', |
|||
'LBRACE', |
|||
'RBRACE', |
|||
'COMMAND', |
|||
'NEWLINE', |
|||
'COMMA', |
|||
'EQUAL', |
|||
'CHARACTER', |
|||
'SPACE', |
|||
'BEGINSONG', |
|||
'SONG_LTITLE', |
|||
'SONG_RTITLE', |
|||
'SONG_LOPTIONS', |
|||
'SONG_ROPTIONS', |
|||
) |
|||
|
|||
class SimpleLexer: |
|||
"""Very simple LaTeX lexer.""" |
|||
|
|||
tokens = tokens |
|||
|
|||
t_LBRACKET = r'\[' |
|||
t_RBRACKET = r'\]' |
|||
t_LBRACE = r'{' |
|||
t_RBRACE = r'}' |
|||
t_COMMAND = r'\\([@a-zA-Z]+|[^\\])' |
|||
t_NEWLINE = r'\\\\' |
|||
SPECIAL_CHARACTERS = ( |
|||
t_LBRACKET + |
|||
t_RBRACKET + |
|||
t_RBRACE + |
|||
t_LBRACE + |
|||
r"\\" + |
|||
r" " + |
|||
r"\n" + |
|||
r"\r" + |
|||
r"%" + |
|||
r"=" + |
|||
r"," |
|||
) |
|||
t_CHARACTER = r'[^{}]'.format(SPECIAL_CHARACTERS) |
|||
t_EQUAL = r'=' |
|||
t_COMMA = r',' |
|||
|
|||
t_SPACE = r'[ \t\n\r]+' |
|||
|
|||
def __init__(self): |
|||
self.__class__.lexer = lex.lex(module=self) |
|||
|
|||
# Define a rule so we can track line numbers |
|||
@staticmethod |
|||
def t_newline(token): |
|||
r'\n+' |
|||
token.lexer.lineno += len(token.value) |
|||
|
|||
@staticmethod |
|||
def t_comment(token): |
|||
r'%.*' |
|||
pass |
|||
|
|||
# Error handling rule |
|||
@staticmethod |
|||
def t_error(token): |
|||
"""Manage errors""" |
|||
LOGGER.error("Illegal character '{}'".format(token.value[0])) |
|||
token.lexer.skip(1) |
|||
|
|||
class SongLexer(SimpleLexer): |
|||
r"""Very simple song lexer. |
|||
|
|||
In the context of this class, a "song" is some LaTeX code containing the |
|||
``\beginsong`` (or ``\sortassong``) command. |
|||
""" |
|||
|
|||
states = ( |
|||
('beginsong', 'inclusive'), |
|||
) |
|||
|
|||
# State beginsong |
|||
@staticmethod |
|||
def t_INITIAL_BEGINSONG(token): |
|||
r'(\\beginsong|\\sortassong)' |
|||
token.lexer.push_state('beginsong') |
|||
token.lexer.open_brackets = 0 |
|||
token.lexer.open_braces = 0 |
|||
return token |
|||
|
|||
@staticmethod |
|||
def t_beginsong_LBRACKET(token): |
|||
r'\[' |
|||
if token.lexer.open_brackets == 0: |
|||
token.type = 'SONG_LOPTIONS' |
|||
|
|||
# Count opening and closing braces to know when to leave the |
|||
# `beginsong` state. |
|||
token.lexer.open_braces += 1 |
|||
token.lexer.open_brackets += 1 |
|||
return token |
|||
|
|||
@staticmethod |
|||
def t_beginsong_RBRACKET(token): |
|||
r'\]' |
|||
token.lexer.open_brackets -= 1 |
|||
if token.lexer.open_brackets == 0: |
|||
token.type = 'SONG_ROPTIONS' |
|||
token.lexer.open_braces -= 1 |
|||
token.lexer.pop_state() |
|||
for __ignored in token.lexer: |
|||
# In this parser, we only want to read metadata. So, after the |
|||
# first ``\beginsong`` command, we can stop parsing. |
|||
pass |
|||
return token |
|||
|
|||
@staticmethod |
|||
def t_beginsong_LBRACE(token): |
|||
r'{' |
|||
if token.lexer.open_braces == 0: |
|||
token.type = 'SONG_LTITLE' |
|||
token.lexer.open_braces += 1 |
|||
return token |
|||
|
|||
@staticmethod |
|||
def t_beginsong_RBRACE1(token): |
|||
r'}(?![ \t\r\n]*\[)' |
|||
token.lexer.open_braces -= 1 |
|||
token.type = 'RBRACE' |
|||
if token.lexer.open_braces == 0: |
|||
token.lexer.pop_state() |
|||
token.type = 'SONG_RTITLE' |
|||
return token |
|||
|
|||
@staticmethod |
|||
def t_beginsong_RBRACE2(token): |
|||
r'}(?=[ \t\r\n]*\[)' |
|||
token.lexer.open_braces -= 1 |
|||
token.type = 'RBRACE' |
|||
if token.lexer.open_braces == 0: |
|||
token.type = 'SONG_RTITLE' |
|||
return token |
|||
|
@ -0,0 +1,256 @@ |
|||
"""Very simple LaTeX parser""" |
|||
|
|||
import logging |
|||
import ply.yacc as yacc |
|||
|
|||
from patacrep.latex.lexer import tokens, SimpleLexer, SongLexer |
|||
from patacrep.latex import ast |
|||
from patacrep.errors import SongbookError |
|||
from patacrep.latex.detex import detex |
|||
|
|||
LOGGER = logging.getLogger() |
|||
|
|||
class ParsingError(SongbookError): |
|||
"""Parsing error.""" |
|||
|
|||
def __init__(self, message): |
|||
super().__init__(self) |
|||
self.message = message |
|||
|
|||
def __str__(self): |
|||
return self.message |
|||
|
|||
# pylint: disable=line-too-long |
|||
class Parser: |
|||
"""LaTeX parser.""" |
|||
|
|||
def __init__(self, filename=None): |
|||
self.tokens = tokens |
|||
self.ast = ast.AST |
|||
self.ast.init_metadata() |
|||
self.filename = filename |
|||
|
|||
@staticmethod |
|||
def __find_column(token): |
|||
"""Return the column of ``token``.""" |
|||
last_cr = token.lexer.lexdata.rfind('\n', 0, token.lexpos) |
|||
if last_cr < 0: |
|||
last_cr = 0 |
|||
column = (token.lexpos - last_cr) + 1 |
|||
return column |
|||
|
|||
def p_error(self, token): |
|||
"""Manage parsing errors.""" |
|||
LOGGER.error( |
|||
"Error in file {}, line {} at position {}.".format( |
|||
str(self.filename), |
|||
token.lineno, |
|||
self.__find_column(token), |
|||
) |
|||
) |
|||
|
|||
@staticmethod |
|||
def p_expression(symbols): |
|||
"""expression : brackets expression |
|||
| braces expression |
|||
| command expression |
|||
| NEWLINE expression |
|||
| beginsong expression |
|||
| word expression |
|||
| SPACE expression |
|||
| empty |
|||
""" |
|||
if len(symbols) == 3: |
|||
if symbols[2] is None: |
|||
symbols[0] = ast.Expression(symbols[1]) |
|||
else: |
|||
symbols[0] = symbols[2].prepend(symbols[1]) |
|||
else: |
|||
symbols[0] = None |
|||
|
|||
@staticmethod |
|||
def p_empty(__symbols): |
|||
"""empty :""" |
|||
return None |
|||
|
|||
@staticmethod |
|||
def p_brackets(symbols): |
|||
"""brackets : LBRACKET expression RBRACKET""" |
|||
symbols[0] = symbols[2] |
|||
|
|||
@staticmethod |
|||
def p_braces(symbols): |
|||
"""braces : LBRACE expression RBRACE""" |
|||
symbols[0] = symbols[2] |
|||
|
|||
@staticmethod |
|||
def p_command(symbols): |
|||
"""command : COMMAND brackets_list braces_list""" |
|||
symbols[0] = ast.Command(symbols[1], symbols[2], symbols[3]) |
|||
|
|||
@staticmethod |
|||
def p_brackets_list(symbols): |
|||
"""brackets_list : brackets brackets_list |
|||
| empty |
|||
""" |
|||
if len(symbols) == 3: |
|||
symbols[0] = symbols[2] |
|||
symbols[0].insert(0, symbols[1]) |
|||
else: |
|||
symbols[0] = [] |
|||
|
|||
@staticmethod |
|||
def p_braces_list(symbols): |
|||
"""braces_list : braces braces_list |
|||
| empty |
|||
""" |
|||
if len(symbols) == 3: |
|||
symbols[0] = symbols[2] |
|||
symbols[0].insert(0, symbols[1]) |
|||
else: |
|||
symbols[0] = [] |
|||
|
|||
@staticmethod |
|||
def p_word(symbols): |
|||
"""word : CHARACTER word_next |
|||
| COMMA word_next |
|||
| EQUAL word_next |
|||
""" |
|||
symbols[0] = symbols[1] + symbols[2] |
|||
|
|||
@staticmethod |
|||
def p_word_next(symbols): |
|||
"""word_next : CHARACTER word_next |
|||
| empty |
|||
""" |
|||
if len(symbols) == 2: |
|||
symbols[0] = "" |
|||
else: |
|||
symbols[0] = symbols[1] + symbols[2] |
|||
|
|||
def p_beginsong(self, symbols): |
|||
"""beginsong : BEGINSONG separator songbraces separator songbrackets""" |
|||
self.ast.metadata["@titles"] = symbols[3] |
|||
self.ast.metadata.update(symbols[5]) |
|||
|
|||
@staticmethod |
|||
def p_songbrackets(symbols): |
|||
"""songbrackets : SONG_LOPTIONS separator dictionary separator SONG_ROPTIONS |
|||
| empty |
|||
""" |
|||
if len(symbols) == 6: |
|||
symbols[0] = symbols[3] |
|||
else: |
|||
symbols[0] = {} |
|||
|
|||
@staticmethod |
|||
def p_songbraces(symbols): |
|||
"""songbraces : SONG_LTITLE separator titles separator SONG_RTITLE |
|||
| empty |
|||
""" |
|||
if len(symbols) == 6: |
|||
symbols[0] = symbols[3] |
|||
else: |
|||
symbols[0] = [] |
|||
|
|||
@staticmethod |
|||
def p_dictionary(symbols): |
|||
"""dictionary : identifier EQUAL braces dictionary_next |
|||
| identifier EQUAL error dictionary_next |
|||
""" |
|||
if isinstance(symbols[3], ast.Expression): |
|||
symbols[0] = {} |
|||
symbols[0][symbols[1]] = symbols[3] |
|||
symbols[0].update(symbols[4]) |
|||
else: |
|||
raise ParsingError("Do enclose arguments between braces.") |
|||
|
|||
@staticmethod |
|||
def p_identifier(symbols): |
|||
"""identifier : CHARACTER identifier |
|||
| empty |
|||
""" |
|||
if len(symbols) == 2: |
|||
symbols[0] = "" |
|||
else: |
|||
symbols[0] = symbols[1] + symbols[2] |
|||
|
|||
@staticmethod |
|||
def p_separator(symbols): |
|||
"""separator : SPACE |
|||
| empty |
|||
""" |
|||
symbols[0] = None |
|||
|
|||
@staticmethod |
|||
def p_dictonary_next(symbols): |
|||
"""dictionary_next : separator COMMA separator dictionary |
|||
| empty |
|||
""" |
|||
if len(symbols) == 5: |
|||
symbols[0] = symbols[4] |
|||
else: |
|||
symbols[0] = {} |
|||
|
|||
@staticmethod |
|||
def p_titles(symbols): |
|||
"""titles : title titles_next""" |
|||
symbols[0] = [symbols[1]] + symbols[2] |
|||
|
|||
@staticmethod |
|||
def p_titles_next(symbols): |
|||
"""titles_next : NEWLINE title titles_next |
|||
| empty |
|||
""" |
|||
if len(symbols) == 2: |
|||
symbols[0] = [] |
|||
else: |
|||
symbols[0] = [symbols[2]] + symbols[3] |
|||
|
|||
@staticmethod |
|||
def p_title(symbols): |
|||
"""title : brackets title |
|||
| braces title |
|||
| command title |
|||
| word title |
|||
| SPACE title |
|||
| empty |
|||
""" |
|||
if len(symbols) == 2: |
|||
symbols[0] = None |
|||
else: |
|||
if symbols[2] is None: |
|||
symbols[0] = ast.Expression(symbols[1]) |
|||
else: |
|||
symbols[0] = symbols[2].prepend(symbols[1]) |
|||
|
|||
def silent_yacc(*args, **kwargs): |
|||
"""Call yacc, suppressing (as far as possible) output and generated files. |
|||
""" |
|||
return yacc.yacc( |
|||
write_tables=0, |
|||
debug=0, |
|||
*args, |
|||
**kwargs |
|||
) |
|||
|
|||
def tex2plain(string): |
|||
"""Parse string and return its plain text version.""" |
|||
return detex( |
|||
silent_yacc( |
|||
module=Parser(), |
|||
).parse( |
|||
string, |
|||
lexer=SimpleLexer().lexer, |
|||
) |
|||
) |
|||
|
|||
def parsesong(string, filename=None): |
|||
"""Parse song and return its metadata.""" |
|||
return detex( |
|||
silent_yacc(module=Parser(filename)).parse( |
|||
string, |
|||
lexer=SongLexer().lexer, |
|||
).metadata |
|||
) |
|||
|
@ -1,117 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""PlasTeX module to process song files.""" |
|||
|
|||
from plasTeX.TeX import TeX |
|||
from plasTeX.Base.LaTeX import Sentences |
|||
|
|||
import locale |
|||
import os |
|||
import sys |
|||
|
|||
from patacrep import encoding |
|||
|
|||
def process_unbr_spaces(node): |
|||
#pylint: disable=line-too-long |
|||
r"""Replace '~' and '\ ' in node by nodes that |
|||
will be rendered as unbreakable space. |
|||
|
|||
Return node object for convenience. |
|||
|
|||
This function is a workaround to a bug that has been solved since: |
|||
- https://github.com/tiarno/plastex/commit/76bb78d5fbaac48e68025a3545286cc63cb4e7ad |
|||
- https://github.com/tiarno/plastex/commit/682a0d223b99d6b949bacf1c974d24dc9bb1d18e |
|||
|
|||
It can be deleted once this bug has been merged in production version of |
|||
PlasTeX. |
|||
""" |
|||
if (type(node) == Sentences.InterWordSpace or |
|||
(type(node) == Sentences.NoLineBreak and node.source == '~ ')): |
|||
node.unicode = unichr(160) |
|||
for child in node.childNodes: |
|||
process_unbr_spaces(child) |
|||
|
|||
return node |
|||
|
|||
|
|||
def simpleparse(text): |
|||
"""Parse a simple LaTeX string. |
|||
""" |
|||
tex = TeX() |
|||
tex.disableLogging() |
|||
tex.input(text) |
|||
doc = tex.parse() |
|||
return process_unbr_spaces(doc.textContent) |
|||
|
|||
|
|||
class SongParser(object): |
|||
"""Analyseur syntaxique de fichiers .sg""" |
|||
|
|||
@staticmethod |
|||
def create_tex(): |
|||
"""Create a TeX object, ready to parse a tex file.""" |
|||
tex = TeX() |
|||
tex.disableLogging() |
|||
tex.ownerDocument.context.loadBaseMacros() |
|||
sys.path.append(os.path.dirname(__file__)) |
|||
tex.ownerDocument.context.loadPackage(tex, "plastex_patchedbabel") |
|||
tex.ownerDocument.context.loadPackage(tex, "plastex_chord") |
|||
tex.ownerDocument.context.loadPackage(tex, "plastex_songs") |
|||
tex.ownerDocument.context.loadPackage(tex, "plastex_misc_commands") |
|||
sys.path.pop() |
|||
return tex |
|||
|
|||
@classmethod |
|||
def parse(cls, filename): |
|||
"""Parse a TeX file, and return its plasTeX representation.""" |
|||
tex = cls.create_tex() |
|||
tex.input(encoding.open_read(filename, 'r')) |
|||
return tex.parse() |
|||
|
|||
|
|||
def parsetex(filename): |
|||
r"""Analyse syntaxique d'un fichier .sg |
|||
|
|||
Renvoie un dictionnaire contenant les métadonnées lues dans le fichier. Les |
|||
clefs sont : |
|||
- languages: l'ensemble des langages utilisés (recherche des |
|||
\selectlanguages{}) ; |
|||
- titles: la liste des titres ; |
|||
- args: le dictionnaire des paramètres passés à \beginsong. |
|||
""" |
|||
# /* BEGIN plasTeX patch |
|||
# The following lines, and another line a few lines later, are used to |
|||
# circumvent a plasTeX bug. It has been reported and corrected : |
|||
# https://github.com/tiarno/plastex/commit/8f4e5a385f3cb6a04d5863f731ce24a7e856f2a4 |
|||
# To see if you can delete those lines, set your LC_TIME locale to French, |
|||
# during a month containing diacritics (e.g. Février), and run songbook. If |
|||
# no plasTeX bug appears, it is safe to remove those lines. |
|||
oldlocale = locale.getlocale(locale.LC_TIME) |
|||
locale.setlocale(locale.LC_TIME, 'C') |
|||
# plasTeX patch END */ |
|||
|
|||
# Analyse syntaxique |
|||
doc = SongParser.parse(filename) |
|||
|
|||
# /* BEGIN plasTeX patch |
|||
if oldlocale[0] and oldlocale[1]: |
|||
try: |
|||
locale.setlocale(locale.LC_TIME, "%s.%s" % oldlocale) |
|||
except locale.Error: |
|||
pass # Workaround a bug on windows |
|||
# plasTeX patch END */ |
|||
|
|||
# Extraction des données |
|||
data = { |
|||
"languages": set(), |
|||
"_doc": doc, |
|||
"_filename": filename, |
|||
} |
|||
for node in doc.allChildNodes: |
|||
if node.nodeName == "selectlanguage": |
|||
data["languages"].add(node.attributes['lang']) |
|||
if node.nodeName in ["beginsong", "sortassong"]: |
|||
data["titles"] = node.attributes["titles"] |
|||
data["args"] = node.attributes["args"] |
|||
|
|||
return data |
@ -1,181 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
r"""PlasTeX module to deal with chords commands of the songs LaTeX package |
|||
|
|||
Chords are set using commands like \[C]. This package parses those commands. |
|||
""" |
|||
|
|||
import logging |
|||
|
|||
import plasTeX |
|||
from plasTeX import Command, Environment, Macro |
|||
from plasTeX.Base.LaTeX.Math import BeginDisplayMath |
|||
|
|||
LOGGER = logging.getLogger(__name__) |
|||
|
|||
# Count the number of levels of 'verse' environment: IN_VERSE==1 means that we |
|||
# are in a 'verse' environment; IN_VERSE==2 means that we are in two included |
|||
# 'verse' environment, and so on. |
|||
IN_VERSE = 0 |
|||
|
|||
def wrap_displaymath(cls): |
|||
"""Decorator to store the depth of 'verse' environment |
|||
|
|||
In the invoke() method classes, global variable IN_VERSE indicates the |
|||
number of 'verse' (or 'chorus' or 'verse*') environment we are in. |
|||
""" |
|||
|
|||
# pylint: disable=no-init,too-few-public-methods |
|||
class WrappedClass(cls): |
|||
"""Wrapper to LaTeX environment updating IN_VERSE""" |
|||
blockType = True |
|||
# pylint: disable=super-on-old-class,global-statement,no-member |
|||
def invoke(self, tex): |
|||
"""Wrapper to invoke() to update global variable IN_VERSE.""" |
|||
global IN_VERSE |
|||
if self.macroMode == Macro.MODE_BEGIN: |
|||
self.ownerDocument.context.push() |
|||
self.ownerDocument.context.catcode("\n", 13) |
|||
IN_VERSE += 1 |
|||
|
|||
# Removing spaces and line breaks at the beginning of verse |
|||
token = None |
|||
for token in tex: |
|||
if not match_space(token): |
|||
break |
|||
if token is not None: |
|||
tex.pushToken(token) |
|||
|
|||
else: |
|||
self.ownerDocument.context.pop() |
|||
IN_VERSE -= 1 |
|||
return super(WrappedClass, self).invoke(tex) |
|||
return WrappedClass |
|||
|
|||
# pylint: disable=too-many-public-methods |
|||
@wrap_displaymath |
|||
class Verse(Environment): |
|||
"""LaTeX 'verse' environment""" |
|||
macroName = 'verse' |
|||
|
|||
# pylint: disable=too-many-public-methods |
|||
@wrap_displaymath |
|||
class VerseStar(Environment): |
|||
"""LaTeX 'verse*' environment""" |
|||
macroName = 'verse*' |
|||
|
|||
# pylint: disable=too-many-public-methods |
|||
@wrap_displaymath |
|||
class Chorus(Environment): |
|||
"""LaTeX 'chorus' environment""" |
|||
macroName = 'chorus' |
|||
|
|||
def match_space(token): |
|||
"""Return True if token is a space or newline character.""" |
|||
return ( |
|||
isinstance(token, plasTeX.Tokenizer.Space) |
|||
or token.nodeName == 'active::\n' |
|||
) |
|||
|
|||
def match_closing_square_bracket(token): |
|||
"""Return True if token is character ']'.""" |
|||
return token.nodeType == token.TEXT_NODE and token.nodeValue == ']' |
|||
|
|||
def match_egroup(token): |
|||
"""Return True if token is of type `egroup` (end of group).""" |
|||
return isinstance(token, plasTeX.Base.Text.egroup) #pylint: disable=no-member |
|||
|
|||
def match_space_or_chord(token): |
|||
"""Return True if token is a space or a chord.""" |
|||
return match_space(token) or isinstance(token, Chord) |
|||
|
|||
def parse_until(tex, end=lambda x: False): |
|||
"""Parse `tex` until condition `end`, or `egroup` is met. |
|||
|
|||
Arguments: |
|||
- tex: object to parse |
|||
- end: function taking a token in argument, and returning a boolean. |
|||
Parsing stops when this function returns True, or an `egroup` is met. |
|||
|
|||
Return: a tuple of two items (the list of parsed tokens, last token). This |
|||
is done so that caller can decide whether they want to discard it or not. |
|||
Last token can be None if everything has been parsed without the end |
|||
condition being met. |
|||
""" |
|||
parsed = [] |
|||
last = None |
|||
for token in tex: |
|||
if end(token) or match_egroup(token): |
|||
last = token |
|||
break |
|||
elif isinstance(token, plasTeX.Base.Text.bgroup): #pylint: disable=no-member |
|||
# pylint: disable=expression-not-assigned |
|||
[token.appendChild(item) for item in parse_until(tex, match_egroup)[0]] |
|||
parsed.append(token) |
|||
return (parsed, last) |
|||
|
|||
|
|||
class Chord(Command): |
|||
"""Beginning of a chord notation""" |
|||
macroName = 'chord' |
|||
macroMode = Command.MODE_NONE |
|||
|
|||
class BeginChordOrDisplayMath(BeginDisplayMath): |
|||
r"""Wrapper to BeginDisplayMath |
|||
|
|||
In a 'verse' (or 'verse*' or 'chorus') environment, the '\[' macro |
|||
displays a chord. Otherwise, it corresponds to the usual LaTeX math mode. |
|||
This class calls the right method, depending on the inclusion of this |
|||
macro in a verse environment. |
|||
""" |
|||
macroName = '[' |
|||
|
|||
def invoke(self, tex): |
|||
"""Process this macro""" |
|||
if IN_VERSE: |
|||
chord = Chord() |
|||
|
|||
self.ownerDocument.context.push() #pylint: disable=no-member |
|||
self.ownerDocument.context.catcode("&", 13) #pylint: disable=no-member |
|||
chord.setAttribute( |
|||
'name', |
|||
parse_until(tex, match_closing_square_bracket)[0], |
|||
) |
|||
self.ownerDocument.context.pop() #pylint: disable=no-member |
|||
|
|||
token = next(iter(tex), None) |
|||
if token is None: |
|||
return [chord] |
|||
elif match_space(token): |
|||
return [chord, token] |
|||
elif ( |
|||
isinstance(token, Verse) |
|||
or isinstance(token, VerseStar) |
|||
or isinstance(token, Chorus) |
|||
): |
|||
LOGGER.warning(( |
|||
"{} L{}: '\\end{{verse}}' (or 'verse*' or 'chorus') not " |
|||
"allowed directly after '\\['." |
|||
).format(tex.filename, tex.lineNumber) |
|||
) |
|||
return [chord] |
|||
elif isinstance(token, Chord): |
|||
token.attributes['name'] = ( |
|||
chord.attributes['name'] |
|||
+ token.attributes['name'] |
|||
) |
|||
chord = token |
|||
return [chord] |
|||
elif isinstance(token, plasTeX.Base.Text.bgroup): #pylint: disable=no-member |
|||
# pylint: disable=expression-not-assigned |
|||
[chord.appendChild(item) for item in parse_until(tex)[0]] |
|||
return [chord] |
|||
else: |
|||
chord.appendChild(token) |
|||
(parsed, last) = parse_until(tex, match_space_or_chord) |
|||
# pylint: disable=expression-not-assigned |
|||
[chord.appendChild(item) for item in parsed] |
|||
return [chord, last] |
|||
else: |
|||
return super(BeginChordOrDisplayMath, self).invoke(tex) |
|||
|
@ -1,15 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Quick management of random LaTeX commands.""" |
|||
|
|||
from plasTeX import Command |
|||
|
|||
# pylint: disable=invalid-name,too-many-public-methods |
|||
class songcolumns(Command): |
|||
r"""Manage `\songcolumns` command""" |
|||
args = '{num:int}' |
|||
|
|||
# pylint: disable=invalid-name,too-many-public-methods |
|||
class gtab(Command): |
|||
r"""Manage `\gta` command""" |
|||
args = '{chord:str}{diagram:str}' |
@ -1,58 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
r"""Patch pour le paquet Babel de PlasTeX |
|||
|
|||
Un bug dans PlasTeX intervient lorsqu'on essaye d'analyser une commande LaTeX |
|||
\selectlanguage{}, que nous voulons utiliser ici. Un patch a été proposé aux |
|||
développeurs de plasTeX, et accepté. Mais il faut que cette correction arrive |
|||
en production. En attendant, nous utilisons cette version modifiée. |
|||
|
|||
Dés que la correction sera entrée en production, il faudra supprimer ce |
|||
fichier, et remplater l'occurence à "patchedbabel" par "babel" dans le fichier |
|||
"plastex.py". |
|||
La correction à suveiller est la révision |
|||
41a48c0c229dd46b69fb0e3720595000a71b17d8 du fichier babel.py : |
|||
https://github.com/tiarno/plastex/commit/41a48c0c229dd46b69fb0e3720595000a71b17d8 |
|||
|
|||
# Comment vérifier si on peut supprimer ce fichier ? |
|||
|
|||
1) Remplacer l'occurence à patchedbabel par babel dans le fichier plastex.py. |
|||
|
|||
2) Générer un fichier .tex à partir d'un fichier .sb, ce dernier faisant |
|||
intervenir des chansons dans lesquelles \selectlanguage est utilisé (par |
|||
exemple, "make -B matteo.tex" ou "make -B naheulbeuk.tex" pour des fichiers pas |
|||
trop gros. |
|||
|
|||
3) Si l'erreur suivante apparaît, c'est qu'il faut encore attendre. |
|||
|
|||
> Traceback (most recent call last): |
|||
> [...] |
|||
> File "/usr/lib/pymodules/python2.7/plasTeX/Packages/babel.py", line 18, in |
|||
> invoke context.loadLanguage(self.attributes['lang'], self.ownerDocument) |
|||
> NameError: global name 'context' is not defined |
|||
|
|||
3 bis) Si elle n'apparait pas : youpi ! Supprimez ce fichier ! |
|||
|
|||
# Contact et commentaires |
|||
|
|||
Mercredi 27 mars 2013 |
|||
Louis <spalax(at)gresille.org> |
|||
|
|||
""" |
|||
|
|||
from plasTeX import Command |
|||
|
|||
# pylint: disable=invalid-name,too-many-public-methods |
|||
class selectlanguage(Command): |
|||
"""Patch of vanilla selectlanguage class. |
|||
|
|||
See module docstring for more information.""" |
|||
args = 'lang:str' |
|||
|
|||
def invoke(self, tex): |
|||
res = Command.invoke(self, tex) |
|||
self.ownerDocument.context.loadLanguage( # pylint: disable=no-member |
|||
self.attributes['lang'], |
|||
self.ownerDocument |
|||
) |
|||
return res |
@ -1,70 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Module to process song LaTeX environment. |
|||
""" |
|||
|
|||
import plasTeX |
|||
|
|||
from patacrep import encoding |
|||
from patacrep.plastex import process_unbr_spaces |
|||
|
|||
|
|||
def split_linebreak(texlist): |
|||
"""Return a list of alternative title. |
|||
|
|||
A title can be defined with alternative names : |
|||
|
|||
A real name\\ |
|||
Alternative name\\ |
|||
Another alternative name |
|||
|
|||
This function takes the object representation of a list of titles, and |
|||
return a list of titles. |
|||
""" |
|||
return_list = [] |
|||
current = [] |
|||
for token in texlist: |
|||
if token.nodeName == '\\': |
|||
return_list.append(current) |
|||
current = [] |
|||
else: |
|||
current.append(encoding.basestring2unicode( |
|||
process_unbr_spaces(token).textContent |
|||
)) |
|||
if current: |
|||
return_list.append(current) |
|||
return return_list |
|||
|
|||
|
|||
class beginsong(plasTeX.Command): # pylint: disable=invalid-name,too-many-public-methods |
|||
"""Class parsing the LaTeX song environment.""" |
|||
|
|||
args = '{titles}[args:dict]' |
|||
|
|||
def invoke(self, tex): |
|||
"""Parse an occurence of song environment.""" |
|||
|
|||
plasTeX.Command.invoke(self, tex) |
|||
|
|||
# Parsing title |
|||
titles = [] |
|||
for tokens in split_linebreak(self.attributes['titles'].allChildNodes): |
|||
titles.append("".join(tokens)) |
|||
self.attributes['titles'] = encoding.list2unicode(titles) |
|||
|
|||
# Parsing keyval arguments |
|||
args = {} |
|||
for (key, val) in self.attributes['args'].iteritems(): |
|||
if isinstance(val, plasTeX.DOM.Element): |
|||
args[key] = encoding.basestring2unicode( |
|||
process_unbr_spaces(val).textContent |
|||
) |
|||
elif isinstance(val, basestring): |
|||
args[key] = encoding.basestring2unicode(val) |
|||
else: |
|||
args[key] = unicode(val) |
|||
self.attributes['args'] = args |
|||
|
|||
class sortassong(beginsong): # pylint: disable=invalid-name,too-many-public-methods |
|||
r"""Treat '\sortassong' exactly as if it were a '\beginsong'.""" |
|||
pass |
@ -0,0 +1,157 @@ |
|||
#! /usr/bin/env python3 |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Command line tool to compile songbooks using the songbook library.""" |
|||
|
|||
import argparse |
|||
import json |
|||
import locale |
|||
import logging |
|||
import os.path |
|||
import textwrap |
|||
import sys |
|||
|
|||
from patacrep.build import SongbookBuilder, DEFAULT_STEPS |
|||
from patacrep import __version__ |
|||
from patacrep import errors |
|||
import patacrep.encoding |
|||
|
|||
# Logging configuration |
|||
logging.basicConfig(level=logging.INFO) |
|||
LOGGER = logging.getLogger() |
|||
|
|||
# pylint: disable=too-few-public-methods |
|||
class ParseStepsAction(argparse.Action): |
|||
"""Argparse action to split a string into a list.""" |
|||
def __call__(self, __parser, namespace, values, __option_string=None): |
|||
if not getattr(namespace, self.dest): |
|||
setattr(namespace, self.dest, []) |
|||
setattr( |
|||
namespace, |
|||
self.dest, |
|||
( |
|||
getattr(namespace, self.dest) |
|||
+ [value.strip() for value in values[0].split(',')] |
|||
), |
|||
) |
|||
|
|||
class VerboseAction(argparse.Action): |
|||
"""Set verbosity level with option --verbose.""" |
|||
def __call__(self, *_args, **_kwargs): |
|||
LOGGER.setLevel(logging.DEBUG) |
|||
|
|||
def argument_parser(args): |
|||
"""Parse arguments""" |
|||
parser = argparse.ArgumentParser(description="A song book compiler") |
|||
|
|||
parser.add_argument('--version', help='Show version', action='version', |
|||
version='%(prog)s ' + __version__) |
|||
|
|||
parser.add_argument('book', nargs=1, help=textwrap.dedent("""\ |
|||
Book to compile. |
|||
""")) |
|||
|
|||
parser.add_argument('--datadir', '-d', nargs='+', type=str, action='append', |
|||
help=textwrap.dedent("""\ |
|||
Data location. Expected (not necessarily required) |
|||
subdirectories are 'songs', 'img', 'latex', 'templates'. |
|||
""")) |
|||
|
|||
parser.add_argument('--verbose', '-v', nargs=0, action=VerboseAction, |
|||
help=textwrap.dedent("""\ |
|||
Show details about the compilation process. |
|||
""")) |
|||
|
|||
parser.add_argument('--steps', '-s', nargs=1, type=str, |
|||
action=ParseStepsAction, |
|||
help=textwrap.dedent("""\ |
|||
Steps to run. Default is "{steps}". |
|||
Available steps are: |
|||
"tex" produce .tex file from templates; |
|||
"pdf" compile .tex file; |
|||
"sbx" compile index files; |
|||
"clean" remove temporary files; |
|||
any string beginning with '%%' (in this case, it will be run |
|||
in a shell). Several steps (excepted the custom shell |
|||
command) can be combinend in one --steps argument, as a |
|||
comma separated string. |
|||
""".format(steps=','.join(DEFAULT_STEPS))), |
|||
default=None, |
|||
) |
|||
|
|||
options = parser.parse_args(args) |
|||
|
|||
return options |
|||
|
|||
|
|||
def main(): |
|||
"""Main function:""" |
|||
|
|||
# set script locale to match user's |
|||
try: |
|||
locale.setlocale(locale.LC_ALL, '') |
|||
except locale.Error as error: |
|||
# Locale is not installed on user's system, or wrongly configured. |
|||
LOGGER.error("Locale error: {}\n".format(str(error))) |
|||
|
|||
options = argument_parser(sys.argv[1:]) |
|||
|
|||
songbook_path = options.book[0] |
|||
|
|||
basename = os.path.basename(songbook_path)[:-3] |
|||
|
|||
try: |
|||
with patacrep.encoding.open_read(songbook_path) as songbook_file: |
|||
songbook = json.load(songbook_file) |
|||
if 'encoding' in songbook: |
|||
with patacrep.encoding.open_read( |
|||
songbook_path, |
|||
encoding=songbook['encoding'] |
|||
) as songbook_file: |
|||
songbook = json.load(songbook_file) |
|||
except Exception as error: # pylint: disable=broad-except |
|||
LOGGER.error(error) |
|||
LOGGER.error("Error while loading file '{}'.".format(songbook_path)) |
|||
sys.exit(1) |
|||
|
|||
# Gathering datadirs |
|||
datadirs = [] |
|||
if options.datadir: |
|||
# Command line options |
|||
datadirs += [item[0] for item in options.datadir] |
|||
if 'datadir' in songbook: |
|||
# .sg file |
|||
if isinstance(songbook['datadir'], str): |
|||
songbook['datadir'] = [songbook['datadir']] |
|||
datadirs += [ |
|||
os.path.join( |
|||
os.path.dirname(os.path.abspath(songbook_path)), |
|||
path |
|||
) |
|||
for path in songbook['datadir'] |
|||
] |
|||
# Default value |
|||
datadirs.append(os.path.dirname(os.path.abspath(songbook_path))) |
|||
|
|||
songbook['datadir'] = datadirs |
|||
|
|||
try: |
|||
sb_builder = SongbookBuilder(songbook, basename) |
|||
sb_builder.unsafe = True |
|||
|
|||
sb_builder.build_steps(options.steps) |
|||
except errors.SongbookError as error: |
|||
LOGGER.error(error) |
|||
if LOGGER.level >= logging.INFO: |
|||
LOGGER.error( |
|||
"Running again with option '-v' may give more information." |
|||
) |
|||
sys.exit(1) |
|||
except KeyboardInterrupt: |
|||
LOGGER.warning("Aborted by user.") |
|||
sys.exit(1) |
|||
|
|||
sys.exit(0) |
|||
|
|||
if __name__ == '__main__': |
|||
main() |
@ -0,0 +1,32 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Very simple LaTeX parsing.""" |
|||
|
|||
import os |
|||
|
|||
from patacrep import files |
|||
from patacrep.latex import parsesong |
|||
from patacrep.songs import Song |
|||
|
|||
class TexRenderer(Song): |
|||
"""Renderer for song and intersong files.""" |
|||
|
|||
def parse(self): |
|||
"""Parse song and set metadata.""" |
|||
self.data = parsesong(self.fullpath, self.encoding) |
|||
self.titles = self.data['@titles'] |
|||
self.languages = self.data['@languages'] |
|||
self.authors = self.data['by'] |
|||
|
|||
def render(self, context): |
|||
"""Return the string that will render the song.""" |
|||
return r'\input{{{}}}'.format(files.path2posix( |
|||
files.relpath( |
|||
self.fullpath, |
|||
os.path.dirname(context['filename']) |
|||
))) |
|||
|
|||
FILE_PLUGINS = { |
|||
'sg': TexRenderer, |
|||
'is': TexRenderer, |
|||
} |
@ -1,152 +1,9 @@ |
|||
#! /usr/bin/env python2 |
|||
# -*- coding: utf-8 -*- |
|||
#! /usr/bin/env python3 |
|||
|
|||
"""Command line tool to compile songbooks using the songbook library.""" |
|||
|
|||
import argparse |
|||
import json |
|||
import locale |
|||
import logging |
|||
import os.path |
|||
import textwrap |
|||
import sys |
|||
|
|||
from patacrep.build import SongbookBuilder, DEFAULT_STEPS |
|||
from patacrep import __version__ |
|||
from patacrep import errors |
|||
from patacrep import encoding |
|||
|
|||
# Logging configuration |
|||
logging.basicConfig(level=logging.INFO) |
|||
LOGGER = logging.getLogger() |
|||
|
|||
# pylint: disable=too-few-public-methods |
|||
class ParseStepsAction(argparse.Action): |
|||
"""Argparse action to split a string into a list.""" |
|||
def __call__(self, __parser, namespace, values, __option_string=None): |
|||
if not getattr(namespace, self.dest): |
|||
setattr(namespace, self.dest, []) |
|||
setattr( |
|||
namespace, |
|||
self.dest, |
|||
( |
|||
getattr(namespace, self.dest) |
|||
+ [value.strip() for value in values[0].split(',')] |
|||
), |
|||
) |
|||
|
|||
class VerboseAction(argparse.Action): |
|||
"""Set verbosity level with option --verbose.""" |
|||
def __call__(self, *_args, **_kwargs): |
|||
LOGGER.setLevel(logging.DEBUG) |
|||
|
|||
def argument_parser(args): |
|||
"""Parse arguments""" |
|||
parser = argparse.ArgumentParser(description="A song book compiler") |
|||
|
|||
parser.add_argument('--version', help='Show version', action='version', |
|||
version='%(prog)s ' + __version__) |
|||
|
|||
parser.add_argument('book', nargs=1, help=textwrap.dedent("""\ |
|||
Book to compile. |
|||
""")) |
|||
|
|||
parser.add_argument('--datadir', '-d', nargs='+', type=str, action='append', |
|||
help=textwrap.dedent("""\ |
|||
Data location. Expected (not necessarily required) |
|||
subdirectories are 'songs', 'img', 'latex', 'templates'. |
|||
""")) |
|||
|
|||
parser.add_argument('--verbose', '-v', nargs=0, action=VerboseAction, |
|||
help=textwrap.dedent("""\ |
|||
Show details about the compilation process. |
|||
""")) |
|||
|
|||
parser.add_argument('--steps', '-s', nargs=1, type=str, |
|||
action=ParseStepsAction, |
|||
help=textwrap.dedent("""\ |
|||
Steps to run. Default is "{steps}". |
|||
Available steps are: |
|||
"tex" produce .tex file from templates; |
|||
"pdf" compile .tex file; |
|||
"sbx" compile index files; |
|||
"clean" remove temporary files; |
|||
any string beginning with '%%' (in this case, it will be run |
|||
in a shell). Several steps (excepted the custom shell |
|||
command) can be combinend in one --steps argument, as a |
|||
comma separated string. |
|||
""".format(steps=','.join(DEFAULT_STEPS))), |
|||
default=None, |
|||
) |
|||
|
|||
options = parser.parse_args(args) |
|||
# Do not edit this file. This file is just a helper file for development test. |
|||
# It is not part of the distributed software. |
|||
|
|||
return options |
|||
|
|||
|
|||
def main(): |
|||
"""Main function:""" |
|||
|
|||
# set script locale to match user's |
|||
try: |
|||
locale.setlocale(locale.LC_ALL, '') |
|||
except locale.Error as error: |
|||
# Locale is not installed on user's system, or wrongly configured. |
|||
sys.stderr.write("Locale error: {}\n".format(error.message)) |
|||
|
|||
options = argument_parser(sys.argv[1:]) |
|||
|
|||
songbook_path = options.book[0] |
|||
|
|||
basename = os.path.basename(songbook_path)[:-3] |
|||
|
|||
songbook_file = None |
|||
try: |
|||
songbook_file = encoding.open_read(songbook_path) |
|||
songbook = json.load(songbook_file) |
|||
except Exception as error: # pylint: disable=broad-except |
|||
LOGGER.error(error) |
|||
LOGGER.error("Error while loading file '{}'.".format(songbook_path)) |
|||
sys.exit(1) |
|||
finally: |
|||
if songbook_file: |
|||
songbook_file.close() |
|||
|
|||
# Gathering datadirs |
|||
datadirs = [] |
|||
if options.datadir: |
|||
# Command line options |
|||
datadirs += [item[0] for item in options.datadir] |
|||
if 'datadir' in songbook: |
|||
# .sg file |
|||
if isinstance(songbook['datadir'], basestring): |
|||
songbook['datadir'] = [songbook['datadir']] |
|||
datadirs += [ |
|||
os.path.join( |
|||
os.path.dirname(os.path.abspath(songbook_path)), |
|||
path |
|||
) |
|||
for path in songbook['datadir'] |
|||
] |
|||
# Default value |
|||
datadirs.append(os.path.dirname(os.path.abspath(songbook_path))) |
|||
|
|||
songbook['datadir'] = datadirs |
|||
|
|||
try: |
|||
sb_builder = SongbookBuilder(songbook, basename) |
|||
sb_builder.unsafe = True |
|||
|
|||
sb_builder.build_steps(options.steps) |
|||
except errors.SongbookError as error: |
|||
LOGGER.error(error) |
|||
if LOGGER.level >= logging.INFO: |
|||
LOGGER.error( |
|||
"Running again with option '-v' may give more information." |
|||
) |
|||
sys.exit(1) |
|||
|
|||
sys.exit(0) |
|||
"""Command line tool to compile songbooks using the songbook library.""" |
|||
|
|||
if __name__ == '__main__': |
|||
main() |
|||
from patacrep.songbook import main |
|||
main() |
|||
|
@ -1,6 +0,0 @@ |
|||
[DEFAULT] |
|||
Depends: python-jinja2, python-pkg-resources, python-plastex, python-chardet, python-unidecode, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, lilypond, texlive-fonts-recommended |
|||
Recommends: texlive-lang-english, texlive-lang-french, texlive-lang-portuguese, texlive-lang-spanish, texlive-fonts-extra |
|||
XS-Python-Version: >=2.7 |
|||
Section: tex |
|||
|
Loading…
Reference in new issue