diff abcfield.py @ 586:daa3b76bd11f

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.
author Jim Hague <jim.hague@acm.org>
date Mon, 31 Oct 2016 23:48:45 +0000
parents 696c461c8dc0
children afc031477784
line wrap: on
line diff
--- a/abcfield.py	Mon Oct 31 23:45:00 2016 +0000
+++ b/abcfield.py	Mon Oct 31 23:48:45 2016 +0000
@@ -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 '<header>:+' *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 @@
     "ss" : ("&szlig;", "\\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 @@
         abc = p[2][0:2]
         t = p[2][2:]
         if abc in accentedletters:
-            if options.html:
+            if latex:
+                res += accentedletters[abc][1]
+            else:
                 res += accentedletters[abc][0]
-            else:
-                res += accentedletters[abc][1]
         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] != '+':
+            if line[0] == "+" or (line[0] == field and line[2] == "+"):
+                if not res:
+                    continue
+                if line[0] == "+":
+                    line = line[2:]
+                else:
+                    line = line[3:]
+                res = res + '\n' + line.strip()
+            else:
+                if res:
                     break
-                line = line[2:].strip()
-            elif line[0] == options.field:
-                if n > 1:
-                    n = n - 1
-                    continue
-                else:
+                if line[0] == field:
                     line = line[2:].strip()
-                    if len(options.starts) > 0:
-                        if line.find(options.starts) == 0:
-                            line = line[len(options.starts):].strip()
-                        else:
+                    if starts:
+                        if line.find(starts) != 0:
                             continue
-            else:
-                continue
-            found = True
-            print(convertField(line, options))
-    return found
+                        line = line[len(starts):].strip()
+                    if n > 1:
+                        n = n - 1
+                        continue
+                    res = line
+    return res
+
+# 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
 
-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()
+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
 
-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))
+    # 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))