diff --git a/chordsheet/document.py b/chordsheet/document.py
index 41c2b89..f76bb77 100644
--- a/chordsheet/document.py
+++ b/chordsheet/document.py
@@ -119,7 +119,7 @@ class Document:
blockChord = c
break
if blockChord is None:
- exit("Chord {c} does not match any chord in {l}.".format(
+ raise ValueError("Chord {c} does not match any chord in {l}.".format(
c=blockChordName, l=self.chordList))
else:
blockChord = None
@@ -144,11 +144,12 @@ class Document:
self.tempo = (root.find('tempo').text if root.find(
'tempo') is not None else None)
- def newFromXML(filepath):
+ @classmethod
+ def newFromXML(cls, filepath):
"""
Create a new Document object directly from an XML file.
"""
- doc = Document()
+ doc = cls()
doc.loadXML(filepath)
return doc
@@ -200,3 +201,92 @@ class Document:
tree = ET.ElementTree(root)
tree.write(filepath)
+
+ def loadCSMacro(self, filepath):
+ """
+ Read a Chordsheet Macro file and import its contents.
+ """
+ self.chordList = []
+ self.sectionList = []
+
+ aliasTable = {}
+
+ def chord(args):
+ argList = args.split(" ")
+ chordName = argList.pop(0)
+
+ self.chordList.append(Chord(parseName(chordName)))
+
+ argIter = iter(argList)
+ subCmdsArgs = list(zip(argIter, argIter))
+
+ for subCmd, arg in subCmdsArgs:
+ if subCmd == "alias":
+ aliasTable[arg] = chordName
+ else:
+ self.chordList[-1].voicings[subCmd] = parseFingering(arg, subCmd)
+
+ def section(args):
+ blockList = []
+
+ sectionName, blocks = [arg.strip() for arg in args.split("\n", 1)]
+
+ self.sectionList.append(Section(name=sectionName))
+
+ for b in blocks.split():
+ blockParams = b.split(",")
+ blockLength = float(blockParams[1])
+
+ if blockParams[0] in aliasTable:
+ blockChordName = aliasTable[blockParams[0]]
+ else:
+ blockChordName = blockParams[0]
+
+ blockChordName = parseName(blockChordName) if blockChordName not in ["NC", "X"] else None
+
+ blockChord = None
+
+ if blockChordName:
+ for c in self.chordList:
+ if c.name == blockChordName:
+ blockChord = c
+ break
+ if blockChord is None:
+ raise ValueError("Chord {c} does not match any chord in {l}.".format(
+ c=blockChordName, l=self.chordList))
+
+ blockList.append(Block(blockLength, chord=blockChord))
+
+ self.sectionList[-1].blockList = blockList
+
+ with open(filepath, 'r') as f:
+ cmatext = f.read()
+
+ cmaCmdsArgs = [statement.split(" ", 1) for statement in \
+ (rawStatement.strip() for rawStatement in cmatext.split("\\")[1:])]
+
+ for cmd, args in cmaCmdsArgs:
+ if cmd == "chordsheet":
+ # There's only one version so no need to do anything with this
+ pass
+ elif cmd == "title":
+ self.title = args
+ elif cmd == "subtitle":
+ self.subtitle = args
+ elif cmd == "arranger":
+ self.arranger = args
+ elif cmd == "composer":
+ self.composer = args
+ elif cmd == "timesig":
+ self.timeSignature = int(args)
+ elif cmd == "tempo":
+ self.tempo = args
+ elif cmd == "chord":
+ chord(args)
+ elif cmd == "section":
+ section(args)
+ elif cmd in ["!", "rem"]:
+ # Simply ignore comments
+ pass
+ else:
+ raise ValueError(f"Command {cmd} not understood.")
\ No newline at end of file
diff --git a/examples/kissoflife.cma b/examples/kissoflife.cma
index 6a152e6..289c437 100644
--- a/examples/kissoflife.cma
+++ b/examples/kissoflife.cma
@@ -1,4 +1,5 @@
\chordsheet 1
+\rem This is a comment to say that the first line isn't really needed (as there's only one version of Chordsheet Macro so far)
\title Kiss of Life
\subtitle Sade
@@ -17,5 +18,7 @@
AM9,8 F#m11,8 DM7,1.5
C#m7,2 Bm7,4.5 F#m11,8
+\! This is another comment. Comments may not exist in the middle of other statements.
+
\section Chorus and bridge
Bm7,3.5 F,4.5 Bm7,3.5 F#m11,4.5
\ No newline at end of file
diff --git a/examples/kissoflife.xml b/examples/kissoflife.xml
index 41b6fc0..4a9fd78 100644
--- a/examples/kissoflife.xml
+++ b/examples/kissoflife.xml
@@ -1 +1 @@
-Kiss of LifeSadeIvan HolmesSade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman4AM9A,B,C♯,EF♯m11A,B,C♯,EDM7A,C♯,F♯C♯m7G♯,B,EBm7A,D,F♯8.0AM98.0F♯m111.5DM72.0C♯m74.5Bm78.0F♯m113.5Bm74.5F♯m113.5Bm74.5F♯m11
\ No newline at end of file
+Kiss of LifeSadeIvan HolmesSade Adu, Paul S. Denman, Andrew Hale, Stuart Matthewman4120AM9A,B,C♯,EF♯m11A,B,C♯,EDM7A,C♯,F♯C♯m7G♯,B,EBm7A,D,F♯8.0AM98.0F♯m111.5DM72.0C♯m74.5Bm78.0F♯m113.5Bm74.5F♯m113.5Bm74.5F♯m11
\ No newline at end of file
diff --git a/gui.py b/gui.py
index f38369e..f5addeb 100755
--- a/gui.py
+++ b/gui.py
@@ -314,7 +314,7 @@ class DocumentWindow(QMainWindow):
def menuFileOpenAction(self):
if self.saveWarning(): # ask the user if they want to save
filePath = QFileDialog.getOpenFileName(self.window.tabWidget, 'Open file', self.getPath(
- "workingPath"), "Chordsheet ML files (*.xml *.cml)")[0]
+ "workingPath"), "Chordsheet Markup Language files (*.xml *.cml);;Chordsheet Macro files (*.cma)")[0]
if filePath:
self.openFile(filePath)
@@ -323,7 +323,14 @@ class DocumentWindow(QMainWindow):
Opens a file from a file path and sets up the window accordingly.
"""
self.currentFilePath = filePath
- self.doc.loadXML(self.currentFilePath)
+
+ fileExt = os.path.splitext(self.currentFilePath)[1].lower()
+
+ if fileExt == ".cma":
+ self.doc.loadCSMacro(self.currentFilePath)
+ else: # if fileExt in [".xml", ".cml"]:
+ self.doc.loadXML(self.currentFilePath)
+
self.lastDoc = copy(self.doc)
self.setPath("workingPath", self.currentFilePath)
self.UIInitDocument()
@@ -570,7 +577,8 @@ class DocumentWindow(QMainWindow):
if b.chord:
if b.chord.name == oldName:
b.chord.name = cName
- self.window.blockTableView.populate(self.currentSection.blockList)
+ if self.currentSection and self.currentSection.blockList:
+ self.window.blockTableView.populate(self.currentSection.blockList)
self.clearChordLineEdits()
def removeSectionAction(self):
diff --git a/linux.spec b/linux.spec
new file mode 100644
index 0000000..5b53784
--- /dev/null
+++ b/linux.spec
@@ -0,0 +1,37 @@
+# -*- mode: python ; coding: utf-8 -*-
+
+block_cipher = None
+
+
+a = Analysis(['gui.py'],
+ pathex=['/home/ivan/Code/chordsheet'],
+ binaries=[],
+ datas=[
+ ('fonts', 'fonts'),
+ ('ui', 'ui')
+ ],
+ hiddenimports=[],
+ hookspath=[],
+ runtime_hooks=[],
+ excludes=[],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False)
+pyz = PYZ(a.pure, a.zipped_data,
+ cipher=block_cipher)
+exe = EXE(pyz,
+ a.scripts,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ [],
+ name='chordsheet',
+ icon='ui/icon.ico',
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ upx_exclude=[],
+ runtime_tmpdir=None,
+ console=False )