Browse Source

Merge pull request #79 from patacrep/chordpro

[chordpro] Parsing and rendering chords
pull/88/head
Luthaf 9 years ago
parent
commit
876d2fa884
  1. 3
      patacrep/build.py
  2. 2
      patacrep/content/__init__.py
  3. 12
      patacrep/content/song.py
  4. 3
      patacrep/data/examples/example-all.sb
  5. 17
      patacrep/data/examples/example-subdir.sb
  6. 19
      patacrep/data/examples/img/datadir.ly
  7. BIN
      patacrep/data/examples/img/datadir.png
  8. 19
      patacrep/data/examples/root.ly
  9. BIN
      patacrep/data/examples/root.png
  10. 23
      patacrep/data/examples/songs/chords.sgc
  11. 13
      patacrep/data/examples/songs/errors.sgc
  12. 4
      patacrep/data/examples/songs/greensleeves.sgc
  13. 10
      patacrep/data/examples/songs/subdir/datadir.sg
  14. 6
      patacrep/data/examples/songs/subdir/datadir.sgc
  15. 19
      patacrep/data/examples/songs/subdir/relative.ly
  16. BIN
      patacrep/data/examples/songs/subdir/relative.png
  17. 10
      patacrep/data/examples/songs/subdir/relative.sg
  18. 6
      patacrep/data/examples/songs/subdir/relative.sgc
  19. 10
      patacrep/data/examples/songs/subdir/root.sg
  20. 6
      patacrep/data/examples/songs/subdir/root.sgc
  21. 10
      patacrep/data/latex/patacrep.sty
  22. 119
      patacrep/data/templates/default.tex
  23. 56
      patacrep/data/templates/layout.tex
  24. 111
      patacrep/data/templates/patacrep.tex
  25. 99
      patacrep/data/templates/songs.tex
  26. 55
      patacrep/songs/__init__.py
  27. 44
      patacrep/songs/chordpro/__init__.py
  28. 290
      patacrep/songs/chordpro/ast.py
  29. 1
      patacrep/songs/chordpro/data/chordpro/content_chord
  30. 6
      patacrep/songs/chordpro/data/chordpro/content_chordlist
  31. 1
      patacrep/songs/chordpro/data/chordpro/content_comment
  32. 25
      patacrep/songs/chordpro/data/chordpro/content_define
  33. 3
      patacrep/songs/chordpro/data/chordpro/content_error
  34. 1
      patacrep/songs/chordpro/data/chordpro/content_guitar_comment
  35. 1
      patacrep/songs/chordpro/data/chordpro/content_image
  36. 3
      patacrep/songs/chordpro/data/chordpro/content_line
  37. 0
      patacrep/songs/chordpro/data/chordpro/content_newline
  38. 1
      patacrep/songs/chordpro/data/chordpro/content_partition
  39. 0
      patacrep/songs/chordpro/data/chordpro/content_space
  40. 5
      patacrep/songs/chordpro/data/chordpro/content_tablature
  41. 5
      patacrep/songs/chordpro/data/chordpro/content_verse
  42. 0
      patacrep/songs/chordpro/data/chordpro/content_word
  43. 35
      patacrep/songs/chordpro/data/chordpro/song
  44. 27
      patacrep/songs/chordpro/data/latex/chordpro.tex
  45. 1
      patacrep/songs/chordpro/data/latex/content_chord
  46. 1
      patacrep/songs/chordpro/data/latex/content_chord.tex
  47. 8
      patacrep/songs/chordpro/data/latex/content_chordlist
  48. 0
      patacrep/songs/chordpro/data/latex/content_comment
  49. 24
      patacrep/songs/chordpro/data/latex/content_define
  50. 3
      patacrep/songs/chordpro/data/latex/content_error
  51. 3
      patacrep/songs/chordpro/data/latex/content_error.tex
  52. 0
      patacrep/songs/chordpro/data/latex/content_guitar_comment
  53. 1
      patacrep/songs/chordpro/data/latex/content_image
  54. 1
      patacrep/songs/chordpro/data/latex/content_image.tex
  55. 3
      patacrep/songs/chordpro/data/latex/content_line
  56. 1
      patacrep/songs/chordpro/data/latex/content_line.tex
  57. 2
      patacrep/songs/chordpro/data/latex/content_newline
  58. 1
      patacrep/songs/chordpro/data/latex/content_partition
  59. 1
      patacrep/songs/chordpro/data/latex/content_partition.tex
  60. 1
      patacrep/songs/chordpro/data/latex/content_space
  61. 5
      patacrep/songs/chordpro/data/latex/content_tablature
  62. 5
      patacrep/songs/chordpro/data/latex/content_verse
  63. 6
      patacrep/songs/chordpro/data/latex/content_verse.tex
  64. 1
      patacrep/songs/chordpro/data/latex/content_word
  65. 50
      patacrep/songs/chordpro/data/latex/song
  66. 38
      patacrep/songs/chordpro/lexer.py
  67. 109
      patacrep/songs/chordpro/syntax.py
  68. 1
      patacrep/songs/chordpro/test/00.sgc
  69. 0
      patacrep/songs/chordpro/test/00.source
  70. 10
      patacrep/songs/chordpro/test/00.tex
  71. 1
      patacrep/songs/chordpro/test/00.txt
  72. 6
      patacrep/songs/chordpro/test/01.sgc
  73. 1
      patacrep/songs/chordpro/test/01.source
  74. 13
      patacrep/songs/chordpro/test/01.tex
  75. 4
      patacrep/songs/chordpro/test/01.txt
  76. 4
      patacrep/songs/chordpro/test/02.sgc
  77. 1
      patacrep/songs/chordpro/test/02.source
  78. 10
      patacrep/songs/chordpro/test/02.tex
  79. 2
      patacrep/songs/chordpro/test/02.txt
  80. 4
      patacrep/songs/chordpro/test/03.sgc
  81. 4
      patacrep/songs/chordpro/test/03.source
  82. 12
      patacrep/songs/chordpro/test/03.tex
  83. 1
      patacrep/songs/chordpro/test/03.txt
  84. 8
      patacrep/songs/chordpro/test/04.sgc
  85. 3
      patacrep/songs/chordpro/test/04.source
  86. 13
      patacrep/songs/chordpro/test/04.tex
  87. 4
      patacrep/songs/chordpro/test/04.txt
  88. 8
      patacrep/songs/chordpro/test/05.sgc
  89. 3
      patacrep/songs/chordpro/test/05.source
  90. 13
      patacrep/songs/chordpro/test/05.tex
  91. 4
      patacrep/songs/chordpro/test/05.txt
  92. 3
      patacrep/songs/chordpro/test/06.sgc
  93. 1
      patacrep/songs/chordpro/test/06.source
  94. 12
      patacrep/songs/chordpro/test/06.tex
  95. 1
      patacrep/songs/chordpro/test/06.txt
  96. 6
      patacrep/songs/chordpro/test/07.sgc
  97. 3
      patacrep/songs/chordpro/test/07.source
  98. 13
      patacrep/songs/chordpro/test/07.tex
  99. 4
      patacrep/songs/chordpro/test/07.txt
  100. 11
      patacrep/songs/chordpro/test/08.sgc

3
patacrep/build.py

