diff --git a/songbook.py b/songbook.py index ed9c5b54..08cf8e80 100755 --- a/songbook.py +++ b/songbook.py @@ -12,6 +12,7 @@ import sys from songbook.build import buildsongbook from songbook import __VERSION__ + def argument_parser(args): parser = argparse.ArgumentParser(description="A song book compiler") @@ -24,34 +25,38 @@ def argument_parser(args): parser.add_argument('--datadir', '-d', nargs=1, type=str, action='store', help=textwrap.dedent("""\ - Data location. Expected (not necessarily required) subdirectories are 'songs', 'img', 'latex', 'templates'. + Data location. Expected (not necessarily required) + subdirectories are 'songs', 'img', 'latex', 'templates'. """)) options = parser.parse_args(args) return options + def main(): - locale.setlocale(locale.LC_ALL, '') # set script locale to match user's + # set script locale to match user's + locale.setlocale(locale.LC_ALL, '') options = argument_parser(sys.argv[1:]) - sbFile = options.book[0] + songbook_path = options.book[0] - basename = os.path.basename(sbFile)[:-3] + basename = os.path.basename(songbook_path)[:-3] - f = open(sbFile) - sb = json.load(f) - f.close() + with open(songbook_path) as songbook_file: + songbook = json.load(songbook_file) if options.datadir is not None: - sb['datadir'] = options.datadir - elif 'datadir' in sb.keys(): - if not os.path.isabs(sb['datadir']): - sb['datadir'] = os.path.join(os.path.dirname(sbFile), sb['datadir']) + songbook['datadir'] = options.datadir + elif 'datadir' in songbook.keys(): + if not os.path.isabs(songbook['datadir']): + songbook['datadir'] = os.path.join(os.path.dirname(songbook_path), + songbook['datadir'] + ) else: - sb['datadir'] = os.path.dirname(sbFile) - buildsongbook(sb, basename) + songbook['datadir'] = os.path.dirname(songbook_path) + buildsongbook(songbook, basename) if __name__ == '__main__': main() diff --git a/songbook/authors.py b/songbook/authors.py index ee3010d9..9ca095e5 100644 --- a/songbook/authors.py +++ b/songbook/authors.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + def split_author_names(string): """Split author between first and last name. @@ -30,6 +31,7 @@ def split_author_names(string): brace_count -= 1 return string[:last_space], string[last_space:] + def split_sep_author(string, sep): authors = [] match = sep.match(string) @@ -40,12 +42,16 @@ def split_sep_author(string, sep): authors.append(string) return authors -def processauthors(authors_string, after = [], ignore = [], sep = []): + +def processauthors(authors_string, after=[], ignore=[], sep=[]): """Return a list of authors For example, we are processing: # processauthors( - # "Lyrics by William Blake (from Milton, 1808), music by Hubert Parry (1916), and sung by The Royal\ Choir~of~Nowhere (just here to show you how processing is done)", + # "Lyrics by William Blake (from Milton, 1808), + music by Hubert Parry (1916), + and sung by The Royal\ Choir~of~Nowhere + (just here to show you how processing is done)", # after = ["by"], # ignore = ["anonymous"], # sep = ["and"] @@ -54,10 +60,12 @@ def processauthors(authors_string, after = [], ignore = [], sep = []): The "authors_string" string is processed as: 1) First, parenthesis (and its content) are removed. - # "Lyrics by William Blake, music by Hubert Parry, and sung by The Royal\ Choir~of~Nowhere" + # "Lyrics by William Blake, music by Hubert Parry, + and sung by The Royal\ Choir~of~Nowhere" 2) String is split, separators being comma and words from "sep". - # ["Lyrics by William Blake", "music by Hubert Parry", "sung by The Royal\ Choir~of~Nowhere"] + # ["Lyrics by William Blake", "music by Hubert Parry", + "sung by The Royal\ Choir~of~Nowhere"] 3) Everything before words in "after" is removed. # ["William Blake", "Hubert Parry", "The Royal\ Choir~of~Nowhere"] @@ -113,7 +121,8 @@ def processauthors(authors_string, after = [], ignore = [], sep = []): authors_list = dest # Cleaning: removing empty authors and unnecessary spaces - authors_list = [author.lstrip() for author in authors_list if author.lstrip()] + authors_list = [author.lstrip() + for author in authors_list if author.lstrip()] # Moving first names after last names dest = [] diff --git a/songbook/build.py b/songbook/build.py index 71906447..dcad965f 100755 --- a/songbook/build.py +++ b/songbook/build.py @@ -14,17 +14,19 @@ from songbook.index import processSXD from songbook.songs import Song, SongsList from songbook import __SHAREDIR__ + def parseTemplate(template): embeddedJsonPattern = re.compile(r"^%%:") - f = open(template) - code = [ line[3:-1] for line in f if embeddedJsonPattern.match(line) ] - f.close() + with open(template) as template_file: + code = [line[3:-1] for line in template_file if embeddedJsonPattern.match(line)] + data = json.loads(''.join(code)) parameters = dict() for param in data: parameters[param["name"]] = param return parameters + def toValue(parameter, data): if "type" not in parameter: return data @@ -37,7 +39,7 @@ def toValue(parameter, data): elif parameter["type"] == "color": return data[1:] elif parameter["type"] == "font": - return data+'pt' + return data + 'pt' elif parameter["type"] == "enum": return data elif parameter["type"] == "file": @@ -49,15 +51,20 @@ def toValue(parameter, data): joinText = '' return joinText.join(data) + def formatDeclaration(name, parameter): value = "" if "default" in parameter: value = parameter["default"] - return '\\def\\set@{name}#1{{\\def\\get{name}{{#1}}}}\n'.format(name=name) + formatDefinition(name, toValue(parameter, value)) + return ('\\def\\set@{name}#1{{\\def\\get{name}{{#1}}}}\n'.format(name=name) + + formatDefinition(name, toValue(parameter, value)) + ) + def formatDefinition(name, value): return '\\set@{name}{{{value}}}\n'.format(name=name, value=value) + def clean(basename): generated_extensions = [ "_auth.sbx", @@ -76,6 +83,7 @@ def clean(basename): return True + def makeTexFile(sb, output): datadir = sb['datadir'] name = output[:-4] @@ -87,7 +95,7 @@ def makeTexFile(sb, output): authwords_tex = "" authwords = {"after": ["by"], "ignore": ["unknown"], "sep": ["and"]} - + # parse the songbook data if "template" in sb: template = sb["template"] @@ -117,10 +125,12 @@ def makeTexFile(sb, output): authwords_tex += "\\auth%sword{%s}\n" % (key, word) sb["authwords"] = authwords_tex if "after" in authwords: - authwords["after"] = [re.compile(r"^.*%s\b(.*)" % after) for after in authwords["after"]] + 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"] ] + authwords["sep"] = [re.compile(r"^(.*)%s (.*)$" % sep) + for sep in authwords["sep"]] if "lang" not in sb: sb["lang"] = "french" @@ -152,15 +162,15 @@ def makeTexFile(sb, output): out.write('%% This file has been automatically generated, do not edit!\n') out.write('\\makeatletter\n') # output automatic parameters - out.write(formatDeclaration("name", {"default":name})) - out.write(formatDeclaration("songslist", {"type":"stringlist"})) + out.write(formatDeclaration("name", {"default": name})) + out.write(formatDeclaration("songslist", {"type": "stringlist"})) # output template parameter command for name, parameter in parameters.iteritems(): out.write(formatDeclaration(name, parameter)) # output template parameter values for name, value in sb.iteritems(): if name in parameters: - out.write(formatDefinition(name, toValue(parameters[name],value))) + out.write(formatDefinition(name, toValue(parameters[name], value))) if len(songs) > 0: out.write(formatDefinition('songslist', songslist.latex())) @@ -169,7 +179,7 @@ def makeTexFile(sb, output): # output template commentPattern = re.compile(r"^\s*%") with codecs.open(os.path.join(template_dir, template), 'r', 'utf-8') as f: - content = [ line for line in f if not commentPattern.match(line) ] + content = [line for line in f if not commentPattern.match(line)] for index, line in enumerate(content): if re.compile("getDataImgDirectory").search(line): @@ -183,6 +193,7 @@ def makeTexFile(sb, output): out.write(u''.join(content)) out.close() + def buildsongbook(sb, basename): """Build a songbook @@ -191,12 +202,12 @@ def buildsongbook(sb, basename): - basename: basename of the songbook to be built. """ - texFile = basename + ".tex" + texFile = basename + ".tex" # Make TeX file makeTexFile(sb, texFile) - if not os.environ.has_key('TEXINPUTS'): + 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(sb['datadir'], 'latex') @@ -210,7 +221,7 @@ def buildsongbook(sb, basename): for sxdFile in sxdFiles: print "processing " + sxdFile idx = processSXD(sxdFile) - indexFile = open(sxdFile[:-3]+"sbx", "w") + indexFile = open(sxdFile[:-3] + "sbx", "w") indexFile.write(idx.entriesToStr().encode('utf8')) indexFile.close() diff --git a/songbook/files.py b/songbook/files.py index f29bbd63..ce2cc6bd 100644 --- a/songbook/files.py +++ b/songbook/files.py @@ -5,10 +5,10 @@ import fnmatch import os + def recursiveFind(root_directory, pattern): matches = [] for root, dirnames, filenames in os.walk(root_directory): for filename in fnmatch.filter(filenames, pattern): matches.append(os.path.join(root, filename)) return matches - diff --git a/songbook/index.py b/songbook/index.py index 7360e031..8a8485e7 100644 --- a/songbook/index.py +++ b/songbook/index.py @@ -13,7 +13,7 @@ from unidecode import unidecode import locale import re import sys -import warnings +#import warnings from songbook.authors import processauthors from songbook.plastex import simpleparse @@ -22,6 +22,7 @@ from songbook.plastex import simpleparse keywordPattern = re.compile(r"^%(\w+)\s?(.*)$") firstLetterPattern = re.compile(r"^(?:\{?\\\w+\}?)*[^\w]*(\w)") + def sortkey(value): ''' From a title, return something usable for sorting. It handles locale (but @@ -30,35 +31,40 @@ def sortkey(value): ''' return locale.strxfrm(unidecode(simpleparse(value).replace(' ', 'A'))) + def processSXDEntry(tab): return (tab[0], tab[1], tab[2]) + def processSXD(filename): - file = open(filename) + index_file = open(filename) data = [] - for line in file: + for line in index_file: data.append(line.strip()) - file.close() + index_file.close() i = 1 idx = index(data[0]) while len(data) > i and data[i].startswith('%'): keywords = keywordPattern.match(data[i]).groups() - idx.keyword(keywords[0],keywords[1]) + idx.keyword(keywords[0], keywords[1]) i += 1 idx.compileKeywords() - for i in range(i,len(data),3): - entry = processSXDEntry(data[i:i+3]) - idx.add(entry[0],entry[1],entry[2]) + for i in range(i, len(data), 3): + entry = processSXDEntry(data[i:i + 3]) + idx.add(entry[0], entry[1], entry[2]) return idx + class index: def __init__(self, indextype): self.data = dict() self.keywords = dict() + self.prefix_patterns = [] + self.authwords = {"after": [], "ignore": [], "sep": []} if indextype == "TITLE INDEX DATA FILE": self.indextype = "TITLE" elif indextype == "SCRIPTURE INDEX DATA FILE": @@ -70,23 +76,21 @@ class index: def filter(self, key): letter = firstLetterPattern.match(key).group(1) - if re.match('\d',letter): + if re.match('\d', letter): letter = '0-9' return (letter.upper(), key) def keyword(self, key, word): - if not self.keywords.has_key(key): + if not key in self.keywords.keys(): self.keywords[key] = [] self.keywords[key].append(word) def compileKeywords(self): - self.prefix_patterns = [] if self.indextype == "TITLE": if 'prefix' in self.keywords: for prefix in self.keywords['prefix']: self.prefix_patterns.append(re.compile(r"^(%s)(\b|\\)(\s*.*)$" % prefix)) - self.authwords = {"after": [], "ignore": [], "sep": []} if self.indextype == "AUTHOR": for key in self.keywords: if key in self.authwords: @@ -94,20 +98,22 @@ class index: for word in self.authwords.keys(): if word in self.keywords: if word == "after": - self.authwords[word] = [re.compile(r"^.*%s\b(.*)" % after) for after in self.keywords[word]] + self.authwords[word] = [re.compile(r"^.*%s\b(.*)" % after) + for after in self.keywords[word]] elif word == "sep": self.authwords[word] = [" %s" % sep for sep in self.authwords[word]] + [","] - self.authwords[word] = [re.compile(r"^(.*)%s (.*)$" % sep) for sep in self.authwords[word] ] + self.authwords[word] = [re.compile(r"^(.*)%s (.*)$" % sep) + for sep in self.authwords[word]] else: self.authwords[word] = self.keywords[word] def _raw_add(self, key, number, link): (first, key) = self.filter(key) - if not self.data.has_key(first): + if not first in self.data.keys(): self.data[first] = dict() - if not self.data[first].has_key(key): + if not key in self.data[first].keys(): self.data[first][key] = [] - self.data[first][key].append({'num':number, 'link':link}) + self.data[first][key].append({'num': number, 'link': link}) def add(self, key, number, link): if self.indextype == "TITLE": @@ -116,8 +122,8 @@ class index: match = pattern.match(key) if match: self._raw_add( - "%s (%s)" % (match.group(2) + match.group(3), match.group(1)), - number, link) + "%s (%s)" % (match.group(2) + match.group(3), + match.group(1)), number, link) return self._raw_add(key, number, link) @@ -129,26 +135,26 @@ class index: self._raw_add(author, number, link) def refToStr(self, ref): - if sys.version_info >= (2,6): + if sys.version_info >= (2, 6): return '\\hyperlink{{{0[link]}}}{{{0[num]}}}'.format(ref) else: return '\\hyperlink{%(link)s}{%(num)s}' % ref def entryToStr(self, key, entry): - if sys.version_info >= (2,6): + if sys.version_info >= (2, 6): return unicode('\\idxentry{{{0}}}{{{1}}}\n').format(key, '\\\\'.join(map(self.refToStr, entry))) else: return unicode('\\idxentry{%s}{%s}\n') % (key, '\\\\'.join(map(self.refToStr, entry))) def idxBlockToStr(self, letter, entries): - str = '\\begin{idxblock}{'+letter+'}'+'\n' + string = '\\begin{idxblock}{' + letter + '}' + '\n' for key in sorted(entries.keys(), key=sortkey): - str += self.entryToStr(key, entries[key]) - str += '\\end{idxblock}'+'\n' - return str + string += self.entryToStr(key, entries[key]) + string += '\\end{idxblock}' + '\n' + return string def entriesToStr(self): - str = "" + string = "" for letter in sorted(self.data.keys()): - str += self.idxBlockToStr(letter, self.data[letter]) - return str + string += self.idxBlockToStr(letter, self.data[letter]) + return string diff --git a/songbook/plastex-songs.py b/songbook/plastex-songs.py index cddec648..beb3f615 100644 --- a/songbook/plastex-songs.py +++ b/songbook/plastex-songs.py @@ -5,6 +5,7 @@ import plasTeX from songbook.plastex import processUnbreakableSpace + def split_linebreak(texlist): return_list = [] current = [] @@ -18,6 +19,7 @@ def split_linebreak(texlist): return_list.append(current) return return_list + class beginsong(plasTeX.Command): args = '{titles}[ args:dict ]' def invoke(self, tex): diff --git a/songbook/plastex.py b/songbook/plastex.py index a130e81e..6b16498e 100755 --- a/songbook/plastex.py +++ b/songbook/plastex.py @@ -5,23 +5,27 @@ from plasTeX.TeX import TeX from plasTeX.Base.LaTeX import Sentences import codecs -import copy +#import copy import locale import os import sys + def processUnbreakableSpace(node): - """Replace '~' and '\ ' in node by nodes that will be rendered as unbreakable space. + """Replace '~' and '\ ' in node by nodes that + will be rendered as unbreakable space. Return node object for convenience. """ - if type(node) == Sentences.InterWordSpace or (type(node) == Sentences.NoLineBreak and node.source == '~ '): + if (type(node) == Sentences.InterWordSpace or + (type(node) == Sentences.NoLineBreak and node.source == '~ ')): node.unicode = unichr(160) for child in node.childNodes: processUnbreakableSpace(child) return node + def simpleparse(text): """Parse a simple LaTeX string. """ @@ -30,6 +34,7 @@ def simpleparse(text): doc = tex.parse() return processUnbreakableSpace(doc.textContent) + class SongParser: """Analyseur syntaxique de fichiers .sg""" @@ -50,6 +55,7 @@ class SongParser: tex.input(codecs.open(filename, 'r+', 'utf-8', 'replace')) return tex.parse() + def parsetex(filename): """Analyse syntaxique d'un fichier .sg diff --git a/songbook/songs.py b/songbook/songs.py index a0277582..943c6c7d 100644 --- a/songbook/songs.py +++ b/songbook/songs.py @@ -10,6 +10,7 @@ import re from songbook.authors import processauthors from songbook.plastex import parsetex + class Song: #: Ordre de tri sort = [] @@ -19,10 +20,15 @@ class Song: authwords = {"after": [], "ignore": [], "sep": []} def __init__(self, path, languages, titles, args): - self.titles = titles - self.normalized_titles = [locale.strxfrm(unprefixed_title(unidecode(unicode(title, "utf-8")), self.prefixes)) for title in titles] - self.args = args - self.path = path + self.titles = titles + self.normalized_titles = [locale.strxfrm( + unprefixed_title(unidecode(unicode(title, "utf-8")), + self.prefixes + ) + ) + for title in titles] + self.args = args + self.path = path self.languages = languages if "by" in self.args.keys(): self.normalized_authors = [ @@ -59,6 +65,7 @@ class Song: return 1 return 0 + def unprefixed_title(title, prefixes): """Remove the first prefix of the list in the beginning of title (if any). """ @@ -68,6 +75,7 @@ def unprefixed_title(title, prefixes): return match.group(2) return title + class SongsList: """Manipulation et traitement de liste de chansons""" @@ -78,7 +86,6 @@ class SongsList: # Liste triée des chansons self.songs = [] - def append(self, filename): """Ajout d'une chanson à la liste @@ -113,7 +120,8 @@ class SongsList: def latex(self): """Renvoie le code LaTeX nécessaire pour intégrer la liste de chansons. """ - result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in self.songs] + result = ['\\input{{{0}}}'.format(song.path.replace("\\", "/").strip()) + for song in self.songs] result.append('\\selectlanguage{%s}' % self._language) return '\n'.join(result)