Browse Source

[WIP] Does not work

pull/112/head
Louis 9 years ago
parent
commit
fc328658ce
  1. 6
      patacrep/latex/lexer.py
  2. 4
      patacrep/latex/syntax.py
  3. 108
      patacrep/songs/chordpro/ast.py
  4. 1
      patacrep/songs/chordpro/data/chordpro/content_endofline
  5. 2
      patacrep/songs/chordpro/data/chordpro/content_newline
  6. 2
      patacrep/songs/chordpro/data/chordpro/song_header
  7. 1
      patacrep/songs/chordpro/data/html/content_endofline
  8. 2
      patacrep/songs/chordpro/data/html/content_newline
  9. 2
      patacrep/songs/chordpro/data/html/song_header
  10. 2
      patacrep/songs/chordpro/data/latex/content_endofline
  11. 3
      patacrep/songs/chordpro/data/latex/content_newline
  12. 2
      patacrep/songs/chordpro/data/latex/song
  13. 6
      patacrep/songs/chordpro/lexer.py
  14. 41
      patacrep/songs/chordpro/syntax.py
  15. 4
      test/test_chordpro/metadata.sgc
  16. 4
      test/test_chordpro/metadata.source
  17. 5
      test/test_chordpro/metadata.tex
  18. 41
      test/test_chordpro/newline.sgc
  19. 32
      test/test_chordpro/newline.source
  20. 61
      test/test_chordpro/newline.tex

6
patacrep/latex/lexer.py

