mirror of https://github.com/patacrep/patacrep.git
Romain Goffe
12 years ago
8 changed files with 589 additions and 42 deletions
@ -0,0 +1,145 @@ |
|||||
|
#!/usr/bin/python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
# Generate indexes files for the Crep's chordbook compilation. This is |
||||
|
# a replacement for the original makeindex program written in C that |
||||
|
# produces an index file (.sbx) from a file generated by the latex |
||||
|
# compilation of the songbook (.sxd). |
||||
|
# |
||||
|
# Usage : songbook-makeindex.py src |
||||
|
# src is the .sxd file generated by latex |
||||
|
# |
||||
|
|
||||
|
import sys |
||||
|
import re |
||||
|
import locale |
||||
|
import warnings |
||||
|
|
||||
|
# Pattern set to ignore latex command in title prefix |
||||
|
keywordPattern = re.compile(r"^%(\w+)\s?(.*)$") |
||||
|
firstLetterPattern = re.compile(r"^(?:\{?\\\w+\}?)*[^\w]*(\w)") |
||||
|
iecPattern = re.compile(r"\IeC {\\(.*?)}") |
||||
|
replacePattern = { |
||||
|
'`A': 'À', |
||||
|
'`a': 'à', |
||||
|
'^a': 'â', |
||||
|
"'a": 'á', |
||||
|
"~a": 'ã', |
||||
|
'oe': 'œ', |
||||
|
"'e" : 'é', |
||||
|
"`e" : 'è', |
||||
|
"^e" : 'ê', |
||||
|
'"e' : 'ë', |
||||
|
"'E" : 'É', |
||||
|
"`E" : 'È', |
||||
|
"'o" : 'ó', |
||||
|
"^o" : 'ô', |
||||
|
r'"\i' : 'i', |
||||
|
r'^\i' : 'i', |
||||
|
'"u' : 'ü', |
||||
|
'`u' : 'ù', |
||||
|
'`u' : 'ù', |
||||
|
'~n' : 'ñ', |
||||
|
"c C" : 'Ç', |
||||
|
"c c" : 'ç', |
||||
|
"textquoteright" : "'", |
||||
|
} |
||||
|
|
||||
|
def sortkey(value): |
||||
|
''' |
||||
|
From a title, return something usable for sorting. It handles locale (but |
||||
|
don't forget to call locale.setlocale(locale.LC_ALL, '')). It also try to |
||||
|
handle the sort with crappy latex escape sequences. Some chars may not be |
||||
|
handled by this function, so add them to *replacePattern* dictionnary. |
||||
|
''' |
||||
|
def repl(match): |
||||
|
try: |
||||
|
return replacePattern[match.group(1).strip()] |
||||
|
except KeyError: |
||||
|
warnings.warn("Error, no match to replace %s in %s. You should add it in the coresponding table in title_sort.py" % (match.group(0), match.group(1))) |
||||
|
|
||||
|
return locale.strxfrm(iecPattern.sub(repl, value).replace(' ', 'A')) |
||||
|
|
||||
|
def processSXDEntry(tab): |
||||
|
return (tab[0], tab[1], tab[2]) |
||||
|
|
||||
|
def processSXD(filename): |
||||
|
file = open(filename) |
||||
|
data = [] |
||||
|
for line in file: |
||||
|
data.append(line.strip()) |
||||
|
file.close() |
||||
|
|
||||
|
type = data[0] |
||||
|
i = 1 |
||||
|
idx = index() |
||||
|
|
||||
|
if len(data) > 1: |
||||
|
while data[i].startswith('%'): |
||||
|
keywords = keywordPattern.match(data[i]).groups() |
||||
|
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]) |
||||
|
return idx |
||||
|
|
||||
|
class index: |
||||
|
data = dict() |
||||
|
keywords = dict() |
||||
|
|
||||
|
def filter(self, key): |
||||
|
letter = firstLetterPattern.match(key).group(1) |
||||
|
if re.match('\d',letter): |
||||
|
letter = '0-9' |
||||
|
return (letter.upper(), key) |
||||
|
|
||||
|
def keyword(self, key, word): |
||||
|
if not self.keywords.has_key(key): |
||||
|
self.keywords[key] = [] |
||||
|
self.keywords[key].append(word) |
||||
|
|
||||
|
def compileKeywords(self): |
||||
|
self.prefix_patterns = [] |
||||
|
if 'prefix' in self.keywords: |
||||
|
for prefix in self.keywords['prefix']: |
||||
|
self.prefix_patterns.append(re.compile(r"^(%s)\b\s*(.*)$" % prefix)) |
||||
|
|
||||
|
def add(self, key, number, link): |
||||
|
for pattern in self.prefix_patterns: |
||||
|
match = pattern.match(key) |
||||
|
if match: |
||||
|
key = "%s (%s)" % (match.group(2), match.group(1)) |
||||
|
break # Only one match per key |
||||
|
(first, key) = self.filter(key) |
||||
|
if not self.data.has_key(first): |
||||
|
self.data[first] = dict() |
||||
|
if not self.data[first].has_key(key): |
||||
|
self.data[first][key] = [] |
||||
|
self.data[first][key].append({'num':number, 'link':link}) |
||||
|
|
||||
|
def refToStr(self, ref): |
||||
|
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): |
||||
|
return '\\idxentry{{{0}}}{{{1}}}\n'.format(key, '\\\\'.join(map(self.refToStr, entry))) |
||||
|
else: |
||||
|
return '\\idxentry{%s}{%s}\n' % (key, '\\\\'.join(map(self.refToStr, entry))) |
||||
|
|
||||
|
def idxBlockToStr(self, letter, entries): |
||||
|
str = '\\begin{idxblock}{'+letter+'}'+'\n' |
||||
|
for key in sorted(entries.keys(), key=sortkey): |
||||
|
str += self.entryToStr(key, entries[key]) |
||||
|
str += '\\end{idxblock}'+'\n' |
||||
|
return str |
||||
|
|
||||
|
def entriesToStr(self): |
||||
|
str = "" |
||||
|
for letter in sorted(self.data.keys()): |
||||
|
str += self.idxBlockToStr(letter, self.data[letter]) |
||||
|
return str |
@ -0,0 +1,21 @@ |
|||||
|
#!/usr/bin/python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
# |
||||
|
|
||||
|
import re |
||||
|
|
||||
|
reTitle = re.compile('(?<=beginsong\\{)(.(?<!\\}]))+') |
||||
|
reArtist = re.compile('(?<=by=)(.(?<![,\\]\\}]))+') |
||||
|
reAlbum = re.compile('(?<=album=)(.(?<![,\\]\\}]))+') |
||||
|
reLilypond = re.compile('(?<=album=)(.(?<![,\\]\\}]))+') |
||||
|
|
||||
|
class Song: |
||||
|
def __init__(self, title, artist, album, path): |
||||
|
self.title = title |
||||
|
self.artist = artist |
||||
|
self.album = album |
||||
|
self.path = path |
||||
|
self.isLilypond = isLilypond |
||||
|
def __repr__(self): |
||||
|
return repr((self.title, self.artist, self.album, self.path)) |
||||
|
|
@ -0,0 +1,282 @@ |
|||||
|
#!/usr/bin/python |
||||
|
# -*- coding: utf-8 -*- |
||||
|
# |
||||
|
|
||||
|
import getopt, sys |
||||
|
import os.path |
||||
|
import re |
||||
|
import json |
||||
|
import locale |
||||
|
import shutil |
||||
|
import locale |
||||
|
import platform |
||||
|
|
||||
|
from utils.utils import recursiveFind |
||||
|
|
||||
|
reTitle = re.compile('(?<=beginsong\\{)(.(?<!\\}]))+') |
||||
|
reArtist = re.compile('(?<=by=)(.(?<![,\\]\\}]))+') |
||||
|
reAlbum = re.compile('(?<=album=)(.(?<![,\\]\\}]))+') |
||||
|
|
||||
|
class Song: |
||||
|
def __init__(self, title, artist, album, path): |
||||
|
self.title = title |
||||
|
self.artist = artist |
||||
|
self.album = album |
||||
|
self.path = path |
||||
|
def __repr__(self): |
||||
|
return repr((self.title, self.artist, self.album, self.path)) |
||||
|
|
||||
|
if platform.system() == "Linux": |
||||
|
from xdg.BaseDirectory import * |
||||
|
cachePath = os.path.join(xdg_cache_home, 'songbook') |
||||
|
else: |
||||
|
cachePath = os.path.join('cache', 'songbook') |
||||
|
|
||||
|
def makeCoverCache(library): |
||||
|
''' |
||||
|
Copy all pictures found in the libraries into a unique cache |
||||
|
folder. |
||||
|
''' |
||||
|
# create the cache directory if it does not exist |
||||
|
if not os.path.exists(cachePath): |
||||
|
os.makedirs(cachePath) |
||||
|
|
||||
|
# copy pictures file into the cache directory |
||||
|
covers = recursiveFind(os.path.join(library, 'songs'), '*.jpg') |
||||
|
for cover in covers: |
||||
|
coverPath = os.path.join(cachePath, os.path.basename(cover)) |
||||
|
shutil.copy(cover, coverPath) |
||||
|
|
||||
|
def matchRegexp(reg, iterable): |
||||
|
return [ m.group(1) for m in (reg.match(l) for l in iterable) if m ] |
||||
|
|
||||
|
def unprefixed(title, prefixes): |
||||
|
"""Remove the first prefix of the list in the beginning of title (if any). |
||||
|
""" |
||||
|
for prefix in prefixes: |
||||
|
match = re.compile(r"^(%s)\b\s*(.*)$" % prefix).match(title) |
||||
|
if match: |
||||
|
return match.group(2) |
||||
|
return title |
||||
|
|
||||
|
def songslist(library, songs, prefixes): |
||||
|
song_objects = [] |
||||
|
for s in songs: |
||||
|
path = library + 'songs/' + s |
||||
|
with open(path, 'r+') as f: |
||||
|
data = f.read() |
||||
|
title = reTitle.search(data).group(0) |
||||
|
artist = reArtist.search(data.replace("{","")).group(0) |
||||
|
match = reAlbum.search(data.replace("{","")) |
||||
|
if match: |
||||
|
album = match.group(0) |
||||
|
else: |
||||
|
album = '' |
||||
|
song_objects.append(Song(title, artist, album, path)) |
||||
|
|
||||
|
song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(unprefixed(x.title, prefixes))) |
||||
|
song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(x.album)) |
||||
|
song_objects = sorted(song_objects, key=lambda x: locale.strxfrm(x.artist)) |
||||
|
|
||||
|
result = [ '\\input{{{0}}}'.format(song.path.replace("\\","/").strip()) for song in song_objects ] |
||||
|
return '\n'.join(result) |
||||
|
|
||||
|
def parseTemplate(template): |
||||
|
embeddedJsonPattern = re.compile(r"^%%:") |
||||
|
f = open(template) |
||||
|
code = [ line[3:-1] for line in f if embeddedJsonPattern.match(line) ] |
||||
|
f.close() |
||||
|
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 |
||||
|
elif parameter["type"] == "stringlist": |
||||
|
if "join" in parameter: |
||||
|
joinText = parameter["join"] |
||||
|
else: |
||||
|
joinText = '' |
||||
|
return joinText.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: |
||||
|
joinText = parameter["join"] |
||||
|
else: |
||||
|
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)) |
||||
|
|
||||
|
def formatDefinition(name, value): |
||||
|
return '\\set@{name}{{{value}}}\n'.format(name=name, value=value) |
||||
|
|
||||
|
def makeTexFile(sb, library, output): |
||||
|
name = output[:-4] |
||||
|
|
||||
|
# default value |
||||
|
template = "patacrep.tmpl" |
||||
|
songs = [] |
||||
|
titleprefixwords = "" |
||||
|
prefixes = [] |
||||
|
|
||||
|
# parse the songbook data |
||||
|
if "template" in sb: |
||||
|
template = sb["template"] |
||||
|
del sb["template"] |
||||
|
if "songs" in sb: |
||||
|
songs = sb["songs"] |
||||
|
del sb["songs"] |
||||
|
if "titleprefixwords" in sb: |
||||
|
prefixes = sb["titleprefixwords"] |
||||
|
for prefix in sb["titleprefixwords"]: |
||||
|
titleprefixwords += "\\titleprefixword{%s}\n" % prefix |
||||
|
sb["titleprefixwords"] = titleprefixwords |
||||
|
|
||||
|
parameters = parseTemplate("templates/"+template) |
||||
|
|
||||
|
# output relevant fields |
||||
|
out = open(output, 'w') |
||||
|
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"})) |
||||
|
# 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))) |
||||
|
# output songslist |
||||
|
if songs == "all": |
||||
|
songs = map(lambda x: x[len(library) + 6:], recursiveFind(os.path.join(library, 'songs'), '*.sg')) |
||||
|
|
||||
|
if len(songs) > 0: |
||||
|
out.write(formatDefinition('songslist', songslist(library, songs, prefixes))) |
||||
|
out.write('\\makeatother\n') |
||||
|
|
||||
|
# output template |
||||
|
commentPattern = re.compile(r"^\s*%") |
||||
|
f = open("templates/"+template) |
||||
|
content = [ line for line in f if not commentPattern.match(line) ] |
||||
|
|
||||
|
for index, line in enumerate(content): |
||||
|
if re.compile("getCacheDirectory").search(line): |
||||
|
line = line.replace("\\getCacheDirectory", cachePath.replace("\\","/") + "/") |
||||
|
content[index] = line |
||||
|
if re.compile("getLibraryImgDirectory").search(line): |
||||
|
line = line.replace("\\getLibraryImgDirectory", library + "img/") |
||||
|
content[index] = line |
||||
|
if re.compile("getLibraryLilypondDirectory").search(line): |
||||
|
line = line.replace("\\getLibraryLilypondDirectory", library + "lilypond/") |
||||
|
content[index] = line |
||||
|
|
||||
|
f.close() |
||||
|
out.write(''.join(content)) |
||||
|
|
||||
|
out.close() |
||||
|
|
||||
|
def makeDepend(sb, library, output): |
||||
|
name = output[:-2] |
||||
|
|
||||
|
indexPattern = re.compile(r"^[^%]*\\(?:newauthor|new)index\{.*\}\{(.*?)\}") |
||||
|
lilypondPattern = re.compile(r"^[^%]*\\(?:lilypond)\{(.*?)\}") |
||||
|
|
||||
|
# check for deps (in sb data) |
||||
|
deps = []; |
||||
|
if sb["songs"] == "all": |
||||
|
deps += recursiveFind(os.path.join(library, 'songs'), '*.sg') |
||||
|
else: |
||||
|
deps += map(lambda x: library + "songs/" + x, sb["songs"]) |
||||
|
|
||||
|
# check for lilypond deps (in songs data) if necessary |
||||
|
lilypond = [] |
||||
|
if "bookoptions" in sb and "lilypond" in sb["bookoptions"]: |
||||
|
for filename in deps: |
||||
|
tmpl = open(filename) |
||||
|
lilypond += matchRegexp(lilypondPattern, tmpl) |
||||
|
tmpl.close() |
||||
|
|
||||
|
# check for index (in template file) |
||||
|
if "template" in sb: |
||||
|
filename = sb["template"] |
||||
|
else: |
||||
|
filename = "patacrep.tmpl" |
||||
|
tmpl = open("templates/"+filename) |
||||
|
idx = map(lambda x: x.replace("\getname", name), matchRegexp(indexPattern, tmpl)) |
||||
|
tmpl.close() |
||||
|
|
||||
|
# write .d file |
||||
|
out = open(output, 'w') |
||||
|
out.write('{0} {1} : {2}\n'.format(output, name+".tex", ' '.join(deps))) |
||||
|
out.write('{0} : {1}\n'.format(name+".pdf", ' '.join(map(lambda x: x+".sbx",idx)+map(lambda x: library+"lilypond/"+x+".pdf", lilypond)))) |
||||
|
out.write('\t$(LATEX) {0}\n'.format(name+".tex")) |
||||
|
out.write('{0} : {1}\n'.format(' '.join(map(lambda x: x+".sxd",idx)), name+".aux")) |
||||
|
out.close() |
||||
|
|
||||
|
def usage(): |
||||
|
print "No usage information yet." |
||||
|
|
||||
|
def main(): |
||||
|
locale.setlocale(locale.LC_ALL, '') # set script locale to match user's |
||||
|
try: |
||||
|
opts, args = getopt.getopt(sys.argv[1:], |
||||
|
"hs:o:l:d", |
||||
|
["help","songbook=","output=","depend","library="]) |
||||
|
except getopt.GetoptError, err: |
||||
|
# print help and exit |
||||
|
print str(err) |
||||
|
usage() |
||||
|
sys.exit(2) |
||||
|
|
||||
|
songbook = None |
||||
|
depend = False |
||||
|
output = None |
||||
|
library = './' |
||||
|
|
||||
|
for o, a in opts: |
||||
|
if o in ("-h", "--help"): |
||||
|
usage() |
||||
|
sys.exit() |
||||
|
elif o in ("-s", "--songbook"): |
||||
|
songbook = a |
||||
|
elif o in ("-d", "--depend"): |
||||
|
depend = True |
||||
|
elif o in ("-o", "--output"): |
||||
|
output = a |
||||
|
elif o in ("-l", "--library"): |
||||
|
if not a.endswith('/'): |
||||
|
a += '/' |
||||
|
library = a |
||||
|
else: |
||||
|
assert False, "unhandled option" |
||||
|
|
||||
|
makeCoverCache(library) |
||||
|
if songbook and output: |
||||
|
f = open(songbook) |
||||
|
sb = json.load(f) |
||||
|
f.close() |
||||
|
|
||||
|
if depend: |
||||
|
makeDepend(sb, library, output) |
||||
|
else: |
||||
|
makeTexFile(sb, library, output) |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
main() |
Loading…
Reference in new issue