From 639f6f42dfadbd54659a9ddd56e410e763b642c3 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sat, 29 Oct 2016 23:18:45 +0100 Subject: [PATCH 1/5] Set exit status on abcfield.py to indicate if anything found. --- abcfield.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/abcfield.py b/abcfield.py index 15d8ca2..6489096 100755 --- a/abcfield.py +++ b/abcfield.py @@ -130,6 +130,7 @@ def process(inf, options): continue found = True print(convertField(line, options)) + return found parser = optparse.OptionParser(usage="usage: %prog [options] [filename]\n\n" " Extract field data from ABC file.") @@ -148,13 +149,14 @@ parser.add_option("-s", "--starts", dest="starts", metavar="CONTENT") (options, args) = parser.parse_args() +res = False if len(args) > 0: for arg in args: try: inf = open(arg, "r") - process(inf, options) + res = res or process(inf, options) finally: inf.close() else: - process(sys.stdin, options) -sys.exit(0) + res = process(sys.stdin, options) +sys.exit(int(not res)) From 3d8f52e6398af87c20102f7d742348daa8badf2e Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 31 Oct 2016 23:45:00 +0000 Subject: [PATCH 2/5] Either abc2midi or abc2xml complains if N: or Z: is the last header. --- Session/MichaelTurnersWaltz.abc | 2 +- Session/Railway.abc | 2 +- Session/RoguesMarch.abc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Session/MichaelTurnersWaltz.abc b/Session/MichaelTurnersWaltz.abc index 7abcd72..2aa2d11 100644 --- a/Session/MichaelTurnersWaltz.abc +++ b/Session/MichaelTurnersWaltz.abc @@ -1,11 +1,11 @@ X: 1 T: Michael Turner's Waltz +Z: Cry Havoc Booke of Dottes. Creative Commons by-nc-sa 2.0 UK licenced. M: 3/4 L: 1/8 R: waltz Q: 140 K: Gmaj -Z: Cry Havoc Booke of Dottes. Creative Commons by-nc-sa 2.0 UK licenced. DGA | "G" B2 B2 "D" c2 | "G" d4 gf | "C" e2>f2 ge | "G" d2>"D" D2 GA | "G" B2 B2 "D" c2 | "G" d2>e2 cA | "G" G3 B "D" AF | "G" G3 :| AB | "D" c2>d2 cB | "D" A4 Bc | "G" d2>e2 dc | "Em" B4 gf | diff --git a/Session/Railway.abc b/Session/Railway.abc index a5e8891..5b84466 100644 --- a/Session/Railway.abc +++ b/Session/Railway.abc @@ -1,10 +1,10 @@ X: 1 T: Railway, The +N: Change: FieryClockFace.abc M: 6/8 L: 1/8 Q: 160 K: Gmaj -N: Change: FieryClockFace.abc D | "G" G2 G GBd | g2 d d2 d | "Am" e2 d c2 B | "D7" ABA FED | "G" G2 G GBd | g2 d d2 d | "C" edc "G" BAG | "D7" A3 "G" G2 :| d | "G" b2 b b2 a | g2 g g2 d | "C" e2 d efg | "D" a2 g fed | diff --git a/Session/RoguesMarch.abc b/Session/RoguesMarch.abc index e80fe7a..c73317d 100644 --- a/Session/RoguesMarch.abc +++ b/Session/RoguesMarch.abc @@ -1,10 +1,10 @@ X: 1 T: Rogues' March +N: Change: CaptainLanoesQuickMarch.abc M: 6/8 L: 1/4 Q: 160 K: Gmaj -N: Change: CaptainLanoesQuickMarch.abc "G" B B/ B/c/d/ | "C" e e Date: Mon, 31 Oct 2016 23:48:45 +0000 Subject: [PATCH 3/5] More abcfield.py updates and mark it Python 3. - Move title fixups into Python. - Move key display name expansion into Python. - Add Dottes-specific header continuation line format. None of the usual tools appears to support the official +: continuation. --- abcfield.py | 184 ++++++++++++++++++++++++++++++------------ makeBookeTunePages.sh | 19 +---- makeWeb.sh | 55 +------------ 3 files changed, 138 insertions(+), 120 deletions(-) diff --git a/abcfield.py b/abcfield.py index 6489096..e8f1501 100755 --- a/abcfield.py +++ b/abcfield.py @@ -1,8 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Extact a text field (title, by default) from a .abc file, and print it out # with any ABC accented characters converted to HTML (default) or Latex. -# Recognise continuation fields and print those too. +# +# Optionally rearrange a field into display format: +# * In Title fields, change 'sort' form such as 'Exploding Potato, The' +# to display format 'The Exploding Potato'. +# * In Key fields, translate the ABC key representation to full text, +# e.g. G#dor becomes G# Dorian. +# +# Recognise continuation header fields and print those too. The ABC standard +# defines continuation fields as starting ':+'. Regrettably none of the tools +# I am using the Booke recognise that syntax, so I am adopting a Booke +# convention of '
:+' *also* being a continuation. Note that a +# continuation is a distinct line in the field value; the value has a line +# break between it and the previous line. # import optparse @@ -87,7 +99,18 @@ accentedletters = { "ss" : ("ß", "\\ss"), } -def convertField(t, options): +abckeys = { + "m": "Minor", + "min": "Minor", + "mix": "Mixolydian", + "dor": "Dorian", + "phr": "Phrygian", + "lyd": "Lydian", + "loc": "Locrian", +} + +# Convert ABC accented chars to HTML entities or LaTex. +def convertAccents(t, latex=False): res = "" while True: p = t.partition('\\') @@ -97,66 +120,121 @@ def convertField(t, options): abc = p[2][0:2] t = p[2][2:] if abc in accentedletters: - if options.html: - res += accentedletters[abc][0] - else: + if latex: res += accentedletters[abc][1] + else: + res += accentedletters[abc][0] else: res += "\\" + abc return res -def process(inf, options): - n = options.index - found = False +# Convert Title fields from sort to display, so Bat, The->The Bat. +def convertTitleToDisplay(t): + p = t.rpartition(',') + if p[1] == "": + return t + else: + return p[2].strip() + " " + p[0].strip() + +# Convert Key field from ABC to display, so G#dor->G# Dorian. +def convertKeyToDisplay(t): + letter = t[0].upper() + accidental = "" + mode = "" + try: + accidental = t[1] + if accidental == '#' or accidental == 'b': + mode = t[2:] + else: + accidental = "" + mode = t[1:] + except IndexError: + pass + mode = mode.strip().lower() + return letter + accidental + ' ' + abckeys.get(mode, "Major") + +# Return the raw text for a given field. Optionally the nth field is taken, +# or the field data must start with a designated string to be recognised. +def getFieldText(inf, field, n = 1, starts = None): + res = None for line in inf: line = line.strip() if len(line) > 2 and line[1] == ':': - if found: - if line[0] != '+': - break - line = line[2:].strip() - elif line[0] == options.field: - if n > 1: - n = n - 1 + if line[0] == "+" or (line[0] == field and line[2] == "+"): + if not res: continue + if line[0] == "+": + line = line[2:] else: - line = line[2:].strip() - if len(options.starts) > 0: - if line.find(options.starts) == 0: - line = line[len(options.starts):].strip() - else: - continue + line = line[3:] + res = res + '\n' + line.strip() else: - continue - found = True - print(convertField(line, options)) - return found + if res: + break + if line[0] == field: + line = line[2:].strip() + if starts: + if line.find(starts) != 0: + continue + line = line[len(starts):].strip() + if n > 1: + n = n - 1 + continue + res = line + return res -parser = optparse.OptionParser(usage="usage: %prog [options] [filename]\n\n" - " Extract field data from ABC file.") -parser.add_option("-f", "--field", dest="field", default="T", - help="extract the field FIELD", metavar="FIELD") -parser.add_option("-l", "--latex", dest="latex", - action="store_true", default=False, - help="convert special characters for LaTeX") -parser.add_option("-n", "--index", dest="index", - action="store", type="int", default=1, - help="report INDEXth value [default: %default]", - metavar="INDEX") -parser.add_option("-s", "--starts", dest="starts", - action="store", type="string", default="", - help="report only if line starts CONTENT and remove CONTENT", - metavar="CONTENT") -(options, args) = parser.parse_args() +# Return display text for a given field. +def getFieldDisplayText(inf, field, n = 1, starts = None, latex = False): + res = getFieldText(inf, field, n, starts) + if res: + if field.upper() == "T": + res = convertTitleToDisplay(res) + elif field.upper() == "K": + res = convertKeyToDisplay(res) + res = convertAccents(res, latex) + return res -res = False -if len(args) > 0: - for arg in args: - try: - inf = open(arg, "r") - res = res or process(inf, options) - finally: - inf.close() -else: - res = process(sys.stdin, options) -sys.exit(int(not res)) +if __name__ == "__main__": + def process(inf, options): + if options.display: + line = getFieldDisplayText(inf, options.field, options.index, options.starts, options.latex) + else: + line = getFieldText(inf, options.field, options.index, options.starts) + if line: + print(line) + return True + else: + return False + + # execute only if run as a script + parser = optparse.OptionParser(usage="usage: %prog [options] [filename]\n\n" + " Extract field data from ABC file.") + parser.add_option("-f", "--field", dest="field", default="T", + help="extract the field FIELD", metavar="FIELD") + parser.add_option("-l", "--latex", dest="latex", + action="store_true", default=False, + help="convert special characters for LaTeX") + parser.add_option("-d", "--display", dest="display", + action="store_true", default=False, + help="convert to display text") + parser.add_option("-n", "--index", dest="index", + action="store", type="int", default=1, + help="report INDEXth value [default: %default]", + metavar="INDEX") + parser.add_option("-s", "--starts", dest="starts", + action="store", type="string", default=None, + help="report only if line starts CONTENT and remove CONTENT", + metavar="CONTENT") + (options, args) = parser.parse_args() + + res = False + if len(args) > 0: + for arg in args: + try: + inf = open(arg, "r") + res = res or process(inf, options) + finally: + inf.close() + else: + res = process(sys.stdin, options) + sys.exit(int(not res)) diff --git a/makeBookeTunePages.sh b/makeBookeTunePages.sh index 9cc1830..9db53c6 100755 --- a/makeBookeTunePages.sh +++ b/makeBookeTunePages.sh @@ -8,13 +8,6 @@ # makeGraphics.sh to make these. # -# Restore titles like 'Exploding Potato, The' to the -# expected 'The Exploding Potato'. -fixtitle() -{ - retval=`echo "$1" | sed -e "s/\(.*\), *\(.*\)/\2 \1/"` -} - if [ $# != 1 ]; then echo "Usage: makeBookeTunePages.sh " exit 1 @@ -38,12 +31,8 @@ find $booke -name "*.abc" | sort | while read filename do name=`basename $filename .abc` - title=`$dir/abcfield.py --field T --latex $filename` - fixtitle "$title" - title=$retval - subtitle=`$dir/abcfield.py --index 2 --field T --latex $filename` - fixtitle "$subtitle" - subtitle=$retval + title=`$dir/abcfield.py --field T --latex --display $filename` + subtitle=`$dir/abcfield.py --index 2 --field T --latex --display $filename` composer=`$dir/abcfield.py --field C --latex $filename` changefile=`$dir/abcfield.py --field N --starts "Change:" $filename` @@ -51,9 +40,7 @@ find $booke -name "*.abc" | sort | changetitle="" if [ -n "$changefile" ]; then changename=`basename $changefile .abc` - changetitle=`$dir/abcfield.py --field T --latex $booke/$changefile` - fixtitle "$changetitle" - changetitle=$retval + changetitle=`$dir/abcfield.py --field T --latex --display $booke/$changefile` fi credit=`$dir/abcfield.py --field N --starts "Credit:" $filename` diff --git a/makeWeb.sh b/makeWeb.sh index 3d6aa92..ba0a2b3 100755 --- a/makeWeb.sh +++ b/makeWeb.sh @@ -7,44 +7,6 @@ #set -x -# Restore titles like 'Exploding Potato, The' to the -# expected 'The Exploding Potato'. -fixtitle() -{ - retval=`echo "$1" | sed -e "s/\(.*\), *\(.*\)/\2 \1/"` -} - -# Format a key in ABC (G, Gmin, etc.) in standard presentation format. -fixkey() -{ - letter=${1:0:1} - accidental=${1:1:1} - if [ "$accidental" != "#" -a "$accidental" != "b" ]; then - accidental="" - mode=${1:1:3} - else - mode=${1:2:3} - fi - mode=${mode,,} - mode=${mode/ //g} - if [ "$mode" = "m" -o "$mode" = "min" ]; then - mode="Minor" - elif [ "$mode" = "mix" ]; then - mode="Mixolydian" - elif [ "$mode" = "dor" ]; then - mode="Dorian" - elif [ "$mode" = "phr" ]; then - mode="Phrygian" - elif [ "$mode" = "lyd" ]; then - mode="Lydian" - elif [ "$mode" = "loc" ]; then - mode="Locrian" - else - mode="Major" - fi - retval="${letter}${accidental} ${mode}" -} - if [ $# -lt 2 -o $# -gt 3 ]; then echo "Usage: makeWeb.sh []" exit 1 @@ -105,22 +67,15 @@ find $bookedir -name "*.abc" | sort | name=`basename $filename .abc` # Extract items to substitute in the web page. - title=`$dir/abcfield.py --field T $filename` - fixtitle "$title" - title=$retval - subtitle=`$dir/abcfield.py --index 2 --field T $filename` - fixtitle "$subtitle" - subtitle=$retval + title=`$dir/abcfield.py --field T --display $filename` + subtitle=`$dir/abcfield.py --index 2 --field T --display $filename` composer=`$dir/abcfield.py --field C $filename` changefile=`$dir/abcfield.py --field N --starts "Change:" $filename` changetitle="" changevisibility="no" if [ -n "$changefile" ]; then - changetitle=`$dir/abcfield.py --field T $bookedir/$changefile` + changetitle=`$dir/abcfield.py --field T --display $bookedir/$changefile` changevisibility="yes" - - fixtitle "$changetitle" - changetitle=$retval fi credit=`$dir/abcfield.py --field N --starts "Credit:" $filename` creditvisibility="no" @@ -128,9 +83,7 @@ find $bookedir -name "*.abc" | sort | creditvisibility="yes" fi lastchanged=`hg log --limit 1 --template "{date|shortdate}" $masterbookedir/${name}.abc` - key=`$dir/abcfield.py --field K $filename` - fixkey $key - key=$retval + key=`$dir/abcfield.py --field K --display $filename` # Copy the ABC into the web. cp $filename $webdir From fdd073b82784902b91ac78567da5f56d71f4ceaf Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 31 Oct 2016 23:55:28 +0000 Subject: [PATCH 4/5] Convert remaining Python to Python3 and add +: support. --- abcfirstline.py | 12 ++++++------ abcrange.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/abcfirstline.py b/abcfirstline.py index ee06f21..f83a2c8 100755 --- a/abcfirstline.py +++ b/abcfirstline.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Write out a modified version of a .abc file with just the data # to print the first line of the music only. @@ -8,23 +8,23 @@ import sys def process(inf): continued = False - print "X:1" + print("X:1") for line in inf: line = line.strip() # If it is empty or starts "%", ignore it. if len(line) == 0 or line[0] == "%": continue - # Is it a header line? I.e. does it start LETTER COLON? + # Is it a header line? I.e. does it start LETTER (or +) COLON? # If so, output only ones we need. start = line[:2] - if len(start) > 1 and start[1] == ":" and start[0].isalpha(): + if len(start) > 1 and start[1] == ":" and (start[0].isalpha() or start[0] == '+'): if start[0] in ["M", "K", "L"]: - print line + print(line) # Output line. If it is a continuation, output at most one # continuation. else: - print line + print(line) if continued or line[-1] != "\\": break else: diff --git a/abcrange.py b/abcrange.py index 2c42e4f..834a579 100755 --- a/abcrange.py +++ b/abcrange.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Find the range of a tune. Do minimal parsing of an ABC input file # and print the lowest and highest notes therein. Accidentals are @@ -29,7 +29,7 @@ def process(filename, inf): # Is it a header line? I.e. does it start LETTER COLON? # If so, ignore. start = line[:2] - if len(start) > 1 and start[1] == ":" and start[0].isalpha(): + if len(start) > 1 and start[1] == ":" and (start[0].isalpha() or start[0] == '+'): continue # Tune line. @@ -82,7 +82,7 @@ def process(filename, inf): lowest = note note = 0 - print "{0}: {1} {2}".format(filename, highest, lowest) + print("{0}: {1} {2}".format(filename, highest, lowest)) if len(sys.argv) > 1: for arg in sys.argv[1:]: From 262ee6c3e4b15a9d545cb8468e6216b546004fbb Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Wed, 2 Nov 2016 00:21:18 +0000 Subject: [PATCH 5/5] Replace sed substitution with Python templating for HTML and LaTeX output. --- abcfield.py | 2 +- abctemplate.py | 88 +++++++++++++++++++++++++++++++++++++++ dottes.html.learnertune | 60 +++++++++++++------------- dottes.html.tune | 42 +++++++++---------- dottes.html.tuneindex | 10 ++--- dottes.tex.firstline-tune | 1 + dottes.tex.tune | 1 + makeBookeTunePages.sh | 19 +-------- makeWeb.sh | 55 +++--------------------- 9 files changed, 155 insertions(+), 123 deletions(-) create mode 100755 abctemplate.py create mode 100644 dottes.tex.firstline-tune create mode 100644 dottes.tex.tune diff --git a/abcfield.py b/abcfield.py index e8f1501..b0aec83 100755 --- a/abcfield.py +++ b/abcfield.py @@ -156,7 +156,7 @@ def convertKeyToDisplay(t): # Return the raw text for a given field. Optionally the nth field is taken, # or the field data must start with a designated string to be recognised. def getFieldText(inf, field, n = 1, starts = None): - res = None + res = "" for line in inf: line = line.strip() if len(line) > 2 and line[1] == ':': diff --git a/abctemplate.py b/abctemplate.py new file mode 100755 index 0000000..52e0be2 --- /dev/null +++ b/abctemplate.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# +# Fill in a template with data from fields in an ABC file. +# Fields have any ABC accented characters converted to HTML (default) or Latex. +# +# Rearrange some field contents into display format: +# * In Title fields, change 'sort' form such as 'Exploding Potato, The' +# to display format 'The Exploding Potato'. +# * In Key fields, translate the ABC key representation to full text, +# e.g. G#dor becomes G# Dorian. +# +# Recognise continuation header fields and print those too. The ABC standard +# defines continuation fields as starting ':+'. Regrettably none of the tools +# I am using the Booke recognise that syntax, so I am adopting a Booke +# convention of '
:+' *also* being a continuation. Note that a +# continuation is a distinct line in the field value; the value has a line +# break between it and the previous line. +# +# Templates are read from file, and are in Python standard library format. +# The following values are substituted: +# * name. The file base name. Base filename without extension. +# * title. The tune title. +# * subtitle. The tune subtitle (second Title field), if any. +# * composer. The tune composer. +# * key. The tune key. +# * changefile. The name of the 'change' file, if any. +# * changename. The change file base name. +# * changetitle. The change file tune title. +# * changevisibility. "yes" if there's a change value, otherwise "no". +# * credit. The 'credit' value. +# * creditvisibility. "yes" if there's a credit value, otherwise "no". +# + +import argparse +import pathlib +import string + +from abcfield import getFieldDisplayText + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Substitute values from ABC file into template.') + parser.add_argument('-l', '--latex', dest='latex', + action='store_true', + help='output LaTeX formatted values (default is HTML)') + parser.add_argument('-t', '--template', dest='template', + type=argparse.FileType('r'), + required=True, + help='template file') + parser.add_argument('-v', '--value', dest='values', action="append", + default=[], help='define var=value items for templater') + parser.add_argument('input', type=argparse.FileType('r'), + help='input ABC file') + args = parser.parse_args() + + with args.input as f: + lines = f.readlines() + + input_path = pathlib.Path(args.input.name) + + vars = dict() + vars["changename"] = "" + vars["changetitle"] = "" + vars["changevisibility"] = "no" + vars["creditvisibility"] = "no" + + vars["name"] = input_path.stem + vars["title"] = getFieldDisplayText(lines, "T", latex=args.latex) + vars["subtitle"] = getFieldDisplayText(lines, "T", n=2, latex=args.latex) + vars["composer"] = getFieldDisplayText(lines, "C", latex=args.latex) + vars["key"] = getFieldDisplayText(lines, "K", latex=args.latex) + vars["changefile"] = getFieldDisplayText(lines, "N", starts="Change:", latex=args.latex) + vars["credit"] = getFieldDisplayText(lines, "N", starts="Credit:", latex=args.latex) + + if vars["changefile"]: + vars["changevisibility"] = "yes" + vars["changename"] = pathlib.Path(vars["changefile"]).stem + cf = pathlib.Path(input_path.parent, vars["changefile"]) + with cf.open() as f: + vars["changetitle"] = getFieldDisplayText(f, "T", latex=args.latex) + + if vars["credit"]: + vars["creditvisibility"] = "yes" + + for val in args.values: + keyval = val.partition("=") + vars[keyval[0]] = keyval[2] + + print(string.Template(args.template.read()).substitute(vars)) diff --git a/dottes.html.learnertune b/dottes.html.learnertune index a1baff2..2b7045f 100644 --- a/dottes.html.learnertune +++ b/dottes.html.learnertune @@ -2,7 +2,7 @@ - Cry Havoc tunes - learning @TITLE@ + Cry Havoc tunes - learning ${title} @@ -33,7 +33,7 @@
- + Dottes @@ -43,15 +43,15 @@
-

@TITLE@

-

@SUBTITLE@

+

${title}

+

${subtitle}

- @COMPOSER@ + ${composer}
-

@TITLE@ is in the key of @KEY@. +

${title} is in the key of ${key}.