@ -12,7 +12,7 @@ tokens = (
'LBRACE',
'RBRACE',
'COMMAND',
'NEWLINE',
'ENDOFLINE',
'COMMA',
'EQUAL',
'CHARACTER',
@ -34,7 +34,7 @@ class SimpleLexer:
t_LBRACE = r'{'
t_RBRACE = r'}'
t_COMMAND = r'\\([@a-zA-Z]+|[^\\])'
t_NEWLINE = r'\\\\'
t_ENDOFLINE = r'\\\\'
SPECIAL_CHARACTERS = (
t_LBRACKET +
t_RBRACKET +
@ -59,7 +59,7 @@ class SimpleLexer:
# Define a rule so we can track line numbers
@staticmethod
def t_newline(token):
def t_endofline(token):
r'\n+'
token.lexer.lineno += len(token.value)

4
patacrep/latex/syntax.py

@ -27,7 +27,7 @@ class LatexParser(Parser):
"""expression : brackets expression
| braces expression
| command expression
| NEWLINE expression
| ENDOFLINE expression
| beginsong expression
| word expression
| SPACE expression
@ -172,7 +172,7 @@ class LatexParser(Parser):
@staticmethod
def p_titles_next(symbols):
"""titles_next : NEWLINE title titles_next
"""titles_next : ENDOFLINE title titles_next
| empty
"""
if len(symbols) == 2:

108
patacrep/songs/chordpro/ast.py

@ -2,47 +2,11 @@
# pylint: disable=too-few-public-methods
from collections import OrderedDict
import logging
LOGGER = logging.getLogger()
class OrderedLifoDict:
"""Ordered (LIFO) dictionary.
Mimics the :class:`dict` dictionary, with:
- dictionary is ordered: the order the keys are kept (as with
:class:`collections.OrderedDict`), excepted that:
- LIFO: the last item is reterned first when iterating.
"""
def __init__(self, default=None):
if default is None:
self._keys = []
self._values = {}
else:
self._keys = list(default.keys())
self._values = default.copy()
def values(self):
"""Same as :meth:`dict.values`."""
for key in self:
yield self._values[key]
def __iter__(self):
yield from self._keys
def __setitem__(self, key, value):
if key not in self._keys:
self._keys.insert(0, key)
self._values[key] = value
def __getitem__(self, key):
return self._values[key]
def get(self, key, default=None):
"""Same as :meth:`dict.get`."""
return self._values.get(key, default)
def _indent(string):
"""Return and indented version of argument."""
return "\n".join([" {}".format(line) for line in string.split('\n')])
@ -100,8 +64,12 @@ class Line(AST):
self.line = []
def prepend(self, data):
"""Add an object at the beginning of line."""
self.line.insert(0, data)
"""Add an object at the beginning of line.
Does nothing if argument is `None`.
"""
if data is not None:
self.line.insert(0, data)
return self
def strip(self):
@ -109,14 +77,17 @@ class Line(AST):
while True:
if not self.line:
return self
if isinstance(self.line[0], Space):
if isinstance(self.line[0], Space) or isinstance(self.line[0], Error):
del self.line[0]
continue
if isinstance(self.line[-1], Space):
if isinstance(self.line[-1], Space) or isinstance(self.line[-1], Error):
del self.line[-1]
continue
return self
def is_empty(self):
return len(self.strip().line) == 0
class LineElement(AST):
"""Something present on a line."""
# pylint: disable=abstract-method
@ -206,26 +177,29 @@ class Song(AST):
def __init__(self, filename):
super().__init__()
self.content = []
self.meta = OrderedLifoDict()
self.meta = OrderedDict()
self._authors = []
self._titles = []
self._subtitles = []
self._keys = []
self.filename = filename
def add(self, data):
"""Add an element to the song"""
if isinstance(data, Error):
return self
pass
elif data is None:
# New line
if not (self.content and isinstance(self.content[0], Newline)):
self.content.insert(0, Newline())
elif isinstance(data, Line):
if not (self.content and isinstance(self.content[0], EndOfLine)):
self.content.insert(0, EndOfLine())
elif isinstance(data, Line) or isinstance(data, NewLine):
# Add a new line, maybe in the current verse.
if not (self.content and isinstance(self.content[0], Verse)):
self.content.insert(0, Verse())
self.content[0].prepend(data.strip())
if not data.is_empty():
if not (self.content and isinstance(self.content[0], Verse)):
self.content.insert(0, Verse())
self.content[0].prepend(data.strip())
elif isinstance(data, Directive) and data.inline:
# Add a directive in the content of the song.
self.content.append(data)
elif data.inline:
# Add an object in the content of the song.
self.content.insert(0, data)
@ -242,13 +216,13 @@ class Song(AST):
def add_title(self, data):
"""Add a title"""
self._titles.insert(0, data.argument)
self._titles.append(data.argument)
def add_cumulative(self, data):
"""Add a cumulative argument into metadata"""
if data.keyword not in self.meta:
self.meta[data.keyword] = []
self.meta[data.keyword].insert(0, data)
self.meta[data.keyword].append(data)
def get_data_argument(self, keyword, default):
"""Return `self.meta[keyword].argument`.
@ -267,7 +241,7 @@ class Song(AST):
def add_subtitle(self, data):
"""Add a subtitle"""
self._subtitles.insert(0, data.argument)
self._subtitles.append(data.argument)
@property
def titles(self):
@ -276,7 +250,7 @@ class Song(AST):
def add_author(self, data):
"""Add an auhor."""
self._authors.insert(0, data.argument)
self._authors.append(data.argument)
@property
def authors(self):
@ -286,19 +260,20 @@ class Song(AST):
def add_key(self, data):
"""Add a new {key: foo: bar} directive."""
key, *argument = data.argument.split(":")
if 'keys' not in self.meta:
self.meta['keys'] = []
self.meta['keys'].insert(0, Directive(
if 'morekeys' not in self.meta:
self.meta['morekeys'] = []
self.meta['morekeys'].append(Directive(
key.strip(),
":".join(argument).strip(),
))
class Newline(AST):
class EndOfLine(AST):
"""New line"""
_template = "newline"
_template = "endofline"
class Directive(AST):
"""A directive"""
meta = True
def __init__(self, keyword, argument=None):
super().__init__()
@ -322,6 +297,21 @@ class Directive(AST):
def __str__(self):
return self.argument
@classmethod
def create(cls, keyword, argument=None):
if keyword == "newline":
return NewLine(keyword, argument)
else:
return cls(keyword, argument)
class NewLine(Directive):
keyword = "newline"
_template = "newline"
meta = False
def strip(self):
return self
class Define(Directive):
"""A chord definition.

1
patacrep/songs/chordpro/data/chordpro/content_endofline

@ -0,0 +1 @@

2
patacrep/songs/chordpro/data/chordpro/content_newline

@ -1 +1 @@
{newline}

2
patacrep/songs/chordpro/data/chordpro/song_header

@ -25,7 +25,7 @@
{(( 'cov' )): (( metadata['cov'].argument|search_image ))}
(* endif *)
(*- for key in metadata.keys -*)
(*- for key in metadata.morekeys -*)
{key: (( key.keyword )): (( key.argument ))}
(* endfor *)

1
patacrep/songs/chordpro/data/html/content_endofline

@ -0,0 +1 @@

2
patacrep/songs/chordpro/data/html/content_newline

@ -1 +1 @@
<br/>

2
patacrep/songs/chordpro/data/html/song_header

@ -23,7 +23,7 @@
(* include 'content_metadata_cover' *)
(*- for key in metadata.keys -*)
(*- for key in metadata.morekeys -*)
{key: (( key.keyword )): (( key.argument ))}
(* endfor *)

2
patacrep/songs/chordpro/data/latex/content_endofline

@ -0,0 +1,2 @@

3
patacrep/songs/chordpro/data/latex/content_newline

@ -1,2 +1 @@
~\\

2
patacrep/songs/chordpro/data/latex/song

@ -30,7 +30,7 @@
(* if 'cov' in metadata *)
cov={(( metadata["cov"].argument|search_image ))},
(* endif *)
(* for key in metadata.keys *)
(* for key in metadata.morekeys *)
(( key.keyword ))={(( key.argument ))},
(* endfor *)
]

6
patacrep/songs/chordpro/lexer.py

@ -9,7 +9,7 @@ LOGGER = logging.getLogger()
tokens = (
'LBRACE',
'RBRACE',
'NEWLINE',
'ENDOFLINE',
'COLON',
'WORD',
'SPACE',
@ -81,14 +81,14 @@ class ChordProLexer:
return token
t_tablature_TEXT = r'[^\n]+'
t_tablature_NEWLINE = r'\n'
t_tablature_ENDOFLINE = r'\n'
def __init__(self):
self.__class__.lexer = lex.lex(module=self)
# Define a rule so we can track line numbers
@staticmethod
def t_NEWLINE(token):
def t_ENDOFLINE(token):
r'[\n\r]'
token.lexer.lineno += 1
return token

41
patacrep/songs/chordpro/syntax.py

@ -16,26 +16,25 @@ class ChordproParser(Parser):
def __init__(self, filename=None):
super().__init__()
self.tokens = tokens
self.filename = filename
self.song = ast.Song(filename)
def p_song(self, symbols):
"""song : block song
| empty
"""
if len(symbols) == 2:
symbols[0] = ast.Song(self.filename)
symbols[0] = self.song
else:
symbols[0] = symbols[2].add(symbols[1])
@staticmethod
def p_block(symbols):
"""block : SPACE block
| directive NEWLINE
| line NEWLINE
| chorus NEWLINE
| tab NEWLINE
| bridge NEWLINE
| NEWLINE
| line ENDOFLINE
| chorus ENDOFLINE
| tab ENDOFLINE
| bridge ENDOFLINE
| ENDOFLINE
"""
if len(symbols) == 3 and isinstance(symbols[1], str):
symbols[0] = symbols[2]
@ -133,16 +132,22 @@ class ChordproParser(Parser):
symbols[0] = ast.Error()
return
symbols[0] = self._parse_define(match.groupdict())
if symbols[0] is None:
define = self._parse_define(match.groupdict())
if define is None:
self.error(
line=symbols.lexer.lineno,
message="Invalid chord definition '{}'.".format(argument),
)
symbols[0] = ast.Error()
return
self.song.add(define)
else:
symbols[0] = ast.Directive(keyword, argument)
directive = ast.Directive.create(keyword, argument)
if directive.meta:
self.song.add(directive)
else:
symbols[0] = directive
@staticmethod
def p_directive_next(symbols):
@ -164,6 +169,7 @@ class ChordproParser(Parser):
def p_line(symbols):
"""line : word line_next
| chord line_next
| directive line_next
"""
symbols[0] = symbols[2].prepend(symbols[1])
@ -172,6 +178,7 @@ class ChordproParser(Parser):
"""line_next : word line_next
| space line_next
| chord line_next
| directive line_next
| empty
"""
if len(symbols) == 2:
@ -196,13 +203,13 @@ class ChordproParser(Parser):
@staticmethod
def p_chorus(symbols):
"""chorus : SOC maybespace NEWLINE chorus_content EOC maybespace
"""chorus : SOC maybespace ENDOFLINE chorus_content EOC maybespace
"""
symbols[0] = symbols[4]
@staticmethod
def p_chorus_content(symbols):
"""chorus_content : line NEWLINE chorus_content
"""chorus_content : line ENDOFLINE chorus_content
| SPACE chorus_content
| empty
"""
@ -215,13 +222,13 @@ class ChordproParser(Parser):
@staticmethod
def p_bridge(symbols):
"""bridge : SOB maybespace NEWLINE bridge_content EOB maybespace
"""bridge : SOB maybespace ENDOFLINE bridge_content EOB maybespace
"""
symbols[0] = symbols[4]
@staticmethod
def p_bridge_content(symbols):
"""bridge_content : line NEWLINE bridge_content
"""bridge_content : line ENDOFLINE bridge_content
| SPACE bridge_content
| empty
"""
@ -235,13 +242,13 @@ class ChordproParser(Parser):
@staticmethod
def p_tab(symbols):
"""tab : SOT maybespace NEWLINE tab_content EOT maybespace
"""tab : SOT maybespace ENDOFLINE tab_content EOT maybespace
"""
symbols[0] = symbols[4]
@staticmethod
def p_tab_content(symbols):
"""tab_content : NEWLINE tab_content
"""tab_content : ENDOFLINE tab_content
| TEXT tab_content
| SPACE tab_content
| empty

4
test/test_chordpro/metadata.sgc

@ -15,5 +15,7 @@
{comment: Comment}
{guitar_comment: GuitarComment}
{image: Image}
{partition: Lilypond}
{image: Image}
Foo

4
test/test_chordpro/metadata.source

@ -15,5 +15,7 @@
{key: foo: Foo}
{comment: Comment}
{guitar_comment: GuitarComment}
{image: Image}
{partition: Lilypond}
{image: Image}
Foo

5
test/test_chordpro/metadata.tex

@ -19,7 +19,10 @@ Subtitle5}[
\textnote{Comment}
\musicnote{GuitarComment}
\image{Image}
\lilypond{Lilypond}
\image{Image}
\begin{verse}
Foo
\end{verse}
\endsong

41
test/test_chordpro/newline.sgc

@ -0,0 +1,41 @@
{language: english}
This is a verse
With a new line
{newline}
The second part of the verse
Is this line
Here is a new line at the end
{newline}
Foo bar
{newline}
And a new line
At the beginning
{start_of_chorus}
New lines can also
{newline}
Be in chorus
{end_of_chorus}
{start_of_bridge}
New lines can also
{newline}
Be in bridges
{end_of_bridge}
New lines can also
{newline}
Be surrounded by spaces
New lines can {newline} appear in the middle of a line

32
test/test_chordpro/newline.source

@ -0,0 +1,32 @@
This is a verse
With a new line
{newline}
The second part of the verse
Is this line
Here is a new line at the end
{newline}
Foo bar
{newline}
And a new line
At the beginning
{soc}
New lines can also
{newline}
Be in chorus
{eoc}
{sob}
New lines can also
{newline}
Be in bridges
{eob}
New lines can also
{newline}
Be surrounded by spaces
New lines can {newline} appear in the middle of a line

61
test/test_chordpro/newline.tex

@ -0,0 +1,61 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\begin{verse}
This is a verse
With a new line
~\\
The second part of the verse
Is this line
\end{verse}
\begin{verse}
Here is a new line at the end
~\\
\end{verse}
\begin{verse}
Foo bar
\end{verse}
\begin{verse}
~\\
And a new line
At the beginning
\end{verse}
\begin{chorus}
New lines can also
~\\
Be in chorus
\end{chorus}
\begin{bridge}
New lines can also
~\\
Be in bridges
\end{bridge}
\begin{verse}
New lines can also
~\\
Be surrounded by spaces
\end{verse}
\begin{verse}
New lines can ~\\ appear in the middle of a line
\end{verse}
\endsong
Loading…
Cancel
Save