diff --git a/patacrep/data/examples/example-all.sb b/patacrep/data/examples/example-all.sb index ca35b622..a9599a26 100644 --- a/patacrep/data/examples/example-all.sb +++ b/patacrep/data/examples/example-all.sb @@ -1,6 +1,6 @@ { "bookoptions" : [ - "importantdiagramonly", + "diagram", "repeatchords", "lilypond", "pictures" diff --git a/patacrep/data/examples/songs/chords.sgc b/patacrep/data/examples/songs/chords.sgc index 96390d2b..8779a140 100644 --- a/patacrep/data/examples/songs/chords.sgc +++ b/patacrep/data/examples/songs/chords.sgc @@ -3,6 +3,11 @@ {title: Chords testing} {subtitle: Test of the chords specification and LaTeX translation} +{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} +{define: A frets x 0 2 2 2 0 fingers - - 1 2 3 -} +{define: C#sus4 base-fret 4 frets x x 3 3 4 1} +{define: Bb frets x 1 3 3 3 1} + [A]Simple [Bb]Bémol [C#]Dièse diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 4e20bd97..5db13afc 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -119,7 +119,7 @@ class Space(LineElement): class ChordList(LineElement): """A list of chords.""" - _template = "chord" + _template = "chordlist" def __init__(self, *chords): self.chords = chords @@ -129,9 +129,11 @@ class ChordList(LineElement): [str(chord) for chord in self.chords] )) -class Chord: +class Chord(AST): """A chord.""" + _template = "chord" + def __init__( self, key, @@ -164,6 +166,50 @@ class Chord: text += self.bassalteration return text +class Define(AST): + """A chord definition. + + Attributes: + + .. attribute:: key + The key, as a :class:`Chord` object. + .. attribute:: basefret + The base fret, as an integer. Can be `None` if no base fret is defined. + .. attribute:: frets + The list of frets, as a list of integers, or `None`, if this fret is not to be played. + .. attribute:: fingers + The list of fingers to use on frets, as a list of integers, or `None` + if no information is given (this string is not played, or is played + open). Can be `None` if not defined. + """ + _template = "define" + inline = True + + def __init__(self, key, basefret, frets, fingers): + self.key = key + self.basefret = basefret # Can be None + self.frets = frets + self.fingers = fingers # Can be None + + def __str__(self): + text = str(self.key) + if self.basefret is not None: + text += " base-fret " + str(self.basefret) + text += " frets" + for fret in self.frets: + if fret is None: + text += " x" + else: + text += " " + str(fret) + if self.fingers: + text += " fingers" + for finger in self.fingers: + if finger is None: + text += " -" + else: + text += " " + str(finger) + return "{{define: {}}}".format(text) + class Verse(AST): """A verse (or bridge, or chorus)""" _template = "verse" diff --git a/patacrep/songs/chordpro/data/latex/content_chord.tex b/patacrep/songs/chordpro/data/latex/content_chord.tex index c3f62f22..29a1cc30 100644 --- a/patacrep/songs/chordpro/data/latex/content_chord.tex +++ b/patacrep/songs/chordpro/data/latex/content_chord.tex @@ -1,16 +1,11 @@ -\[ - (*- for chord in content.chords -*) - (* if not loop.first *) (* endif *) - ((- chord.key -)) - (* if chord.alteration == '#' *)#(* endif -*) - (* if chord.alteration == 'b' *)&(* endif -*) - (* if chord.modifier *)((chord.modifier))(* endif -*) - (* if chord.add_note *)((chord.add_note))(* endif -*) - (* if chord.basskey -*) - / - ((- chord.basskey -)) - (* if chord.bassalteration == '#' *)#(* endif -*) - (* if chord.bassalteration == 'b' *)&(* endif -*) - (* endif -*) - (* endfor -*) -] +((- content.key -)) +(* if content.alteration == '#' *)#(* endif -*) +(* if content.alteration == 'b' *)&(* endif -*) +(* if content.modifier *)((content.modifier))(* endif -*) +(* if content.addnote *)((content.addnote))(* endif -*) +(* if content.basskey -*) + / + ((- content.basskey -)) + (* if content.bassalteration == '#' *)#(* endif -*) + (* if content.bassalteration == 'b' *)&(* endif -*) +(* endif -*) diff --git a/patacrep/songs/chordpro/data/latex/content_chordlist.tex b/patacrep/songs/chordpro/data/latex/content_chordlist.tex new file mode 100644 index 00000000..d5ad7c3c --- /dev/null +++ b/patacrep/songs/chordpro/data/latex/content_chordlist.tex @@ -0,0 +1,6 @@ +\[ + (*- for chord in content.chords -*) + (* if not loop.first *) (* endif -*) + (( render(chord) -)) + (* endfor -*) +] diff --git a/patacrep/songs/chordpro/data/latex/content_define.tex b/patacrep/songs/chordpro/data/latex/content_define.tex new file mode 100644 index 00000000..ca899ff3 --- /dev/null +++ b/patacrep/songs/chordpro/data/latex/content_define.tex @@ -0,0 +1,24 @@ +\gtab{ + ((- render(content.key) -)) +}{ + (*- if content.basefret -*) + ((content.basefret)): + (*- endif -*) + (*- for string in content.frets -*) + (*- if string is none -*) + X + (*- else -*) + (( string -)) + (* endif -*) + (* endfor -*) + (* if content.fingers -*) + : + (*- for finger in content.fingers -*) + (* if finger is none -*) + 0 + (*- else -*) + (( finger -)) + (* endif -*) + (* endfor -*) + (* endif -*) +} diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index de6b5a28..91a0cb41 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -36,6 +36,50 @@ def _parse_chords(string): TODO yield ast.Chord(**match.groupdict()) +def _parse_define(key, basefret, frets, fingers): + """Parse a `{define: KEY base-fret BASE frets FRETS fingers FINGERS}` directive + + Return a :class:`ast.Define` object. + """ + # pylint: disable=too-many-branches + key = list(_parse_chords(key)) + if len(key) != 1: + TODO + else: + processed_key = key[0] + + if basefret is None: + processed_basefret = None + else: + processed_basefret = int(basefret) + + if frets is None: + processed_frets = None + else: + processed_frets = [] + for fret in frets.split(): + if fret == "x": + processed_frets.append(None) + else: + processed_frets.append(int(fret)) + + if fingers is None: + processed_fingers = None + else: + processed_fingers = [] + for finger in fingers.split(): + if finger == '-': + processed_fingers.append(None) + else: + processed_fingers.append(int(finger)) + + return ast.Define( + key=processed_key, + basefret=processed_basefret, + frets=processed_frets, + fingers=processed_fingers, + ) + class ChordproParser(Parser): """ChordPro parser class""" # pylint: disable=too-many-public-methods @@ -91,6 +135,21 @@ class ChordproParser(Parser): else: symbols[4].keyword = symbols[3] symbols[0] = symbols[4] + if symbols[0].keyword == 'define': + match = re.compile( + r""" + (?P[^\ ]*)\ * + (base-fret\ *(?P[2-9]))?\ * + frets\ *(?P((\d+|x)\ *)+)\ * + (fingers\ *(?P(([0-4-])\ *)*))? + """, + re.VERBOSE + ).match(symbols[0].argument) + + if match is None: + TODO + + symbols[0] = _parse_define(**match.groupdict()) @staticmethod def p_directive_next(symbols): diff --git a/patacrep/songs/chordpro/test/customchords.sgc b/patacrep/songs/chordpro/test/customchords.sgc new file mode 100644 index 00000000..b7192c87 --- /dev/null +++ b/patacrep/songs/chordpro/test/customchords.sgc @@ -0,0 +1,2 @@ +{define: E4 base-fret 7 frets 0 1 3 3 x x} +{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -} diff --git a/patacrep/songs/chordpro/test/customchords.txt b/patacrep/songs/chordpro/test/customchords.txt new file mode 100644 index 00000000..c8d99d13 --- /dev/null +++ b/patacrep/songs/chordpro/test/customchords.txt @@ -0,0 +1,3 @@ +======== +{define: E4 base-fret 7 frets 0 1 3 3 x x} +{define: E5 base-fret 7 frets 0 1 3 3 x x fingers - 1 2 3 - -}