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': [],
'titleprefixwords': [],
'encoding': None,
'datadir': [],
}
@ -113,7 +114,7 @@ class Songbook(object):
)
# Configuration set
config['render_content'] = content.render_content
config['render'] = content.render
config['content'] = content.process_content(
config.get('content', []),
config,

2
patacrep/content/__init__.py

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

12
patacrep/content/song.py

@ -4,6 +4,7 @@ import glob
import jinja2
import logging
import os
import textwrap
from patacrep.content import process_content, ContentError, Content
from patacrep import files, errors
@ -34,7 +35,16 @@ class SongRenderer(Content):
def render(self, context):
"""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
def parse(keyword, argument, contentlist, config):

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

@ -1,11 +1,12 @@
{
"bookoptions" : [
"importantdiagramonly",
"diagram",
"repeatchords",
"lilypond",
"pictures"
],
"booktype" : "chorded",
"template" : "patacrep.tex",
"lang" : "french",
"encoding": "utf8",
"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}
{artist: Traditionnel}
{artist: Prénom Nom}
{cover : traditionnel }
{cover : traditionnel.jpg }
{album :Angleterre}
{partition : greensleeves.ly}
@ -41,7 +41,7 @@ And [Am]all this [E]cost I [Am]spent on thee
{gc: test of guitar comment}
{image: traditionnel}
{image: traditionnel.jpg}
Thy [Am]smock of silke, both [G]faire and white
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{xstring}
\RequirePackage{framed}
\RequirePackage{currfile}
\RequirePackage{ifthen}
\RequirePackage{tikz}
\RequirePackage{import}
% tabs: display the guitar tabs
\newif{\iftabs}
@ -134,8 +134,7 @@
\setlength{\coverspace}{0.1cm}
\newcommand{\songcover}{}
\newcommand{\songalbum}{}
\newsongkey{cov}{\let\songcover\@empty}{\def\songcover{\currfiledir#1}}
\newsongkey{vcov}{\let\songcover\@empty}{\def\songcover{#1}}
\newsongkey{cov}{\let\songcover\@empty}{\def\songcover{#1}}
\newsongkey{album}{\let\songalbum\@empty}{\def\songalbum{#1}}
\newsongkey{url}{\let\songurl\@empty}{\def\songurl{#1}}
\newsongkey{original}{\let\songoriginal\@empty}{\def\songoriginal{#1}}
@ -247,11 +246,6 @@
\newcommand{\lilypond}[1]{%
\iflilypond%
\epstopdfsetup{suffix=-\the\hsize-converted}
\includegraphics{\currfiledir#1}%
\fi%
}
\newcommand{\vlilypond}[1]{%
\iflilypond%
\includegraphics{#1}%
\fi%
}

119
patacrep/data/templates/default.tex

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

56
patacrep/data/templates/layout.tex

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

99
patacrep/data/templates/songs.tex

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

55
patacrep/songs/__init__.py

@ -10,7 +10,6 @@ import re
from patacrep.authors import process_listauthors
from patacrep import files, encoding
from patacrep.content import Content
LOGGER = logging.getLogger(__name__)
@ -63,25 +62,25 @@ class DataSubpath(object):
return self
# pylint: disable=too-many-instance-attributes
class Song(Content):
class Song:
"""Song (or song metadata)
This class represents a song, bound to a file.
- 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
file is not parsed again.
This class is inherited by classes implementing song management for
several file formats. Those subclasses must implement:
- `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
# information stored in cache.
CACHE_VERSION = 1
CACHE_VERSION = 2
# List of attributes to cache
cached_attributes = [
@ -144,7 +143,7 @@ class Song(Content):
]
self.authors = process_listauthors(
self.authors,
**config["_compiled_authwords"]
**config.get("_compiled_authwords", {})
)
# Cache management
@ -166,12 +165,16 @@ class Song(Content):
def __repr__(self):
return repr((self.titles, self.data, self.fullpath))
def tex(self, output): # pylint: disable=no-self-use, unused-argument
"""Return the LaTeX code rendering this song.
def render(self, output, output_format):
"""Return the code rendering this song.
Arguments:
- 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()
def parse(self, config): # pylint: disable=no-self-use
@ -199,3 +202,39 @@ def unprefixed_title(title, prefixes):
if match:
return match.group(2)
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
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.templates import TexRenderer
from patacrep.templates import Renderer
class ChordproSong(Song):
"""Chordpros song parser."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.texenv = None
self.jinjaenv = None
def parse(self, config):
"""Parse content, and return the dictionary of song data."""
@ -22,42 +22,46 @@ class ChordproSong(Song):
song = parse_song(song.read(), self.fullpath)
self.authors = song.authors
self.titles = song.titles
self.languages = song.get_directives('language')
self.data = dict([meta.as_tuple for meta in song.meta])
self.languages = song.get_data_argument('language', [self.config['lang']])
self.data = song.meta
self.cached = {
'song': song,
}
def tex(self, output):
def render(self, output, output_format):
context = {
'language': self.cached['song'].get_directive('language', self.config['lang']),
'columns': self.cached['song'].get_directive('columns', 1),
'language': self.languages[0],
"path": files.relpath(self.fullpath, os.path.dirname(output)),
"titles": r"\\".join(self.titles),
"authors": ", ".join(["{} {}".format(name[1], name[0]) for name in self.authors]),
"titles": self.titles,
"authors": self.authors,
"metadata": self.data,
"beginsong": self.cached['song'].meta_beginsong(),
"render": self.render_tex,
"render": self._render_ast,
"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')),
'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
def render_tex(self, context, content, template=None):
"""Render ``content`` as tex."""
def _render_ast(self, context, content, template=None):
"""Render ``content``."""
if isinstance(context, dict):
context['content'] = content
else:
context.vars['content'] = content
if template is None:
template = content.template('tex')
return TexRenderer(
template = content.template()
return Renderer(
template=template,
encoding='utf8',
texenv=self.texenv,
jinjaenv=self.jinjaenv,
).template.render(context)
SONG_PARSERS = {

290
patacrep/songs/chordpro/ast.py

@ -2,12 +2,43 @@
# pylint: disable=too-few-public-methods
import functools
import logging
import os
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):
"""Return and indented version of argument."""
return "\n".join([" {}".format(line) for line in string.split('\n')])
@ -21,15 +52,6 @@ INLINE_PROPERTIES = {
"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:
#: Foo}` are equivalent.
DIRECTIVE_SHORTCUTS = {
@ -40,7 +62,6 @@ DIRECTIVE_SHORTCUTS = {
"c": "comment",
"gc": "guitar_comment",
"cover": "cov",
"vcover": "vcov",
}
def directive_name(text):
@ -53,14 +74,17 @@ class AST:
_template = None
inline = False
def template(self, extension):
def template(self):
"""Return the template to be used to render this object."""
if self._template is None:
LOGGER.warning("No template defined for {}.".format(self.__class__))
base = "error"
else:
base = self._template
return "content_{}.{}".format(base, extension)
return "content_{}".format(base)
class Error(AST):
"""Parsing error. To be ignored."""
class Line(AST):
"""A line is a sequence of (possibly truncated) words, spaces and chords."""
@ -76,9 +100,6 @@ class Line(AST):
self.line.insert(0, data)
return self
def __str__(self):
return "".join([str(item) for item in self.line])
def strip(self):
"""Remove spaces at the beginning and end of line."""
while True:
@ -94,6 +115,7 @@ class Line(AST):
class LineElement(AST):
"""Something present on a line."""
# pylint: disable=abstract-method
pass
class Word(LineElement):
@ -104,9 +126,6 @@ class Word(LineElement):
super().__init__()
self.value = value
def __str__(self):
return self.value
class Space(LineElement):
"""A space between words"""
_template = "space"
@ -114,20 +133,21 @@ class Space(LineElement):
def __init__(self):
super().__init__()
def __str__(self):
return " "
class ChordList(LineElement):
"""A list of chords."""
_template = "chordlist"
def __init__(self, *chords):
self.chords = chords
class Chord(LineElement):
class Chord(AST):
"""A chord."""
_template = "chord"
def __init__(self, value):
super().__init__()
self.value = value
def __str__(self):
return "[{}]".format(self.value)
def __init__(self, chord):
# pylint: disable=too-many-arguments
self.chord = chord
class Verse(AST):
"""A verse (or bridge, or chorus)"""
@ -144,12 +164,6 @@ class Verse(AST):
self.lines.insert(0, data)
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):
"""Chorus"""
type = 'chorus'
@ -167,30 +181,23 @@ class Song(AST):
- titles: The list of titles
- language: The language (if set), None otherwise
- 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.
"""
#: Some directives are added to the song using special methods.
METADATA_TYPE = {
METADATA_ADD = {
"title": "add_title",
"subtitle": "add_subtitle",
"artist": "add_author",
"key": "add_key",
}
#: Some directives have to be processed before being considered.
PROCESS_DIRECTIVE = {
"cov": "_process_relative",
"partition": "_process_relative",
"image": "_process_relative",
"define": "add_cumulative",
"language": "add_cumulative",
}
def __init__(self, filename):
super().__init__()
self.content = []
self.meta = []
self.meta = OrderedLifoDict()
self._authors = []
self._titles = []
self._subtitles = []
@ -199,13 +206,9 @@ class Song(AST):
def add(self, data):
"""Add an element to the song"""
if isinstance(data, Directive):
# Some directives are preprocessed
name = directive_name(data.keyword)
if name in self.PROCESS_DIRECTIVE:
data = getattr(self, self.PROCESS_DIRECTIVE[name])(data)
if data is None:
if isinstance(data, Error):
return self
elif data is None:
# New line
if not (self.content and isinstance(self.content[0], Newline)):
self.content.insert(0, Newline())
@ -219,127 +222,78 @@ class Song(AST):
self.content.insert(0, data)
elif isinstance(data, Directive):
# Add a metadata directive. Some of them are added using special
# methods listed in ``METADATA_TYPE``.
name = directive_name(data.keyword)
if name in self.METADATA_TYPE:
getattr(self, self.METADATA_TYPE[name])(*data.as_tuple)
# methods listed in ``METADATA_ADD``.
if data.keyword in self.METADATA_ADD:
getattr(self, self.METADATA_ADD[data.keyword])(data)
else:
self.meta.append(data)
self.meta[data.keyword] = data
else:
raise Exception()
return self
def str_meta(self):
"""Return an iterator over *all* metadata, as strings."""
for title in self.titles:
yield "{{title: {}}}".format(title)
for author in self.authors:
yield "{{by: {}}}".format(author)
for key in sorted(self.keys):
yield "{{key: {}}}".format(str(key))
for key in sorted(self.meta):
yield str(key)
def add_title(self, data):
"""Add a title"""
self._titles.insert(0, 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)
def __str__(self):
return (
"\n".join(self.str_meta()).strip()
+
"\n========\n"
+
"\n".join([str(item) for item in self.content]).strip()
)
def get_data_argument(self, keyword, default):
"""Return `self.meta[keyword].argument`.
Return `default` if `self.meta[keyword]` does not exist.
def add_title(self, __ignored, title):
"""Add a title"""
self._titles.insert(0, title)
If `self.meta[keyword]` is a list, return the list of `item.argument`
for each item in the list.
"""
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"""
self._subtitles.insert(0, title)
self._subtitles.insert(0, data.argument)
@property
def titles(self):
"""Return the list of titles (and subtitles)."""
return self._titles + self._subtitles
def add_author(self, __ignored, title):
def add_author(self, data):
"""Add an auhor."""
self._authors.insert(0, title)
self._authors.insert(0, data.argument)
@property
def authors(self):
"""Return the list of (raw) authors."""
return self._authors
def get_directive(self, key, default=None):
"""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):
def add_key(self, data):
"""Add a new {key: foo: bar} directive."""
key, *argument = argument.split(":")
self._keys.append(Directive(
key, *argument = data.argument.split(":")
if 'keys' not in self.meta:
self.meta['keys'] = []
self.meta['keys'].insert(0, Directive(
key.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):
"""New line"""
_template = "newline"
def __str__(self):
return ""
@functools.total_ordering
class Directive(AST):
"""A directive"""
def __init__(self, keyword="", argument=None):
def __init__(self, keyword, argument=None):
super().__init__()
self._keyword = None
self.keyword = keyword
self.keyword = directive_name(keyword.strip())
self.argument = argument
@property
@ -350,49 +304,47 @@ class Directive(AST):
"""
return self.keyword
@property
def keyword(self):
"""Keyword of the directive."""
return self._keyword
@property
def inline(self):
"""True iff this directive is to be rendered in the flow on the song.
"""
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):
if self.argument is not None:
return "{{{}: {}}}".format(
self.keyword,
self.argument,
)
else:
return "{{{}}}".format(self.keyword)
return self.argument
@property
def as_tuple(self):
"""Return the directive as a tuple."""
return (self.keyword, self.argument)
class Define(Directive):
"""A chord definition.
Attributes:
def __eq__(self, other):
return self.as_tuple == other.as_tuple
.. 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.
"""
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):
return self.as_tuple < other.as_tuple
def __str__(self):
return None
class Tab(AST):
"""Tablature"""
inline = True
_template = "tablature"
def __init__(self):
super().__init__()
@ -402,9 +354,3 @@ class Tab(AST):
"""Add an element at the beginning of content."""
self.content.insert(0, data)
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 = (
'LBRACE',
'RBRACE',
'CHORD',
'NEWLINE',
'COLON',
'WORD',
'SPACE',
'CHORD',
'TEXT',
'KEYWORD',
'SOC',
@ -39,7 +39,7 @@ class ChordProLexer:
t_SPACE = r'[ \t]+'
t_chord_CHORD = r'[A-G7#m]+'
t_chord_CHORD = r'[^\]]+'
t_directive_SPACE = r'[ \t]+'
t_directive_KEYWORD = r'[a-zA-Z_]+'
@ -133,28 +133,32 @@ class ChordProLexer:
return token
@staticmethod
def t_error(token):
"""Manage errors"""
LOGGER.error("Illegal character '{}'".format(token.value[0]))
def error(token, more=""):
"""Display error message, and skip illegal token."""
LOGGER.error(
"Line {line}: Illegal character '{char}'{more}.".format(
line=token.lexer.lineno,
char=token.value[0],
more=more,
)
)
token.lexer.skip(1)
@staticmethod
def t_chord_error(token):
def t_error(self, token):
"""Manage errors"""
LOGGER.error("Illegal character '{}' in chord..".format(token.value[0]))
token.lexer.skip(1)
self.error(token)
@staticmethod
def t_tablature_error(token):
def t_chord_error(self, token):
"""Manage errors"""
LOGGER.error("Illegal character '{}' in tablature..".format(token.value[0]))
token.lexer.skip(1)
self.error(token, more=" in chord")
@staticmethod
def t_directive_error(token):
def t_tablature_error(self, token):
"""Manage errors"""
LOGGER.error("Illegal character '{}' in directive..".format(token.value[0]))
token.lexer.skip(1)
self.error(token, more=" in tablature")
def t_directive_error(self, token):
"""Manage errors"""
self.error(token, more=" in directive")
def t_directiveargument_error(self, token):
"""Manage errors"""

109
patacrep/songs/chordpro/syntax.py

@ -1,16 +1,15 @@
"""ChordPro parser"""
import logging
import ply.yacc as yacc
import re
from patacrep.songs.syntax import Parser
from patacrep.songs.chordpro import ast
from patacrep.songs.chordpro.lexer import tokens, ChordProLexer
LOGGER = logging.getLogger()
class ChordproParser(Parser):
"""ChordPro parser class"""
# pylint: disable=too-many-public-methods
start = "song"
@ -23,7 +22,6 @@ class ChordproParser(Parser):
"""song : block song
| empty
"""
#if isinstance(symbols[1], str):
if len(symbols) == 2:
symbols[0] = ast.Song(self.filename)
else:
@ -54,28 +52,113 @@ class ChordproParser(Parser):
symbols[0] = None
@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
| LBRACE SPACE KEYWORD directive_next RBRACE
"""
if len(symbols) == 5:
symbols[3].keyword = symbols[2]
symbols[0] = symbols[3]
keyword = symbols[2]
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:
symbols[4].keyword = symbols[3]
symbols[0] = symbols[4]
symbols[0] = ast.Directive(keyword, argument)
@staticmethod
def p_directive_next(symbols):
"""directive_next : SPACE COLON TEXT
| COLON TEXT
| COLON
| empty
"""
symbols[0] = ast.Directive()
if len(symbols) == 3:
symbols[0].argument = symbols[2].strip()
symbols[0] = symbols[2].strip()
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
def p_line(symbols):
@ -109,7 +192,7 @@ class ChordproParser(Parser):
@staticmethod
def p_chord(symbols):
"""chord : CHORD"""
symbols[0] = ast.Chord(symbols[1])
symbols[0] = ast.ChordList(*[ast.Chord(chord) for chord in symbols[1].split()])
@staticmethod
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}
A one line chorus
{eoc}
{language: english}
{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}
A one line bridge
{eob}
{language: english}
{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
{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
# comment
A lot of new lines
# comment
{start_of_verse}
A lot of new lines
{end_of_verse}

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

Loading…
Cancel
Save