Browse Source

wip

remotes/origin/next
Romain Goffe 12 years ago
parent
commit
802446b832
  1. 145
      python/index.py
  2. 21
      python/song.py
  3. 282
      python/template.py
  4. 0
      python/tools.py
  5. 2
      songbook-makeindex.py
  6. 79
      songbook.py
  7. 102
      sortindex.py
  8. 0
      utils/__init__.py

145
python/index.py

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

21
python/song.py

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

282
python/template.py

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

0
utils/utils.py → python/tools.py

2
songbook-makeindex.py

@ -9,10 +9,10 @@
# src is the .sxd file generated by latex
#
import sys
import os.path
import glob
import re
import sys
from optparse import OptionParser
import sortindex
import locale

79
songbook.py

@ -4,33 +4,22 @@
import getopt, sys
import os.path
import re
import json
import locale
import shutil
import locale
import platform
import shutil
import json
import re
from subprocess import call
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))
from tools import recursiveFind
from song import *
from index import *
if platform.system() == "Linux":
from xdg.BaseDirectory import *
cachePath = os.path.join(xdg_cache_home, 'songbook')
from xdg.BaseDirectory import *
cachePath = os.path.join(xdg_cache_home, 'songbook')
else:
cachePath = os.path.join('cache', 'songbook')
cachePath = os.path.join('cache', 'songbook')
def makeCoverCache(library):
'''
@ -237,17 +226,15 @@ 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="])
"hs:l:",
["help","songbook=","library="])
except getopt.GetoptError, err:
# print help and exit
print str(err)
usage()
sys.exit(2)
songbook = None
depend = False
output = None
sbFile = None
library = './'
for o, a in opts:
@ -255,11 +242,7 @@ def main():
usage()
sys.exit()
elif o in ("-s", "--songbook"):
songbook = a
elif o in ("-d", "--depend"):
depend = True
elif o in ("-o", "--output"):
output = a
sbFile = a
elif o in ("-l", "--library"):
if not a.endswith('/'):
a += '/'
@ -267,16 +250,34 @@ def main():
else:
assert False, "unhandled option"
basename = os.path.basename(sbFile)[:-3]
texFile = basename + ".tex"
pdfFile = basename + ".pdf"
makeCoverCache(library)
if songbook and output:
f = open(songbook)
sb = json.load(f)
f.close()
f = open(sbFile)
sb = json.load(f)
f.close()
if depend:
makeDepend(sb, library, output)
else:
makeTexFile(sb, library, output)
# Make TeX file
makeTexFile(sb, library, texFile)
# First pdflatex pass
call(["pdflatex", texFile])
# Make index
sxdFiles = recursiveFind(".", basename + "*.sxd")
print sxdFiles
for sxdFile in sxdFiles:
print "processing " + sxdFile
idx = processSXD(sxdFile)
indexFile = open(sxdFile[:-3]+"sbx", "w")
indexFile.write(idx.entriesToStr())
indexFile.close()
# Second pdflatex pass
call(["pdflatex", texFile])
if __name__ == '__main__':
main()

102
sortindex.py

@ -1,9 +1,22 @@
#coding:utf8
#!/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 warnings
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': 'À',
@ -45,3 +58,88 @@ def sortkey(value):
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
utils/__init__.py

Loading…
Cancel
Save