@ -33,6 +33,7 @@ DEFAULT_CONFIG = {
'content': [], 'content': [],
'titleprefixwords': [], 'titleprefixwords': [],
'encoding': None, 'encoding': None,
'datadir': [],
} }
@ -113,7 +114,7 @@ class Songbook(object):
) )
# Configuration set # Configuration set
config['render_content'] = content.render_content config['render'] = content.render
config['content'] = content.process_content( config['content'] = content.process_content(
config.get('content', []), config.get('content', []),
config, config,

2
patacrep/content/__init__.py

@ -131,7 +131,7 @@ class ContentError(SongbookError):
return "Content: {}: {}".format(self.keyword, self.message) return "Content: {}: {}".format(self.keyword, self.message)
@jinja2.contextfunction @jinja2.contextfunction
def render_content(context, content): def render(context, content):
"""Render the content of the songbook as a LaTeX code. """Render the content of the songbook as a LaTeX code.
Arguments: Arguments:

12
patacrep/content/song.py

@ -4,6 +4,7 @@ import glob
import jinja2 import jinja2
import logging import logging
import os import os
import textwrap
from patacrep.content import process_content, ContentError, Content from patacrep.content import process_content, ContentError, Content
from patacrep import files, errors from patacrep import files, errors
@ -34,7 +35,16 @@ class SongRenderer(Content):
def render(self, context): def render(self, context):
"""Return the string that will render the song.""" """Return the string that will render the song."""
return self.song.tex(output=context['filename']) return textwrap.dedent("""\
{separator}
%% {path}
{song}
""").format(
separator="%"*80,
path=self.song.subpath,
song=self.song.render(output=context['filename'], output_format="latex"),
)
#pylint: disable=unused-argument #pylint: disable=unused-argument
def parse(keyword, argument, contentlist, config): def parse(keyword, argument, contentlist, config):

3
patacrep/data/examples/example-all.sb

@ -1,11 +1,12 @@
{ {
"bookoptions" : [ "bookoptions" : [
"importantdiagramonly", "diagram",
"repeatchords", "repeatchords",
"lilypond", "lilypond",
"pictures" "pictures"
], ],
"booktype" : "chorded", "booktype" : "chorded",
"template" : "patacrep.tex",
"lang" : "french", "lang" : "french",
"encoding": "utf8", "encoding": "utf8",
"authwords" : { "authwords" : {

17
patacrep/data/examples/example-subdir.sb

@ -0,0 +1,17 @@
{
"bookoptions" : [
"diagram",
"repeatchords",
"lilypond",
"pictures"
],
"booktype" : "chorded",
"template" : "patacrep.tex",
"lang" : "french",
"encoding": "utf8",
"authwords" : {
"sep" : ["and", "et"]
},
"content": [["sorted", "subdir/*.sg", "subdir/*.sgc"]]
}

19
patacrep/data/examples/img/datadir.ly

@ -0,0 +1,19 @@
\include "_lilypond/header"
\paper{paper-height = 6.5\cm}
{
\key a \minor
\time 6/8
\partial 8 a'8
\relative c''{
c4 d8 e8. (f16) e8 d4 b8 g8. (a16) b8
c4 a8 a8. (gis16) a8 b4 gis8 e4 a8
c4 d8 e8. (f16 e8) d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4 a8 a4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c4 (a8) a8. (gis16) a8 b4 gis8 e4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4. a4.
}
}

BIN
patacrep/data/examples/img/datadir.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

19
patacrep/data/examples/root.ly

@ -0,0 +1,19 @@
\include "_lilypond/header"
\paper{paper-height = 6.5\cm}
{
\key a \minor
\time 6/8
\partial 8 a'8
\relative c''{
c4 d8 e8. (f16) e8 d4 b8 g8. (a16) b8
c4 a8 a8. (gis16) a8 b4 gis8 e4 a8
c4 d8 e8. (f16 e8) d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4 a8 a4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c4 (a8) a8. (gis16) a8 b4 gis8 e4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4. a4.
}
}

BIN
patacrep/data/examples/root.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

23
patacrep/data/examples/songs/chords.sgc

@ -0,0 +1,23 @@
{language: english}
{columns: 1}
{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
[Adim]dim
[Dmaj]maj
[Em3]m chiffre
[G4]Nombre
[Emaj3]maj et nombre
[Absus8]bémol, sus et nombre
[A/A]Deux notes
[F/Fb]Deux notes, bémol
[B/C#]Deux notes, dièse
[A/C# Dmaj]Plusieurs accords

13
patacrep/data/examples/songs/errors.sgc

@ -0,0 +1,13 @@
{language : english}
{columns : 2}
{ title : Error}
{subtitle: A chordpro file with many errors}
{artist: Traditionnel}
{define: H4 base-fret 7 frets 2}
{define:}
Bla []bla
Bla [H]bla

4
patacrep/data/examples/songs/greensleeves.sgc

@ -4,7 +4,7 @@
{subtitle: Test of the chordpro format} {subtitle: Test of the chordpro format}
{artist: Traditionnel} {artist: Traditionnel}
{artist: Prénom Nom} {artist: Prénom Nom}
{cover : traditionnel } {cover : traditionnel.jpg }
{album :Angleterre} {album :Angleterre}
{partition : greensleeves.ly} {partition : greensleeves.ly}
@ -41,7 +41,7 @@ And [Am]all this [E]cost I [Am]spent on thee
{gc: test of guitar comment} {gc: test of guitar comment}
{image: traditionnel} {image: traditionnel.jpg}
Thy [Am]smock of silke, both [G]faire and white Thy [Am]smock of silke, both [G]faire and white
With [Am]gold embrodered [E]gorgeously With [Am]gold embrodered [E]gorgeously

10
patacrep/data/examples/songs/subdir/datadir.sg

@ -0,0 +1,10 @@
\beginsong{Image included from datadir\\\LaTeX}
[cov={datadir}]
\cover
\lilypond{datadir.ly}
\image{datadir}
\endsong

6
patacrep/data/examples/songs/subdir/datadir.sgc

@ -0,0 +1,6 @@
{title : Image included from datadir}
{subtitle: Chordpro}
{cover: datadir.png}
{partition: datadir.ly}
{image: datadir.png}

19
patacrep/data/examples/songs/subdir/relative.ly

@ -0,0 +1,19 @@
\include "_lilypond/header"
\paper{paper-height = 6.5\cm}
{
\key a \minor
\time 6/8
\partial 8 a'8
\relative c''{
c4 d8 e8. (f16) e8 d4 b8 g8. (a16) b8
c4 a8 a8. (gis16) a8 b4 gis8 e4 a8
c4 d8 e8. (f16 e8) d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4 a8 a4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c4 (a8) a8. (gis16) a8 b4 gis8 e4.
g'4. g8. (fis16) e8 d4 b8 g8. (a16) b8
c8. (b16) a8 gis8. (fis16) gis8 a4. a4.
}
}

BIN
patacrep/data/examples/songs/subdir/relative.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

10
patacrep/data/examples/songs/subdir/relative.sg

@ -0,0 +1,10 @@
\beginsong{Image included from song directory\\\LaTeX}
[cov={relative}]
\cover
\lilypond{relative.ly}
\image{relative}
\endsong

6
patacrep/data/examples/songs/subdir/relative.sgc

@ -0,0 +1,6 @@
{title : Image included from song directory}
{subtitle: Chordpro}
{cover: relative.png}
{partition: relative.ly}
{image: relative.png}

10
patacrep/data/examples/songs/subdir/root.sg

@ -0,0 +1,10 @@
\beginsong{Image included from root directory\\\LaTeX}
[cov={root}]
\cover
\lilypond{root.ly}
\image{root}
\endsong

6
patacrep/data/examples/songs/subdir/root.sgc

@ -0,0 +1,6 @@
{title : Image included from root directory}
{subtitle: Chordpro}
{cover: root.png}
{partition: root.ly}
{image: root.png}

10
patacrep/data/latex/patacrep.sty

@ -11,9 +11,9 @@
\RequirePackage{fancybox} \RequirePackage{fancybox}
\RequirePackage{xstring} \RequirePackage{xstring}
\RequirePackage{framed} \RequirePackage{framed}
\RequirePackage{currfile}
\RequirePackage{ifthen} \RequirePackage{ifthen}
\RequirePackage{tikz} \RequirePackage{tikz}
\RequirePackage{import}
% tabs: display the guitar tabs % tabs: display the guitar tabs
\newif{\iftabs} \newif{\iftabs}
@ -134,8 +134,7 @@
\setlength{\coverspace}{0.1cm} \setlength{\coverspace}{0.1cm}
\newcommand{\songcover}{} \newcommand{\songcover}{}
\newcommand{\songalbum}{} \newcommand{\songalbum}{}
\newsongkey{cov}{\let\songcover\@empty}{\def\songcover{\currfiledir#1}} \newsongkey{cov}{\let\songcover\@empty}{\def\songcover{#1}}
\newsongkey{vcov}{\let\songcover\@empty}{\def\songcover{#1}}
\newsongkey{album}{\let\songalbum\@empty}{\def\songalbum{#1}} \newsongkey{album}{\let\songalbum\@empty}{\def\songalbum{#1}}
\newsongkey{url}{\let\songurl\@empty}{\def\songurl{#1}} \newsongkey{url}{\let\songurl\@empty}{\def\songurl{#1}}
\newsongkey{original}{\let\songoriginal\@empty}{\def\songoriginal{#1}} \newsongkey{original}{\let\songoriginal\@empty}{\def\songoriginal{#1}}
@ -247,11 +246,6 @@
\newcommand{\lilypond}[1]{% \newcommand{\lilypond}[1]{%
\iflilypond% \iflilypond%
\epstopdfsetup{suffix=-\the\hsize-converted} \epstopdfsetup{suffix=-\the\hsize-converted}
\includegraphics{\currfiledir#1}%
\fi%
}
\newcommand{\vlilypond}[1]{%
\iflilypond%
\includegraphics{#1}% \includegraphics{#1}%
\fi% \fi%
} }

119
patacrep/data/templates/default.tex

@ -1,23 +1,22 @@
%! Copyright (C) 2014 The Patacrep team (www.patacrep.com) %!- Copyright (C) 2014 The Patacrep team (www.patacrep.com)
%! %!-
%! This program is free software; you can redistribute it and/or %!- This program is free software; you can redistribute it and/or
%! modify it under the terms of the GNU General Public License %!- modify it under the terms of the GNU General Public License
%! as published by the Free Software Foundation; either version 2 %!- as published by the Free Software Foundation; either version 2
%! of the License, or (at your option) any later version. %!- of the License, or (at your option) any later version.
%! %!-
%! This program is distributed in the hope that it will be useful, %!- This program is distributed in the hope that it will be useful,
%! but WITHOUT ANY WARRANTY; without even the implied warranty of %!- but WITHOUT ANY WARRANTY; without even the implied warranty of
%! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %!- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%! GNU General Public License for more details. %!- GNU General Public License for more details.
%! %!-
%! You should have received a copy of the GNU General Public License %!- You should have received a copy of the GNU General Public License
%! along with this program; if not, write to the Free Software %!- along with this program; if not, write to the Free Software
%! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, %!- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%! MA 02110-1301, USA. %!- MA 02110-1301, USA.
%! %!-
%! The latest version of this program can be obtained from %!- The latest version of this program can be obtained from
%! https://github.com/patacrep/ %!- https://github.com/patacrep/
(* variables *) (* variables *)
{ {
@ -40,47 +39,49 @@
"default": {"default": "alphascale", "french": "solfedge"} "default": {"default": "alphascale", "french": "solfedge"}
} }
} }
(* endvariables *) (* endvariables -*)
(* extends "songs.tex" *) (*- extends "songs.tex" -*)
(* set indexes = "titleidx,authidx" *) (*- set indexes = "titleidx,authidx" -*)
(* block documentclass *) (* block documentclass *)
\documentclass[(* for option in classoptions *) \documentclass[
((option)), (* for option in classoptions *)
(* endfor *)]{article} ((option)),
(* endfor *)
]{article}
(* endblock *) (* endblock *)
(* block songbookpreambule *) (* block songbookpreambule *)
(( super() )) (( super() ))
\usepackage{chords} \usepackage{chords}
\title{((title))} \title{((title))}
\author{((author))} \author{((author))}
\newindex{titleidx}{((filename))_title} \newindex{titleidx}{((filename))_title}
\newauthorindex{authidx}{((filename))_auth} \newauthorindex{authidx}{((filename))_auth}
(* for prefix in titleprefixwords *) (* for prefix in titleprefixwords -*)
\titleprefixword{((prefix))} \titleprefixword{((prefix))}
(* endfor*) (* endfor*)
(* for word in authwords.ignore *) (* for word in authwords.ignore -*)
\authignoreword{((word))} \authignoreword{((word))}
(* endfor *) (* endfor *)
(* for word in authwords.after *) (* for word in authwords.after -*)
\authbyword{((word))} \authbyword{((word))}
(* endfor *) (* endfor *)
(* for word in authwords.sep *) (* for word in authwords.sep -*)
\authsepword{((word))} \authsepword{((word))}
(* endfor *) (* endfor *)
(* if notenamesout=="alphascale" *) (* if notenamesout=="alphascale" -*)
\notenamesout{A}{B}{C}{D}{E}{F}{G} \notenamesout{A}{B}{C}{D}{E}{F}{G}
(* else *) (* else -*)
\notenamesout{La}{Si}{Do}{R\'e}{Mi}{Fa}{Sol} \notenamesout{La}{Si}{Do}{R\'e}{Mi}{Fa}{Sol}
(* endif *) (* endif *)
(* endblock *) (* endblock *)
(* block title *) (* block title *)
@ -88,18 +89,18 @@
(* endblock *) (* endblock *)
(* block index *) (* block index *)
\showindex{\songindexname}{titleidx} \showindex{\songindexname}{titleidx}
\showindex{\authorindexname}{authidx} \showindex{\authorindexname}{authidx}
(* endblock *) (* endblock *)
(* block chords *) (* block chords *)
% list of chords % list of chords
\ifchorded \ifchorded
\ifdiagram \ifdiagram
\phantomsection \phantomsection
\addcontentsline{toc}{section}{\chordlistname} \addcontentsline{toc}{section}{\chordlistname}
\chords \chords
\fi \fi
\fi \fi
(* endblock *) (* endblock *)

56
patacrep/data/templates/layout.tex

@ -1,23 +1,22 @@
%! Copyright (C) 2014 The Patacrep team (www.patacrep.com) %!- Copyright (C) 2014 The Patacrep team (www.patacrep.com)
%! %!-
%! This program is free software; you can redistribute it and/or %!- This program is free software; you can redistribute it and/or
%! modify it under the terms of the GNU General Public License %!- modify it under the terms of the GNU General Public License
%! as published by the Free Software Foundation; either version 2 %!- as published by the Free Software Foundation; either version 2
%! of the License, or (at your option) any later version. %!- of the License, or (at your option) any later version.
%! %!-
%! This program is distributed in the hope that it will be useful, %!- This program is distributed in the hope that it will be useful,
%! but WITHOUT ANY WARRANTY; without even the implied warranty of %!- but WITHOUT ANY WARRANTY; without even the implied warranty of
%! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %!- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%! GNU General Public License for more details. %!- GNU General Public License for more details.
%! %!-
%! You should have received a copy of the GNU General Public License %!- You should have received a copy of the GNU General Public License
%! along with this program; if not, write to the Free Software %!- along with this program; if not, write to the Free Software
%! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, %!- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%! MA 02110-1301, USA. %!- MA 02110-1301, USA.
%! %!-
%! The latest version of this program can be obtained from %!- The latest version of this program can be obtained from
%! https://github.com/patacrep/ %!- https://github.com/patacrep/
%% Automatically generated document. %% Automatically generated document.
%% You may edit this file but all changes will be overwritten. %% You may edit this file but all changes will be overwritten.
@ -27,10 +26,11 @@
%% Generated using Songbook <http://www.patacrep.com> %% Generated using Songbook <http://www.patacrep.com>
\makeatletter \makeatletter
\def\input@path{(* for dir in datadir *) \def\input@path{ %
{(( path2posix(dir) ))/latex/} % (* for dir in datadir *)
(* endfor *) {(( path2posix(dir) ))/latex/} %
} (* endfor *)
}
\makeatother \makeatother
(* block documentclass *) (* block documentclass *)
@ -41,9 +41,9 @@
(* endblock *) (* endblock *)
(* block songbookpreambule *) (* block songbookpreambule *)
\usepackage[utf8]{inputenc} \usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc} \usepackage[T1]{fontenc}
\usepackage{lmodern} \usepackage{lmodern}
(* endblock songbookpreambule *) (* endblock songbookpreambule *)
(* block preambule *) (* block preambule *)
@ -71,4 +71,4 @@
\end{document} \end{document}
%! End of file %!- End of file

111
patacrep/data/templates/patacrep.tex

@ -1,23 +1,22 @@
%! Copyright (C) 2014 The Patacrep team (www.patacrep.com) %!- Copyright (C) 2014 The Patacrep team (www.patacrep.com)
%! %!-
%! This program is free software; you can redistribute it and/or %!- This program is free software; you can redistribute it and/or
%! modify it under the terms of the GNU General Public License %!- modify it under the terms of the GNU General Public License
%! as published by the Free Software Foundation; either version 2 %!- as published by the Free Software Foundation; either version 2
%! of the License, or (at your option) any later version. %!- of the License, or (at your option) any later version.
%! %!-
%! This program is distributed in the hope that it will be useful, %!- This program is distributed in the hope that it will be useful,
%! but WITHOUT ANY WARRANTY; without even the implied warranty of %!- but WITHOUT ANY WARRANTY; without even the implied warranty of
%! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %!- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%! GNU General Public License for more details. %!- GNU General Public License for more details.
%! %!-
%! You should have received a copy of the GNU General Public License %!- You should have received a copy of the GNU General Public License
%! along with this program; if not, write to the Free Software %!- along with this program; if not, write to the Free Software
%! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, %!- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%! MA 02110-1301, USA. %!- MA 02110-1301, USA.
%! %!-
%! The latest version of this program can be obtained from %!- The latest version of this program can be obtained from
%! https://github.com/patacrep/ %!- https://github.com/patacrep/
(* variables *) (* variables *)
{ {
@ -61,21 +60,25 @@
"Des", "El", "Les", "Ma", "Mon", "Un"]} "Des", "El", "Les", "Ma", "Mon", "Un"]}
} }
} }
(* endvariables *) (* endvariables -*)
(* extends "default.tex" *) (*- extends "default.tex" -*)
(* block songbookpackages *) (* block songbookpackages *)
%! booktype, bookoptions and instruments are defined in "songs.tex" %! booktype, bookoptions and instruments are defined in "songs.tex"
\usepackage[((booktype)), \usepackage[
(* for option in bookoptions *)((option)), ((booktype)),
(* endfor *) (* for option in bookoptions *)
(* for instrument in instruments *)((instrument)), ((option)),
(* endfor *)]{crepbook} (* endfor *)
(* for instrument in instruments *)
((instrument)),
(* endfor *)
]{crepbook}
(* endblock *) (* endblock *)
(* block songbookpreambule *) (* block songbookpreambule *)
\usepackage[ \usepackage[
a4paper % paper size a4paper % paper size
,includeheadfoot % include header and footer into text size ,includeheadfoot % include header and footer into text size
,hmarginratio=1:1 % ratio between inner and outer margin (default) ,hmarginratio=1:1 % ratio between inner and outer margin (default)
@ -84,35 +87,37 @@
,bmargin=1.3cm % bottom margin ,bmargin=1.3cm % bottom margin
]{geometry} ]{geometry}
(( super() )) (( super() ))
\pagestyle{empty} \pagestyle{empty}
\definecolor{SongNumberBgColor}{HTML}{((songnumberbgcolor))} \definecolor{SongNumberBgColor}{HTML}{((songnumberbgcolor))}
\definecolor{NoteBgColor}{HTML}{((notebgcolor))} \definecolor{NoteBgColor}{HTML}{((notebgcolor))}
\definecolor{IndexBgColor}{HTML}{((indexbgcolor))} \definecolor{IndexBgColor}{HTML}{((indexbgcolor))}
\renewcommand{\snumbgcolor}{SongNumberBgColor} \renewcommand{\snumbgcolor}{SongNumberBgColor}
\renewcommand{\notebgcolor}{NoteBgColor} \renewcommand{\notebgcolor}{NoteBgColor}
\renewcommand{\idxbgcolor}{IndexBgColor} \renewcommand{\idxbgcolor}{IndexBgColor}
\definecolor{tango-green-3}{HTML}{4e9a06} \definecolor{tango-green-3}{HTML}{4e9a06}
\definecolor{tango-blue-3}{HTML}{204a87} \definecolor{tango-blue-3}{HTML}{204a87}
\usepackage[bookmarks, \usepackage[
bookmarksopen, bookmarks,
hyperfigures=true, bookmarksopen,
colorlinks=true, hyperfigures=true,
linkcolor=tango-green-3, colorlinks=true,
urlcolor=tango-blue-3]{hyperref} linkcolor=tango-green-3,
urlcolor=tango-blue-3
]{hyperref}
\subtitle{((subtitle))} \subtitle{((subtitle))}
(* if version!="undefined" *) (* if version!="undefined" -*)
\version{((version))} \version{((version))}
(* endif *) (* endif *)
\mail{((mail))} \mail{((mail))}
\web{((web))} \web{((web))}
\picture{((picture))} \picture{((picture))}
\picturecopyright{((picturecopyright))} \picturecopyright{((picturecopyright))}
\footer{((footer))} \footer{((footer))}
(* endblock *) (* endblock *)

99
patacrep/data/templates/songs.tex

@ -1,22 +1,22 @@
%! Copyright (C) 2014 The Patacrep team (www.patacrep.com) %!- Copyright (C) 2014 The Patacrep team (www.patacrep.com)
%! %!-
%! This program is free software; you can redistribute it and/or %!- This program is free software; you can redistribute it and/or
%! modify it under the terms of the GNU General Public License %!- modify it under the terms of the GNU General Public License
%! as published by the Free Software Foundation; either version 2 %!- as published by the Free Software Foundation; either version 2
%! of the License, or (at your option) any later version. %!- of the License, or (at your option) any later version.
%! %!-
%! This program is distributed in the hope that it will be useful, %!- This program is distributed in the hope that it will be useful,
%! but WITHOUT ANY WARRANTY; without even the implied warranty of %!- but WITHOUT ANY WARRANTY; without even the implied warranty of
%! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %!- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%! GNU General Public License for more details. %!- GNU General Public License for more details.
%! %!-
%! You should have received a copy of the GNU General Public License %!- You should have received a copy of the GNU General Public License
%! along with this program; if not, write to the Free Software %!- along with this program; if not, write to the Free Software
%! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, %!- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%! MA 02110-1301, USA. %!- MA 02110-1301, USA.
%! %!-
%! The latest version of this program can be obtained from %!- The latest version of this program can be obtained from
%! https://github.com/patacrep/ %!- https://github.com/patacrep/
(* variables *) (* variables *)
{ {
@ -63,48 +63,51 @@
"default": {"default": {}} "default": {"default": {}}
} }
} }
(* endvariables *) (* endvariables -*)
(* extends "layout.tex" *) (*- extends "layout.tex" -*)
(* block songbookpackages *) (* block songbookpackages *)
\usepackage[((booktype)), \usepackage[
(* for option in bookoptions *)((option)), ((booktype)),
(* endfor *) (* for option in bookoptions *)((option)),
(* for instrument in instruments *)((instrument)), (* endfor *)
(* endfor *)]{patacrep} (* for instrument in instruments *)((instrument)),
(* endfor *)
]{patacrep}
(* endblock *) (* endblock *)
(* block songbookpreambule *) (* block songbookpreambule *)
(( super() )) (( super() ))
(* for lang in _languages *) (* for lang in _languages -*)
\PassOptionsToPackage{((lang))}{babel} \PassOptionsToPackage{((lang))}{babel}
(* endfor *) (* endfor *)
\usepackage[((lang))]{babel} \usepackage[((lang))]{babel}
\lang{((lang))} \lang{((lang))}
\usepackage{graphicx} \usepackage{graphicx}
\graphicspath{(* for dir in datadir *) \graphicspath{ %
{(( path2posix(dir) ))/img/} % (* for dir in datadir *)
(* endfor *) {(( path2posix(dir) ))/img/} %
} (* endfor *)
}
\makeatletter \makeatletter
\@ifpackageloaded{hyperref}{}{ \@ifpackageloaded{hyperref}{}{
\usepackage{url} \usepackage{url}
\newcommand{\phantomsection}{} \newcommand{\phantomsection}{}
\newcommand{\hyperlink}[2]{#2} \newcommand{\hyperlink}[2]{#2}
\newcommand{\href}[2]{\expandafter\url\expandafter{#1}} \newcommand{\href}[2]{\expandafter\url\expandafter{#1}}
} }
\makeatother \makeatother
(* endblock *) (* endblock *)
(* block songs *) (* block songs *)
\phantomsection \phantomsection
\addcontentsline{toc}{section}{\songlistname} \addcontentsline{toc}{section}{\songlistname}
((render_content(content) )) (( render(content) ))
(* endblock *) (* endblock *)

55
patacrep/songs/__init__.py

@ -10,7 +10,6 @@ import re
from patacrep.authors import process_listauthors from patacrep.authors import process_listauthors
from patacrep import files, encoding from patacrep import files, encoding
from patacrep.content import Content
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -63,25 +62,25 @@ class DataSubpath(object):
return self return self
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class Song(Content): class Song:
"""Song (or song metadata) """Song (or song metadata)
This class represents a song, bound to a file. This class represents a song, bound to a file.
- It can parse the file given in arguments. - It can parse the file given in arguments.
- It can render the song as some LaTeX code. - It can render the song as some code (LaTeX, chordpro, depending on subclasses implemetation).
- Its content is cached, so that if the file has not been changed, the - Its content is cached, so that if the file has not been changed, the
file is not parsed again. file is not parsed again.
This class is inherited by classes implementing song management for This class is inherited by classes implementing song management for
several file formats. Those subclasses must implement: several file formats. Those subclasses must implement:
- `parse()` to parse the file; - `parse()` to parse the file;
- `render()` to render the song as LaTeX code. - `render()` to render the song as code.
""" """
# Version format of cached song. Increment this number if we update # Version format of cached song. Increment this number if we update
# information stored in cache. # information stored in cache.
CACHE_VERSION = 1 CACHE_VERSION = 2
# List of attributes to cache # List of attributes to cache
cached_attributes = [ cached_attributes = [
@ -144,7 +143,7 @@ class Song(Content):
] ]
self.authors = process_listauthors( self.authors = process_listauthors(
self.authors, self.authors,
**config["_compiled_authwords"] **config.get("_compiled_authwords", {})
) )
# Cache management # Cache management
@ -166,12 +165,16 @@ class Song(Content):
def __repr__(self): def __repr__(self):
return repr((self.titles, self.data, self.fullpath)) return repr((self.titles, self.data, self.fullpath))
def tex(self, output): # pylint: disable=no-self-use, unused-argument def render(self, output, output_format):
"""Return the LaTeX code rendering this song. """Return the code rendering this song.
Arguments: Arguments:
- output: Name of the output file. - output: Name of the output file.
- output_format: Format of the output file (latex, chordpro...)
""" """
method = "render_{}".format(output_format)
if hasattr(self, method):
return getattr(self, method)(output)
raise NotImplementedError() raise NotImplementedError()
def parse(self, config): # pylint: disable=no-self-use def parse(self, config): # pylint: disable=no-self-use
@ -199,3 +202,39 @@ def unprefixed_title(title, prefixes):
if match: if match:
return match.group(2) return match.group(2)
return title return title
def search_image(image, chordprofile, config):
"""Return the file name of an image, so that LaTeX will find it.
:param str image: The name, as provided in the chordpro file.
:param str chordprofile: The name of the file including this image.
:param dict config: Songbook configuration dictionary.
The image can be:
- in the same directory as the including song file;
- in the same directory as the main LaTeX file;
- in some of the `DATADIR/img` directories.
If image is not found, the `image` argument is returned.
"""
# Image is in the same folder as its song
texdir = os.path.dirname(chordprofile)
if os.path.exists(os.path.join(texdir, image)):
return os.path.join(texdir, image)
# Image is in the same directory as the main tex file
rootdir = os.path.dirname(os.path.join(
os.getcwd(),
config['filename'],
))
if os.path.exists(os.path.join(rootdir, image)):
return image
# Image is in a datadir
for directory in config['datadir']:
if os.path.exists(os.path.join(directory, 'img', image)):
return os.path.join(directory, 'img', image)
# Could not find image
return image

44
patacrep/songs/chordpro/__init__.py

@ -5,16 +5,16 @@ import pkg_resources
import os import os
from patacrep import encoding, files from patacrep import encoding, files
from patacrep.songs import Song from patacrep.songs import Song, search_image
from patacrep.songs.chordpro.syntax import parse_song from patacrep.songs.chordpro.syntax import parse_song
from patacrep.templates import TexRenderer from patacrep.templates import Renderer
class ChordproSong(Song): class ChordproSong(Song):
"""Chordpros song parser.""" """Chordpros song parser."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.texenv = None self.jinjaenv = None
def parse(self, config): def parse(self, config):
"""Parse content, and return the dictionary of song data.""" """Parse content, and return the dictionary of song data."""
@ -22,42 +22,46 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath) song = parse_song(song.read(), self.fullpath)
self.authors = song.authors self.authors = song.authors
self.titles = song.titles self.titles = song.titles
self.languages = song.get_directives('language') self.languages = song.get_data_argument('language', [self.config['lang']])
self.data = dict([meta.as_tuple for meta in song.meta]) self.data = song.meta
self.cached = { self.cached = {
'song': song, 'song': song,
} }
def tex(self, output): def render(self, output, output_format):
context = { context = {
'language': self.cached['song'].get_directive('language', self.config['lang']), 'language': self.languages[0],
'columns': self.cached['song'].get_directive('columns', 1),
"path": files.relpath(self.fullpath, os.path.dirname(output)), "path": files.relpath(self.fullpath, os.path.dirname(output)),
"titles": r"\\".join(self.titles), "titles": self.titles,
"authors": ", ".join(["{} {}".format(name[1], name[0]) for name in self.authors]), "authors": self.authors,
"metadata": self.data, "metadata": self.data,
"beginsong": self.cached['song'].meta_beginsong(), "render": self._render_ast,
"render": self.render_tex, "config": self.config,
} }
self.texenv = Environment(loader=FileSystemLoader(os.path.join( self.jinjaenv = Environment(loader=FileSystemLoader(os.path.join(
os.path.abspath(pkg_resources.resource_filename(__name__, 'data')), os.path.abspath(pkg_resources.resource_filename(__name__, 'data')),
'latex' output_format,
))) )))
return self.render_tex(context, self.cached['song'].content, template="chordpro.tex") self.jinjaenv.filters['search_image'] = search_image
return self._render_ast(
context,
self.cached['song'].content,
template="song",
)
@contextfunction @contextfunction
def render_tex(self, context, content, template=None): def _render_ast(self, context, content, template=None):
"""Render ``content`` as tex.""" """Render ``content``."""
if isinstance(context, dict): if isinstance(context, dict):
context['content'] = content context['content'] = content
else: else:
context.vars['content'] = content context.vars['content'] = content
if template is None: if template is None:
template = content.template('tex') template = content.template()
return TexRenderer( return Renderer(
template=template, template=template,
encoding='utf8', encoding='utf8',
texenv=self.texenv, jinjaenv=self.jinjaenv,
).template.render(context) ).template.render(context)
SONG_PARSERS = { SONG_PARSERS = {

290
patacrep/songs/chordpro/ast.py

@ -2,12 +2,43 @@
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import functools
import logging import logging
import os
LOGGER = logging.getLogger() 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 _indent(string): def _indent(string):
"""Return and indented version of argument.""" """Return and indented version of argument."""
return "\n".join([" {}".format(line) for line in string.split('\n')]) return "\n".join([" {}".format(line) for line in string.split('\n')])
@ -21,15 +52,6 @@ INLINE_PROPERTIES = {
"image", "image",
} }
#: List of properties that are listed in the `\beginsong` LaTeX directive.
BEGINSONG_PROPERTIES = {
"album",
"copyright",
"cov",
"vcov",
"tag",
}
#: Some directive have alternative names. For instance `{title: Foo}` and `{t: #: Some directive have alternative names. For instance `{title: Foo}` and `{t:
#: Foo}` are equivalent. #: Foo}` are equivalent.
DIRECTIVE_SHORTCUTS = { DIRECTIVE_SHORTCUTS = {
@ -40,7 +62,6 @@ DIRECTIVE_SHORTCUTS = {
"c": "comment", "c": "comment",
"gc": "guitar_comment", "gc": "guitar_comment",
"cover": "cov", "cover": "cov",
"vcover": "vcov",
} }
def directive_name(text): def directive_name(text):
@ -53,14 +74,17 @@ class AST:
_template = None _template = None
inline = False inline = False
def template(self, extension): def template(self):
"""Return the template to be used to render this object.""" """Return the template to be used to render this object."""
if self._template is None: if self._template is None:
LOGGER.warning("No template defined for {}.".format(self.__class__)) LOGGER.warning("No template defined for {}.".format(self.__class__))
base = "error" base = "error"
else: else:
base = self._template base = self._template
return "content_{}.{}".format(base, extension) return "content_{}".format(base)
class Error(AST):
"""Parsing error. To be ignored."""
class Line(AST): class Line(AST):
"""A line is a sequence of (possibly truncated) words, spaces and chords.""" """A line is a sequence of (possibly truncated) words, spaces and chords."""
@ -76,9 +100,6 @@ class Line(AST):
self.line.insert(0, data) self.line.insert(0, data)
return self return self
def __str__(self):
return "".join([str(item) for item in self.line])
def strip(self): def strip(self):
"""Remove spaces at the beginning and end of line.""" """Remove spaces at the beginning and end of line."""
while True: while True:
@ -94,6 +115,7 @@ class Line(AST):
class LineElement(AST): class LineElement(AST):
"""Something present on a line.""" """Something present on a line."""
# pylint: disable=abstract-method
pass pass
class Word(LineElement): class Word(LineElement):
@ -104,9 +126,6 @@ class Word(LineElement):
super().__init__() super().__init__()
self.value = value self.value = value
def __str__(self):
return self.value
class Space(LineElement): class Space(LineElement):
"""A space between words""" """A space between words"""
_template = "space" _template = "space"
@ -114,20 +133,21 @@ class Space(LineElement):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
def __str__(self): class ChordList(LineElement):
return " " """A list of chords."""
_template = "chordlist"
def __init__(self, *chords):
self.chords = chords
class Chord(LineElement): class Chord(AST):
"""A chord.""" """A chord."""
_template = "chord" _template = "chord"
def __init__(self, value): def __init__(self, chord):
super().__init__() # pylint: disable=too-many-arguments
self.value = value self.chord = chord
def __str__(self):
return "[{}]".format(self.value)
class Verse(AST): class Verse(AST):
"""A verse (or bridge, or chorus)""" """A verse (or bridge, or chorus)"""
@ -144,12 +164,6 @@ class Verse(AST):
self.lines.insert(0, data) self.lines.insert(0, data)
return self return self
def __str__(self):
return '{{start_of_{type}}}\n{content}\n{{end_of_{type}}}'.format(
type=self.type,
content=_indent("\n".join([str(line) for line in self.lines])),
)
class Chorus(Verse): class Chorus(Verse):
"""Chorus""" """Chorus"""
type = 'chorus' type = 'chorus'
@ -167,30 +181,23 @@ class Song(AST):
- titles: The list of titles - titles: The list of titles
- language: The language (if set), None otherwise - language: The language (if set), None otherwise
- authors: The list of authors - authors: The list of authors
- meta_beginsong: The list of directives that are to be set in the
`\beginsong{}` LaTeX directive.
- meta: Every other metadata. - meta: Every other metadata.
""" """
#: Some directives are added to the song using special methods. #: Some directives are added to the song using special methods.
METADATA_TYPE = { METADATA_ADD = {
"title": "add_title", "title": "add_title",
"subtitle": "add_subtitle", "subtitle": "add_subtitle",
"artist": "add_author", "artist": "add_author",
"key": "add_key", "key": "add_key",
} "define": "add_cumulative",
"language": "add_cumulative",
#: Some directives have to be processed before being considered.
PROCESS_DIRECTIVE = {
"cov": "_process_relative",
"partition": "_process_relative",
"image": "_process_relative",
} }
def __init__(self, filename): def __init__(self, filename):
super().__init__() super().__init__()
self.content = [] self.content = []
self.meta = [] self.meta = OrderedLifoDict()
self._authors = [] self._authors = []
self._titles = [] self._titles = []
self._subtitles = [] self._subtitles = []
@ -199,13 +206,9 @@ class Song(AST):
def add(self, data): def add(self, data):
"""Add an element to the song""" """Add an element to the song"""
if isinstance(data, Directive): if isinstance(data, Error):
# Some directives are preprocessed return self
name = directive_name(data.keyword) elif data is None:
if name in self.PROCESS_DIRECTIVE:
data = getattr(self, self.PROCESS_DIRECTIVE[name])(data)
if data is None:
# New line # New line
if not (self.content and isinstance(self.content[0], Newline)): if not (self.content and isinstance(self.content[0], Newline)):
self.content.insert(0, Newline()) self.content.insert(0, Newline())
@ -219,127 +222,78 @@ class Song(AST):
self.content.insert(0, data) self.content.insert(0, data)
elif isinstance(data, Directive): elif isinstance(data, Directive):
# Add a metadata directive. Some of them are added using special # Add a metadata directive. Some of them are added using special
# methods listed in ``METADATA_TYPE``. # methods listed in ``METADATA_ADD``.
name = directive_name(data.keyword) if data.keyword in self.METADATA_ADD:
if name in self.METADATA_TYPE: getattr(self, self.METADATA_ADD[data.keyword])(data)
getattr(self, self.METADATA_TYPE[name])(*data.as_tuple)
else: else:
self.meta.append(data) self.meta[data.keyword] = data
else: else:
raise Exception() raise Exception()
return self return self
def str_meta(self): def add_title(self, data):
"""Return an iterator over *all* metadata, as strings.""" """Add a title"""
for title in self.titles: self._titles.insert(0, data.argument)
yield "{{title: {}}}".format(title)
for author in self.authors: def add_cumulative(self, data):
yield "{{by: {}}}".format(author) """Add a cumulative argument into metadata"""
for key in sorted(self.keys): if data.keyword not in self.meta:
yield "{{key: {}}}".format(str(key)) self.meta[data.keyword] = []
for key in sorted(self.meta): self.meta[data.keyword].insert(0, data)
yield str(key)
def __str__(self): def get_data_argument(self, keyword, default):
return ( """Return `self.meta[keyword].argument`.
"\n".join(self.str_meta()).strip()
+
"\n========\n"
+
"\n".join([str(item) for item in self.content]).strip()
)
Return `default` if `self.meta[keyword]` does not exist.
def add_title(self, __ignored, title): If `self.meta[keyword]` is a list, return the list of `item.argument`
"""Add a title""" for each item in the list.
self._titles.insert(0, title) """
if keyword not in self.meta:
return default
if isinstance(self.meta[keyword], list):
return [item.argument for item in self.meta[keyword]]
else:
return self.meta[keyword].argument
def add_subtitle(self, __ignored, title): def add_subtitle(self, data):
"""Add a subtitle""" """Add a subtitle"""
self._subtitles.insert(0, title) self._subtitles.insert(0, data.argument)
@property @property
def titles(self): def titles(self):
"""Return the list of titles (and subtitles).""" """Return the list of titles (and subtitles)."""
return self._titles + self._subtitles return self._titles + self._subtitles
def add_author(self, __ignored, title): def add_author(self, data):
"""Add an auhor.""" """Add an auhor."""
self._authors.insert(0, title) self._authors.insert(0, data.argument)
@property @property
def authors(self): def authors(self):
"""Return the list of (raw) authors.""" """Return the list of (raw) authors."""
return self._authors return self._authors
def get_directive(self, key, default=None): def add_key(self, data):
"""Return the first directive with a given key."""
for directive in self.meta:
if directive.keyword == directive_name(key):
return directive.argument
return default
def get_directives(self, key):
"""Return the list of directives with a given key."""
values = []
for directive in self.meta:
if directive.keyword == directive_name(key):
values.append(directive.argument)
return values
def add_key(self, __ignored, argument):
"""Add a new {key: foo: bar} directive.""" """Add a new {key: foo: bar} directive."""
key, *argument = argument.split(":") key, *argument = data.argument.split(":")
self._keys.append(Directive( if 'keys' not in self.meta:
self.meta['keys'] = []
self.meta['keys'].insert(0, Directive(
key.strip(), key.strip(),
":".join(argument).strip(), ":".join(argument).strip(),
)) ))
@property
def keys(self):
"""Return the list of keys.
That is, directive that where given of the form ``{key: foo: bar}``.
"""
return self._keys
def meta_beginsong(self):
r"""Return the meta information to be put in \beginsong."""
for directive in BEGINSONG_PROPERTIES:
if self.get_directive(directive) is not None:
yield (directive, self.get_directive(directive))
for (key, value) in self.keys:
yield (key, value)
def _process_relative(self, directive):
"""Return the directive, in which the argument is given relative to file
This argument is expected to be a path (as a string).
"""
return Directive(
directive.keyword,
os.path.join(
os.path.dirname(self.filename),
directive.argument,
),
)
class Newline(AST): class Newline(AST):
"""New line""" """New line"""
_template = "newline" _template = "newline"
def __str__(self):
return ""
@functools.total_ordering
class Directive(AST): class Directive(AST):
"""A directive""" """A directive"""
def __init__(self, keyword="", argument=None): def __init__(self, keyword, argument=None):
super().__init__() super().__init__()
self._keyword = None self.keyword = directive_name(keyword.strip())
self.keyword = keyword
self.argument = argument self.argument = argument
@property @property
@ -350,49 +304,47 @@ class Directive(AST):
""" """
return self.keyword return self.keyword
@property
def keyword(self):
"""Keyword of the directive."""
return self._keyword
@property @property
def inline(self): def inline(self):
"""True iff this directive is to be rendered in the flow on the song. """True iff this directive is to be rendered in the flow on the song.
""" """
return self.keyword in INLINE_PROPERTIES return self.keyword in INLINE_PROPERTIES
@keyword.setter
def keyword(self, value):
"""self.keyword setter
Replace keyword by its canonical name if it is a shortcut.
"""
self._keyword = directive_name(value.strip())
def __str__(self): def __str__(self):
if self.argument is not None: return self.argument
return "{{{}: {}}}".format(
self.keyword,
self.argument,
)
else:
return "{{{}}}".format(self.keyword)
@property class Define(Directive):
def as_tuple(self): """A chord definition.
"""Return the directive as a tuple."""
return (self.keyword, self.argument) Attributes:
def __eq__(self, other): .. attribute:: key
return self.as_tuple == other.as_tuple 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.
"""
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
super().__init__("define", None)
def __lt__(self, other): def __str__(self):
return self.as_tuple < other.as_tuple return None
class Tab(AST): class Tab(AST):
"""Tablature""" """Tablature"""
inline = True inline = True
_template = "tablature"
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -402,9 +354,3 @@ class Tab(AST):
"""Add an element at the beginning of content.""" """Add an element at the beginning of content."""
self.content.insert(0, data) self.content.insert(0, data)
return self return self
def __str__(self):
return '{{start_of_tab}}\n{}\n{{end_of_tab}}'.format(
_indent("\n".join(self.content)),
)

1
patacrep/songs/chordpro/data/chordpro/content_chord

@ -0,0 +1 @@
((- content.chord -))

6
patacrep/songs/chordpro/data/chordpro/content_chordlist

@ -0,0 +1,6 @@
[
(*- for chord in content.chords -*)
(* if not loop.first *) (* endif -*)
(( render(chord) -))
(* endfor -*)
]

1
patacrep/songs/chordpro/data/chordpro/content_comment

@ -0,0 +1 @@
{comment: (( content.argument ))}

25
patacrep/songs/chordpro/data/chordpro/content_define

@ -0,0 +1,25 @@
{define: (( render(content.key) ))
(*- if content.basefret *)
base-fret ((content.basefret))
(*- endif *)
frets
(*- for string in content.frets -*)
(( " " -))
(*- if string is none -*)
x
(*- else -*)
(( string -))
(*- endif -*)
(*- endfor -*)
(* if content.fingers *)
fingers
(*- for finger in content.fingers -*)
(( " " -))
(* if finger is none -*)
-
(*- else -*)
(( finger -))
(* endif -*)
(* endfor -*)
(* endif -*)
}

3
patacrep/songs/chordpro/data/chordpro/content_error

@ -0,0 +1,3 @@
ERROR : Template not found for "(( content.__class__.__name__ ))". See the logs for details.

1
patacrep/songs/chordpro/data/chordpro/content_guitar_comment

@ -0,0 +1 @@
{guitar_comment: (( content.argument ))}

1
patacrep/songs/chordpro/data/chordpro/content_image

@ -0,0 +1 @@
{image: (( content.argument ))}

3
patacrep/songs/chordpro/data/chordpro/content_line

@ -0,0 +1,3 @@
(* for item in content.line -*)
(( render(item) ))
(*- endfor *)

0
patacrep/songs/chordpro/data/latex/content_newline.tex → patacrep/songs/chordpro/data/chordpro/content_newline

1
patacrep/songs/chordpro/data/chordpro/content_partition

@ -0,0 +1 @@
{partition: ((content.argument))}

0
patacrep/songs/chordpro/data/latex/content_space.tex → patacrep/songs/chordpro/data/chordpro/content_space

5
patacrep/songs/chordpro/data/chordpro/content_tablature

@ -0,0 +1,5 @@
{start_of_tab}
(* for content in content.content *)
((- content ))
(* endfor *)
{end_of_tab}

5
patacrep/songs/chordpro/data/chordpro/content_verse

@ -0,0 +1,5 @@
{start_of_(( content.type ))}
(* for line in content.lines *)
(( render(line) ))
(* endfor *)
{end_of_(( content.type ))}

0
patacrep/songs/chordpro/data/latex/content_word.tex → patacrep/songs/chordpro/data/chordpro/content_word

35
patacrep/songs/chordpro/data/chordpro/song

@ -0,0 +1,35 @@
(* if language is defined -*)
{language: (( language ))}
(* endif *)
(* if metadata.columns is defined -*)
{columns: (( metadata.columns ))}
(* endif *)
(* if metadata.capo is defined -*)
{capo: (( metadata.capo ))}
(* endif *)
(*- for title in titles -*)
{title: (( title ))}
(* endfor -*)
(*- for author in authors -*)
{artist: (( author[1] )) (( author[0] ))}
(* endfor *)
(*- for key in ['album', 'copyright', 'cov', 'tag'] *)
(* if key in metadata -*)
{(( key )): (( metadata[key] ))}
(* endif *)
(* endfor *)
(*- for key in metadata.keys -*)
{key: (( key.keyword )): (( key.argument ))}
(* endfor *)
(*- for chord in metadata['define'] *)
((- render(chord) ))
(* endfor *)
(* for item in content -*)
(( render(item) ))
(* endfor *)

27
patacrep/songs/chordpro/data/latex/chordpro.tex

@ -1,27 +0,0 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% ((path))
(* if language is defined *)
\selectlanguage{((language))}
(* endif *)
\songcolumns{((metadata.columns))}
\beginsong{((titles))}[
by={((authors))},
(* for (key, argument) in beginsong *)
((key))={((argument))},
(* endfor *)
]
(* if (metadata.cov is defined) or (metadata.vcov is defined) *)
\cover
(* endif *)
(* for item in content *)
(( render(item) ))
(* endfor *)
\endsong
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

1
patacrep/songs/chordpro/data/latex/content_chord

@ -0,0 +1 @@
((- content.chord|replace("b", "&") -))

1
patacrep/songs/chordpro/data/latex/content_chord.tex

@ -1 +0,0 @@
\[(( content.value ))]

8
patacrep/songs/chordpro/data/latex/content_chordlist

@ -0,0 +1,8 @@
(* if content.chords *)
\[
(*- for chord in content.chords -*)
(* if not loop.first *) (* endif -*)
(( render(chord) -))
(* endfor -*)
]
(*- endif -*)

0
patacrep/songs/chordpro/data/latex/content_comment.tex → patacrep/songs/chordpro/data/latex/content_comment

24
patacrep/songs/chordpro/data/latex/content_define

@ -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 -*)
}

3
patacrep/songs/chordpro/data/latex/content_error

@ -0,0 +1,3 @@
ERROR : Template not found for \verb+(( content.__class__.__name__ ))+. See the logs for details.

3
patacrep/songs/chordpro/data/latex/content_error.tex

@ -1,3 +0,0 @@
ERROR : Template not found for \verb+(( content.__class__))+. See the logs for details.

0
patacrep/songs/chordpro/data/latex/content_guitar_comment.tex → patacrep/songs/chordpro/data/latex/content_guitar_comment

1
patacrep/songs/chordpro/data/latex/content_image

@ -0,0 +1 @@
\image{(( content.argument|search_image(path, config) ))}

1
patacrep/songs/chordpro/data/latex/content_image.tex

@ -1 +0,0 @@
\image{(( content.argument ))}

3
patacrep/songs/chordpro/data/latex/content_line

@ -0,0 +1,3 @@
(* for item in content.line -*)
(( render(item) ))
(*- endfor *)

1
patacrep/songs/chordpro/data/latex/content_line.tex

@ -1 +0,0 @@
(* for item in content.line *)(( render(item) ))(* endfor *)

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

@ -0,0 +1,2 @@

1
patacrep/songs/chordpro/data/latex/content_partition

@ -0,0 +1 @@
\lilypond{ ((- content.argument|search_image(path, config) -)) }

1
patacrep/songs/chordpro/data/latex/content_partition.tex

@ -1 +0,0 @@
\lilypond{((content.argument))}

1
patacrep/songs/chordpro/data/latex/content_space

@ -0,0 +1 @@

5
patacrep/songs/chordpro/data/latex/content_tablature

@ -0,0 +1,5 @@
\begin{verbatim}
(* for content in content.content *)
((- content ))
(* endfor *)
\end{verbatim}

5
patacrep/songs/chordpro/data/latex/content_verse

@ -0,0 +1,5 @@
\begin{(( content.type ))}
(* for line in content.lines *)
(( render(line) ))
(* endfor *)
\end{(( content.type ))}

6
patacrep/songs/chordpro/data/latex/content_verse.tex

@ -1,6 +0,0 @@
\begin{(( content.type ))}
(* for line in content.lines *)
(( render(line) ))
(* endfor *)
\end{(( content.type ))}

1
patacrep/songs/chordpro/data/latex/content_word

@ -0,0 +1 @@
(( content.value ))

50
patacrep/songs/chordpro/data/latex/song

@ -0,0 +1,50 @@
(* if language is defined -*)
\selectlanguage{((language))}
(* endif *)
(*- if metadata.columns is defined *)
\songcolumns{(( metadata.columns ))}
(* endif *)
\beginsong{
(*- for title in titles -*)
(( title ))
(*- if not loop.last -*)
\\
(* endif *)
(* endfor -*)
}[
by={
(* for author in authors *)
(( author[1] )) (( author[0] ))
(*- if not loop.last -*)
,
(* endif *)
(* endfor *)
},
(* for key in ['album', 'copyright', 'tag'] *)
(* if key in metadata *)
(( key ))={(( metadata[key] ))},
(* endif *)
(* endfor *)
(* if 'cov' in metadata *)
cov={(( metadata["cov"].argument|search_image(path, config) ))},
(* endif *)
(* for key in metadata.keys *)
(( key.keyword ))={(( key.argument ))},
(* endfor *)
]
(* if (metadata.cov is defined) *)
\cover
(* endif *)
(*- for chord in metadata['define'] -*)
(( render(chord) ))
(* endfor *)
(* for item in content -*)
(( render(item) ))
(* endfor *)
\endsong

38
patacrep/songs/chordpro/lexer.py

@ -9,11 +9,11 @@ LOGGER = logging.getLogger()
tokens = ( tokens = (
'LBRACE', 'LBRACE',
'RBRACE', 'RBRACE',
'CHORD',
'NEWLINE', 'NEWLINE',
'COLON', 'COLON',
'WORD', 'WORD',
'SPACE', 'SPACE',
'CHORD',
'TEXT', 'TEXT',
'KEYWORD', 'KEYWORD',
'SOC', 'SOC',
@ -39,7 +39,7 @@ class ChordProLexer:
t_SPACE = r'[ \t]+' t_SPACE = r'[ \t]+'
t_chord_CHORD = r'[A-G7#m]+' t_chord_CHORD = r'[^\]]+'
t_directive_SPACE = r'[ \t]+' t_directive_SPACE = r'[ \t]+'
t_directive_KEYWORD = r'[a-zA-Z_]+' t_directive_KEYWORD = r'[a-zA-Z_]+'
@ -133,28 +133,32 @@ class ChordProLexer:
return token return token
@staticmethod @staticmethod
def t_error(token): def error(token, more=""):
"""Manage errors""" """Display error message, and skip illegal token."""
LOGGER.error("Illegal character '{}'".format(token.value[0])) LOGGER.error(
"Line {line}: Illegal character '{char}'{more}.".format(
line=token.lexer.lineno,
char=token.value[0],
more=more,
)
)
token.lexer.skip(1) token.lexer.skip(1)
@staticmethod def t_error(self, token):
def t_chord_error(token):
"""Manage errors""" """Manage errors"""
LOGGER.error("Illegal character '{}' in chord..".format(token.value[0])) self.error(token)
token.lexer.skip(1)
@staticmethod def t_chord_error(self, token):
def t_tablature_error(token):
"""Manage errors""" """Manage errors"""
LOGGER.error("Illegal character '{}' in tablature..".format(token.value[0])) self.error(token, more=" in chord")
token.lexer.skip(1)
@staticmethod def t_tablature_error(self, token):
def t_directive_error(token):
"""Manage errors""" """Manage errors"""
LOGGER.error("Illegal character '{}' in directive..".format(token.value[0])) self.error(token, more=" in tablature")
token.lexer.skip(1)
def t_directive_error(self, token):
"""Manage errors"""
self.error(token, more=" in directive")
def t_directiveargument_error(self, token): def t_directiveargument_error(self, token):
"""Manage errors""" """Manage errors"""

109
patacrep/songs/chordpro/syntax.py

@ -1,16 +1,15 @@
"""ChordPro parser""" """ChordPro parser"""
import logging
import ply.yacc as yacc import ply.yacc as yacc
import re
from patacrep.songs.syntax import Parser from patacrep.songs.syntax import Parser
from patacrep.songs.chordpro import ast from patacrep.songs.chordpro import ast
from patacrep.songs.chordpro.lexer import tokens, ChordProLexer from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
LOGGER = logging.getLogger()
class ChordproParser(Parser): class ChordproParser(Parser):
"""ChordPro parser class""" """ChordPro parser class"""
# pylint: disable=too-many-public-methods
start = "song" start = "song"
@ -23,7 +22,6 @@ class ChordproParser(Parser):
"""song : block song """song : block song
| empty | empty
""" """
#if isinstance(symbols[1], str):
if len(symbols) == 2: if len(symbols) == 2:
symbols[0] = ast.Song(self.filename) symbols[0] = ast.Song(self.filename)
else: else:
@ -54,28 +52,113 @@ class ChordproParser(Parser):
symbols[0] = None symbols[0] = None
@staticmethod @staticmethod
def p_directive(symbols): def _parse_define(groups):
"""Parse a `{define: KEY base-fret BASE frets FRETS fingers FINGERS}` directive
Return a :class:`ast.Define` object.
"""
# pylint: disable=too-many-branches
if not groups['key'].strip():
return None
else:
key = ast.Chord(groups['key'].strip())
if groups['basefret'] is None:
basefret = None
else:
basefret = int(groups['basefret'])
if groups['frets'] is None:
frets = None
else:
frets = []
for fret in groups['frets'].split():
if fret in "xX":
frets.append(None)
else:
frets.append(int(fret))
if groups['fingers'] is None:
fingers = None
else:
fingers = []
for finger in groups['fingers'].split():
if finger == '-':
fingers.append(None)
else:
fingers.append(int(finger))
return ast.Define(
key=key,
basefret=basefret,
frets=frets,
fingers=fingers,
)
def p_directive(self, symbols):
"""directive : LBRACE KEYWORD directive_next RBRACE """directive : LBRACE KEYWORD directive_next RBRACE
| LBRACE SPACE KEYWORD directive_next RBRACE | LBRACE SPACE KEYWORD directive_next RBRACE
""" """
if len(symbols) == 5: if len(symbols) == 5:
symbols[3].keyword = symbols[2] keyword = symbols[2]
symbols[0] = symbols[3] argument = symbols[3]
else:
keyword = symbols[3]
argument = symbols[4]
if keyword == "define":
match = re.compile(
r"""
^
(?P<key>[^\ ]*)\ *
(base-fret\ *(?P<basefret>\d{1,2}))?\ *
frets\ *(?P<frets>((\d+|x|X)\ *)+)\ *
(fingers\ *(?P<fingers>(([0-4-])\ *)*))?
$
""",
re.VERBOSE
).match(argument)
if match is None:
if argument.strip():
self.error(
line=symbols.lexer.lineno,
message="Invalid chord definition '{}'.".format(argument),
)
else:
self.error(
line=symbols.lexer.lineno,
message="Invalid empty chord definition.",
)
symbols[0] = ast.Error()
return
symbols[0] = self._parse_define(match.groupdict())
if symbols[0] is None:
self.error(
line=symbols.lexer.lineno,
message="Invalid chord definition '{}'.".format(argument),
)
symbols[0] = ast.Error()
else: else:
symbols[4].keyword = symbols[3] symbols[0] = ast.Directive(keyword, argument)
symbols[0] = symbols[4]
@staticmethod @staticmethod
def p_directive_next(symbols): def p_directive_next(symbols):
"""directive_next : SPACE COLON TEXT """directive_next : SPACE COLON TEXT
| COLON TEXT | COLON TEXT
| COLON
| empty | empty
""" """
symbols[0] = ast.Directive()
if len(symbols) == 3: if len(symbols) == 3:
symbols[0].argument = symbols[2].strip() symbols[0] = symbols[2].strip()
elif len(symbols) == 4: elif len(symbols) == 4:
symbols[0].argument = symbols[3].strip() symbols[0] = symbols[3].strip()
elif len(symbols) == 2 and symbols[1] == ":":
symbols[0] = ""
else:
symbols[0] = None
@staticmethod @staticmethod
def p_line(symbols): def p_line(symbols):
@ -109,7 +192,7 @@ class ChordproParser(Parser):
@staticmethod @staticmethod
def p_chord(symbols): def p_chord(symbols):
"""chord : CHORD""" """chord : CHORD"""
symbols[0] = ast.Chord(symbols[1]) symbols[0] = ast.ChordList(*[ast.Chord(chord) for chord in symbols[1].split()])
@staticmethod @staticmethod
def p_chorus(symbols): def p_chorus(symbols):

1
patacrep/songs/chordpro/test/00.sgc

@ -0,0 +1 @@
{language: english}

0
patacrep/songs/chordpro/test/00.source

10
patacrep/songs/chordpro/test/00.tex

@ -0,0 +1,10 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\endsong

1
patacrep/songs/chordpro/test/00.txt

@ -1 +0,0 @@
========

6
patacrep/songs/chordpro/test/01.sgc

@ -1 +1,5 @@
A verse line {language: english}
{start_of_verse}
A verse line
{end_of_verse}

1
patacrep/songs/chordpro/test/01.source

@ -0,0 +1 @@
A verse line

13
patacrep/songs/chordpro/test/01.tex

@ -0,0 +1,13 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\begin{verse}
A verse line
\end{verse}
\endsong

4
patacrep/songs/chordpro/test/01.txt

@ -1,4 +0,0 @@
========
{start_of_verse}
A verse line
{end_of_verse}

4
patacrep/songs/chordpro/test/02.sgc

@ -1 +1,3 @@
{title : A directive} {language: english}
{title: A directive}

1
patacrep/songs/chordpro/test/02.source

@ -0,0 +1 @@
{title : A directive}

10
patacrep/songs/chordpro/test/02.tex

@ -0,0 +1,10 @@
\selectlanguage{english}
\beginsong{A directive}[
by={
},
]
\endsong

2
patacrep/songs/chordpro/test/02.txt

@ -1,2 +0,0 @@
{title: A directive}
========

4
patacrep/songs/chordpro/test/03.sgc

@ -1,4 +1,2 @@
{language: english}

4
patacrep/songs/chordpro/test/03.source

@ -0,0 +1,4 @@

12
patacrep/songs/chordpro/test/03.tex

@ -0,0 +1,12 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\endsong

1
patacrep/songs/chordpro/test/03.txt

@ -1 +0,0 @@
========

8
patacrep/songs/chordpro/test/04.sgc

@ -1,3 +1,5 @@
{soc} {language: english}
A one line chorus
{eoc} {start_of_chorus}
A one line chorus
{end_of_chorus}

3
patacrep/songs/chordpro/test/04.source

@ -0,0 +1,3 @@
{soc}
A one line chorus
{eoc}

13
patacrep/songs/chordpro/test/04.tex

@ -0,0 +1,13 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\begin{chorus}
A one line chorus
\end{chorus}
\endsong

4
patacrep/songs/chordpro/test/04.txt

@ -1,4 +0,0 @@
========
{start_of_chorus}
A one line chorus
{end_of_chorus}

8
patacrep/songs/chordpro/test/05.sgc

@ -1,3 +1,5 @@
{sob} {language: english}
A one line bridge
{eob} {start_of_bridge}
A one line bridge
{end_of_bridge}

3
patacrep/songs/chordpro/test/05.source

@ -0,0 +1,3 @@
{sob}
A one line bridge
{eob}

13
patacrep/songs/chordpro/test/05.tex

@ -0,0 +1,13 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\begin{bridge}
A one line bridge
\end{bridge}
\endsong

4
patacrep/songs/chordpro/test/05.txt

@ -1,4 +0,0 @@
========
{start_of_bridge}
A one line bridge
{end_of_bridge}

3
patacrep/songs/chordpro/test/06.sgc

@ -1 +1,2 @@
# A comment {language: english}

1
patacrep/songs/chordpro/test/06.source

@ -0,0 +1 @@
# A comment

12
patacrep/songs/chordpro/test/06.tex

@ -0,0 +1,12 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\endsong

1
patacrep/songs/chordpro/test/06.txt

@ -1 +0,0 @@
========

6
patacrep/songs/chordpro/test/07.sgc

@ -1,3 +1,5 @@
{sot} {language: english}
{start_of_tab}
A tab A tab
{eot} {end_of_tab}

3
patacrep/songs/chordpro/test/07.source

@ -0,0 +1,3 @@
{sot}
A tab
{eot}

13
patacrep/songs/chordpro/test/07.tex

@ -0,0 +1,13 @@
\selectlanguage{english}
\beginsong{}[
by={
},
]
\begin{verbatim}
A tab
\end{verbatim}
\endsong

4
patacrep/songs/chordpro/test/07.txt

@ -1,4 +0,0 @@
========
{start_of_tab}
A tab
{end_of_tab}

11
patacrep/songs/chordpro/test/08.sgc

@ -1,10 +1,7 @@
{language: english}
# comment {start_of_verse}
# comment A lot of new lines
{end_of_verse}
A lot of new lines
# comment

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save