Browse Source

Merge branch 'master' into errors

pull/176/head
Louis 9 years ago
parent
commit
ab5aa859d3
  1. 17
      examples/example-all.yaml.sb
  2. 5
      patacrep/__init__.py
  3. 31
      patacrep/build.py
  4. 9
      patacrep/content/__init__.py
  5. 3
      patacrep/content/cwd.py
  6. 4
      patacrep/content/section.py
  7. 5
      patacrep/content/song.py
  8. 9
      patacrep/content/sorted.py
  9. 16
      patacrep/errors.py
  10. 8
      patacrep/index.py
  11. 2
      patacrep/latex/lexer.py
  12. 5
      patacrep/latex/syntax.py
  13. 6
      patacrep/songbook/__main__.py
  14. 14
      patacrep/songs/__init__.py
  15. 5
      patacrep/songs/chordpro/__init__.py
  16. 4
      patacrep/songs/chordpro/syntax.py
  17. 2
      patacrep/songs/convert/__main__.py
  18. 5
      patacrep/templates.py
  19. 2
      setup.py
  20. 12
      test/__init__.py
  21. 7
      test/test_compilation/datadir.sb
  22. 3
      test/test_compilation/languages.sb
  23. 4
      test/test_compilation/syntax.sb
  24. 4
      test/test_compilation/unicode.sb
  25. 0
      test/test_content/__init__.py
  26. 1
      test/test_content/cwd.control
  27. 1
      test/test_content/cwd.source
  28. 1
      test/test_content/cwd_list.control
  29. 1
      test/test_content/cwd_list.source
  30. 51
      test/test_content/datadir/songs/chordpro.csg
  31. 1
      test/test_content/datadir/songs/custom_list.json
  32. 0
      test/test_content/datadir/songs/exsong.sg
  33. 6
      test/test_content/datadir/songs/intersong.is
  34. 0
      test/test_content/datadir/songs/jsonlist.json
  35. 51
      test/test_content/datadir/songs/subdir/chordpro.csg
  36. 70
      test/test_content/datadir/songs/texfile.tex
  37. 70
      test/test_content/datadir/songs/texsong.tsg
  38. 1
      test/test_content/glob.control
  39. 1
      test/test_content/glob.source
  40. 1
      test/test_content/include.control
  41. 1
      test/test_content/include.source
  42. 1
      test/test_content/sections.control
  43. 6
      test/test_content/sections.source
  44. 1
      test/test_content/sections_short.control
  45. 6
      test/test_content/sections_short.source
  46. 1
      test/test_content/songs.control
  47. 1
      test/test_content/songs.source
  48. 1
      test/test_content/songsection.control
  49. 6
      test/test_content/songsection.source
  50. 1
      test/test_content/sorted.control
  51. 1
      test/test_content/sorted.source
  52. 118
      test/test_content/test_content.py
  53. 1
      test/test_content/tex.control
  54. 1
      test/test_content/tex.source
  55. 0
      test/test_song/00.csg
  56. 0
      test/test_song/00.csg.source
  57. 0
      test/test_song/00.tsg
  58. 0
      test/test_song/01.csg
  59. 0
      test/test_song/01.csg.source
  60. 0
      test/test_song/01.tsg
  61. 0
      test/test_song/02.csg
  62. 0
      test/test_song/02.csg.source
  63. 0
      test/test_song/02.tsg
  64. 0
      test/test_song/03.csg
  65. 0
      test/test_song/03.csg.source
  66. 0
      test/test_song/03.tsg
  67. 0
      test/test_song/04.csg
  68. 0
      test/test_song/04.csg.source
  69. 0
      test/test_song/04.tsg
  70. 0
      test/test_song/05.csg
  71. 0
      test/test_song/05.csg.source
  72. 0
      test/test_song/05.tsg
  73. 0
      test/test_song/06.csg
  74. 0
      test/test_song/06.csg.source
  75. 0
      test/test_song/06.tsg
  76. 0
      test/test_song/07.csg
  77. 0
      test/test_song/07.csg.source
  78. 0
      test/test_song/07.tsg
  79. 0
      test/test_song/08.csg
  80. 0
      test/test_song/08.csg.source
  81. 0
      test/test_song/08.tsg
  82. 0
      test/test_song/09.csg
  83. 0
      test/test_song/09.csg.source
  84. 0
      test/test_song/09.tsg
  85. 0
      test/test_song/10.csg
  86. 0
      test/test_song/10.csg.source
  87. 0
      test/test_song/10.tsg
  88. 0
      test/test_song/11.csg
  89. 0
      test/test_song/11.csg.source
  90. 0
      test/test_song/11.tsg
  91. 0
      test/test_song/12.csg
  92. 0
      test/test_song/12.csg.source
  93. 0
      test/test_song/12.tsg
  94. 0
      test/test_song/13.csg
  95. 0
      test/test_song/13.csg.source
  96. 0
      test/test_song/13.tsg
  97. 0
      test/test_song/21.csg
  98. 0
      test/test_song/21.csg.source
  99. 0
      test/test_song/21.tsg
  100. 0
      test/test_song/22.csg

17
examples/example-all.yaml.sb

@ -0,0 +1,17 @@
bookoptions:
- "diagram"
- "repeatchords"
- "lilypond"
- "pictures"
booktype: "chorded"
datadir: "."
template: "patacrep.tex"
lang: "fr"
encoding: "utf8"
authwords:
sep:
- "and"
- "et"
content:
-
- "sorted"

5
patacrep/__init__.py

@ -1,10 +1,11 @@
"""Global variables.""" """Global variables."""
from pkg_resources import resource_filename
import os import os
import sys
from pkg_resources import resource_filename
# Check Python version # Check Python version
import sys
if sys.version_info < (3, 3): if sys.version_info < (3, 3):
print("ERROR: Your Python version is too old. Please use a Python version > 3.3.") print("ERROR: Your Python version is too old. Please use a Python version > 3.3.")
sys.exit(1) sys.exit(1)

31
patacrep/build.py

@ -11,7 +11,7 @@ from subprocess import Popen, PIPE, call, check_call
from patacrep import authors, content, errors, files from patacrep import authors, content, errors, files
from patacrep.index import process_sxd from patacrep.index import process_sxd
from patacrep.templates import TexBookRenderer from patacrep.templates import TexBookRenderer
from patacrep.songs import DataSubpath from patacrep.songs import DataSubpath, DEFAULT_CONFIG
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
EOL = "\n" EOL = "\n"
@ -27,14 +27,6 @@ GENERATED_EXTENSIONS = [
"_title.sbx", "_title.sbx",
"_title.sxd", "_title.sxd",
] ]
DEFAULT_CONFIG = {
'template': "default.tex",
'lang': 'en',
'content': [],
'titleprefixwords': [],
'encoding': None,
'datadir': [],
}
@ -49,6 +41,7 @@ class Songbook:
def __init__(self, raw_songbook, basename): def __init__(self, raw_songbook, basename):
self._raw_config = raw_songbook self._raw_config = raw_songbook
self.config = raw_songbook
self.basename = basename self.basename = basename
self._errors = list() self._errors = list()
self._config = dict() self._config = dict()
@ -142,6 +135,10 @@ class Songbook:
continue continue
yield from item.iter_errors() yield from item.iter_errors()
def requires_lilypond(self):
"""Tell if lilypond is part of the bookoptions"""
return 'lilypond' in self.config.get('bookoptions', [])
def _log_pipe(pipe): def _log_pipe(pipe):
"""Log content from `pipe`.""" """Log content from `pipe`."""
while 1: while 1:
@ -243,9 +240,23 @@ class SongbookBuilder:
stderr=PIPE, stderr=PIPE,
universal_newlines=True, universal_newlines=True,
) )
except Exception as error: except FileNotFoundError as error:
raise errors.ExecutableNotFound(compiler) raise errors.ExecutableNotFound(compiler)
# Test if lilypond compiler is accessible
if self.songbook.requires_lilypond():
lilypond_compiler = 'lilypond'
try:
check_call(
[lilypond_compiler, "--version"],
stdin=PIPE,
stdout=PIPE,
stderr=PIPE,
universal_newlines=True,
)
except FileNotFoundError as error:
raise errors.ExecutableNotFound(lilypond_compiler)
# Perform compilation # Perform compilation
try: try:
process = Popen( process = Popen(

9
patacrep/content/__init__.py

@ -66,12 +66,13 @@ More documentation in the docstring of ContentItem.
""" """
import glob import glob
import jinja2
import logging import logging
import os import os
import re import re
import sys import sys
import jinja2
from patacrep import files from patacrep import files
from patacrep.errors import SharedError from patacrep.errors import SharedError
@ -123,7 +124,7 @@ class ContentItem:
class ContentError(SharedError): class ContentError(SharedError):
"""Error in a content plugin.""" """Error in a content plugin."""
def __init__(self, keyword=None, message=None): def __init__(self, keyword=None, message=None):
super(ContentError, self).__init__() super().__init__()
self.keyword = keyword self.keyword = keyword
self.message = message self.message = message
@ -217,14 +218,12 @@ def process_content(content, config=None):
""" """
contentlist = ContentList() contentlist = ContentList()
plugins = config.get('_content_plugins', {}) plugins = config.get('_content_plugins', {})
keyword_re = re.compile(r'^ *(?P<keyword>\w*) *(\((?P<argument>.*)\))? *$') keyword_re = re.compile(r'^ *(?P<keyword>[\w\*]*) *(\((?P<argument>.*)\))? *$')
if not content: if not content:
content = [["song"]] content = [["song"]]
for elem in content: for elem in content:
if isinstance(elem, str): if isinstance(elem, str):
elem = ["song", elem] elem = ["song", elem]
if len(content) == 0:
content = ["song"]
try: try:
match = keyword_re.match(elem[0]).groupdict() match = keyword_re.match(elem[0]).groupdict()
except AttributeError: except AttributeError:

3
patacrep/content/cwd.py

@ -25,8 +25,7 @@ def parse(keyword, config, argument, contentlist):
old_songdir = config['_songdir'] old_songdir = config['_songdir']
config['_songdir'] = ( config['_songdir'] = (
[DataSubpath(".", argument)] + [DataSubpath(".", argument)] +
[path.clone().join(argument) for path in config['_songdir']] + [path.clone().join(argument) for path in config['_songdir']]
config['_songdir']
) )
processed_content = process_content(contentlist, config) processed_content = process_content(contentlist, config)
config['_songdir'] = old_songdir config['_songdir'] = old_songdir

4
patacrep/content/section.py

@ -37,8 +37,8 @@ def parse(keyword, argument, contentlist, config):
their starred versions "part*", "chapter*", ... , "subparagraph*"): the their starred versions "part*", "chapter*", ... , "subparagraph*"): the
section to use; section to use;
- argument: unused; - argument: unused;
- contentlist: a list of one or two strings, which are the names (short - contentlist: a list of one or two strings, which are the names (long
and long) of the section; and short) of the section;
- config: configuration dictionary of the current songbook. - config: configuration dictionary of the current songbook.
""" """
try: try:

5
patacrep/content/song.py

@ -1,11 +1,12 @@
"""Plugin to include songs to the songbook.""" """Plugin to include songs to the songbook."""
import glob import glob
import jinja2
import logging import logging
import os import os
import textwrap import textwrap
import jinja2
from patacrep.content import process_content from patacrep.content import process_content
from patacrep.content import ContentError, ContentItem, ContentList from patacrep.content import ContentError, ContentItem, ContentList
from patacrep import files, errors from patacrep import files, errors
@ -83,6 +84,8 @@ def parse(keyword, argument, contentlist, config):
if not os.path.isdir(songdir.datadir): if not os.path.isdir(songdir.datadir):
continue continue
with files.chdir(songdir.datadir): with files.chdir(songdir.datadir):
# Starting with Python 3.5 glob can be recursive: **/*.csg for instance
# for filename in glob.iglob(os.path.join(songdir.subpath, elem), recursive=True):
for filename in glob.iglob(os.path.join(songdir.subpath, elem)): for filename in glob.iglob(os.path.join(songdir.subpath, elem)):
LOGGER.debug('Parsing file "{}"'.format(filename)) LOGGER.debug('Parsing file "{}"'.format(filename))
extension = filename.split(".")[-1] extension = filename.split(".")[-1]

9
patacrep/content/sorted.py

@ -9,8 +9,9 @@ import logging
import unidecode import unidecode
from patacrep import files from patacrep import files
from patacrep.content import ContentError, ContentList from patacrep.content import ContentError, ContentList, EmptyContentList
from patacrep.content.song import OnlySongsError, process_songs from patacrep.content import process_content
from patacrep.content.song import OnlySongsError
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -83,9 +84,9 @@ def parse(keyword, config, argument, contentlist):
else: else:
sort = DEFAULT_SORT sort = DEFAULT_SORT
try: try:
songlist = process_songs(contentlist, config) songlist = process_content(contentlist, config)
except OnlySongsError as error: except OnlySongsError as error:
return EmptyContentError(errors=[ContentError(keyword, ( return EmptyContentList(errors=[ContentError(keyword, (
"Content list of this keyword can be only songs (or content " "Content list of this keyword can be only songs (or content "
"that result into songs), and the following are not:" + "that result into songs), and the following are not:" +
str(error.not_songs) str(error.not_songs)

16
patacrep/errors.py

@ -11,7 +11,7 @@ class SBFileError(SongbookError):
"""Error during songbook file decoding""" """Error during songbook file decoding"""
def __init__(self, message=None): def __init__(self, message=None):
super(SBFileError, self).__init__() super().__init__()
self.message = message self.message = message
def __str__(self): def __str__(self):
@ -21,7 +21,7 @@ class TemplateError(SongbookError):
"""Error during template generation""" """Error during template generation"""
def __init__(self, original, message=None): def __init__(self, original, message=None):
super(TemplateError, self).__init__() super().__init__()
self.original = original self.original = original
self.message = message self.message = message
@ -35,7 +35,7 @@ class ExecutableNotFound(SongbookError):
"""Couldn't find a LaTeX executable.""" """Couldn't find a LaTeX executable."""
def __init__(self, executable): def __init__(self, executable):
super(ExecutableNotFound, self).__init__( super().__init__(
( (
"""Could not find the following executable: {executable}""" """Could not find the following executable: {executable}"""
).format(executable=executable) ).format(executable=executable)
@ -45,7 +45,7 @@ class StepError(SongbookError):
"""Error during execution of one compilation step.""" """Error during execution of one compilation step."""
def __init__(self, message): def __init__(self, message):
super(StepError, self).__init__() super().__init__()
self.message = message self.message = message
def __str__(self): def __str__(self):
@ -55,7 +55,7 @@ class LatexCompilationError(StepError):
"""Error during LaTeX compilation.""" """Error during LaTeX compilation."""
def __init__(self, basename): def __init__(self, basename):
super(LatexCompilationError, self).__init__( super().__init__(
( (
"""Error while LaTeX compilation of "{basename}.tex" """ """Error while LaTeX compilation of "{basename}.tex" """
"""(see {basename}.log for more information).""" """(see {basename}.log for more information)."""
@ -66,7 +66,7 @@ class StepCommandError(StepError):
"""Error during custom command compilation.""" """Error during custom command compilation."""
def __init__(self, command, code): def __init__(self, command, code):
super(StepCommandError, self).__init__(( super().__init__((
"""Error while running custom command "{command}": got return""" """Error while running custom command "{command}": got return"""
" code {code}." " code {code}."
).format(command=command, code=code)) ).format(command=command, code=code))
@ -76,7 +76,7 @@ class CleaningError(SongbookError):
"""Error during cleaning of LaTeX auxiliary files.""" """Error during cleaning of LaTeX auxiliary files."""
def __init__(self, filename, exception): def __init__(self, filename, exception):
super(CleaningError, self).__init__() super().__init__()
self.filename = filename self.filename = filename
self.exception = exception self.exception = exception
@ -90,7 +90,7 @@ class UnknownStep(StepError):
"""Unknown compilation step.""" """Unknown compilation step."""
def __init__(self, step): def __init__(self, step):
super(UnknownStep, self).__init__( super().__init__(
"""Compilation step "{step}" unknown.""".format(step=step) """Compilation step "{step}" unknown.""".format(step=step)
) )

8
patacrep/index.py

@ -6,8 +6,8 @@ from a file generated by the latex compilation of the songbook (.sxd).
""" """
import locale import locale
import unidecode
import re import re
import unidecode
from patacrep import authors from patacrep import authors
from patacrep import encoding from patacrep import encoding
@ -77,7 +77,7 @@ class Index:
def add_keyword(self, key, word): def add_keyword(self, key, word):
"""Add 'word' to self.keywords[key].""" """Add 'word' to self.keywords[key]."""
if not key in self.keywords: if key not in self.keywords:
self.keywords[key] = [] self.keywords[key] = []
self.keywords[key].append(word) self.keywords[key].append(word)
@ -101,9 +101,9 @@ class Index:
similar method with processing. similar method with processing.
""" """
first = self.get_first_letter(key[0]) first = self.get_first_letter(key[0])
if not first in self.data: if first not in self.data:
self.data[first] = dict() self.data[first] = dict()
if not key in self.data[first]: if key not in self.data[first]:
self.data[first][key] = { self.data[first][key] = {
'sortingkey': [ 'sortingkey': [
unidecode.unidecode(tex2plain(item)).lower() unidecode.unidecode(tex2plain(item)).lower()

2
patacrep/latex/lexer.py

@ -60,7 +60,7 @@ class SimpleLexer:
# Define a rule so we can track line numbers # Define a rule so we can track line numbers
@staticmethod @staticmethod
def t_endofline(token): def t_endofline(token):
r'\n+' r'(\r?\n)+'
token.lexer.lineno += len(token.value) token.lexer.lineno += len(token.value)
@staticmethod @staticmethod

5
patacrep/latex/syntax.py

@ -130,9 +130,12 @@ class LatexParser(Parser):
def p_dictionary(symbols): def p_dictionary(symbols):
"""dictionary : identifier EQUAL braces dictionary_next """dictionary : identifier EQUAL braces dictionary_next
| identifier EQUAL error dictionary_next | identifier EQUAL error dictionary_next
| empty
""" """
symbols[0] = {} symbols[0] = {}
if isinstance(symbols[3], ast.Expression): if len(symbols) == 2:
pass
elif isinstance(symbols[3], ast.Expression):
symbols[0][symbols[1]] = symbols[3] symbols[0][symbols[1]] = symbols[3]
symbols[0].update(symbols[4]) symbols[0].update(symbols[4])
else: else:

6
patacrep/songbook/__main__.py

@ -1,12 +1,12 @@
"""Command line tool to compile songbooks using the songbook library.""" """Command line tool to compile songbooks using the songbook library."""
import argparse import argparse
import json
import locale import locale
import logging import logging
import os.path import os.path
import textwrap import textwrap
import sys import sys
import yaml
from patacrep.build import SongbookBuilder, DEFAULT_STEPS from patacrep.build import SongbookBuilder, DEFAULT_STEPS
from patacrep.utils import yesno from patacrep.utils import yesno
@ -135,13 +135,13 @@ def main():
try: try:
with patacrep.encoding.open_read(songbook_path) as songbook_file: with patacrep.encoding.open_read(songbook_path) as songbook_file:
songbook = json.load(songbook_file) songbook = yaml.load(songbook_file)
if 'encoding' in songbook: if 'encoding' in songbook:
with patacrep.encoding.open_read( with patacrep.encoding.open_read(
songbook_path, songbook_path,
encoding=songbook['encoding'] encoding=songbook['encoding']
) as songbook_file: ) as songbook_file:
songbook = json.load(songbook_file) songbook = yaml.load(songbook_file)
except Exception as error: # pylint: disable=broad-except except Exception as error: # pylint: disable=broad-except
LOGGER.error(error) LOGGER.error(error)
LOGGER.error("Error while loading file '{}'.".format(songbook_path)) LOGGER.error("Error while loading file '{}'.".format(songbook_path))

14
patacrep/songs/__init__.py

@ -14,6 +14,15 @@ from patacrep.songs import errors as song_errors
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
DEFAULT_CONFIG = {
'template': "default.tex",
'lang': 'en',
'content': [],
'titleprefixwords': [],
'encoding': None,
'datadir': [],
}
def cached_name(datadir, filename): def cached_name(datadir, filename):
"""Return the filename of the cache version of the file.""" """Return the filename of the cache version of the file."""
fullpath = os.path.abspath(os.path.join(datadir, '.cache', filename)) fullpath = os.path.abspath(os.path.join(datadir, '.cache', filename))
@ -95,7 +104,10 @@ class Song:
"_version", "_version",
] ]
def __init__(self, subpath, config, *, datadir=None): def __init__(self, subpath, config=None, *, datadir=None):
if config is None:
config = DEFAULT_CONFIG.copy()
if datadir is None: if datadir is None:
self.datadir = "" self.datadir = ""
# Only songs in datadirs may be cached # Only songs in datadirs may be cached

5
patacrep/songs/chordpro/__init__.py

@ -1,11 +1,12 @@
"""Chordpro parser""" """Chordpro parser"""
from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader
import jinja2
import logging import logging
import operator import operator
import os import os
from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader
import jinja2
from patacrep import encoding, files, pkg_datapath from patacrep import encoding, files, pkg_datapath
from patacrep.songs import Song from patacrep.songs import Song
from patacrep.songs.chordpro.syntax import parse_song from patacrep.songs.chordpro.syntax import parse_song

4
patacrep/songs/chordpro/syntax.py

@ -2,11 +2,11 @@
import functools import functools
import logging import logging
import ply.yacc as yacc
import re import re
import ply.yacc as yacc
from patacrep.content import ContentError from patacrep.content import ContentError
from patacrep.songs import errors
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
from patacrep.songs.syntax import Parser from patacrep.songs.syntax import Parser

2
patacrep/songs/convert/__main__.py

@ -8,7 +8,7 @@ import logging
import sys import sys
from patacrep import files from patacrep import files
from patacrep.build import DEFAULT_CONFIG from patacrep.songs import DEFAULT_CONFIG
from patacrep.utils import yesno from patacrep.utils import yesno
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)

5
patacrep/templates.py

@ -1,11 +1,12 @@
"""Template for .tex generation settings and utilities""" """Template for .tex generation settings and utilities"""
import re
import json
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, \ from jinja2 import Environment, FileSystemLoader, ChoiceLoader, \
TemplateNotFound, nodes TemplateNotFound, nodes
from jinja2.ext import Extension from jinja2.ext import Extension
from jinja2.meta import find_referenced_templates as find_templates from jinja2.meta import find_referenced_templates as find_templates
import re
import json
from patacrep import errors, files from patacrep import errors, files
from patacrep.latex import lang2babel from patacrep.latex import lang2babel

2
setup.py

@ -34,7 +34,7 @@ setup(
packages=find_packages(exclude=["test*"]), packages=find_packages(exclude=["test*"]),
license="GPLv2 or any later version", license="GPLv2 or any later version",
install_requires=[ install_requires=[
"unidecode", "jinja2", "ply", "unidecode", "jinja2", "ply", "pyyaml",
], ],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

12
test/__init__.py

@ -9,11 +9,15 @@ import unittest
import patacrep import patacrep
@contextlib.contextmanager @contextlib.contextmanager
def disable_logging(): def logging_reduced(module_name=None, level=logging.CRITICAL):
"""Context locally disabling logging.""" """Temporarly reduce the logging level of a specific module or globally if None
logging.disable(logging.CRITICAL) """
logger = logging.getLogger(module_name)
old_level = logger.getEffectiveLevel()
logger.setLevel(level)
yield yield
logging.disable(logging.NOTSET) logger.setLevel(old_level)
def suite(): def suite():
"""Return a :class:`TestSuite` object, testing all module :mod:`patacrep`. """Return a :class:`TestSuite` object, testing all module :mod:`patacrep`.

7
test/test_compilation/datadir.sb

@ -1,7 +0,0 @@
{
"bookoptions" : [
"pictures"
],
"datadir": ["datadir_datadir", "datadir_datadir2"],
"lang": "en"
}

3
test/test_compilation/languages.sb

@ -1,3 +0,0 @@
{
"datadir": ["languages_datadir"]
}

4
test/test_compilation/syntax.sb

@ -1,4 +0,0 @@
{
"datadir": ["syntax_datadir"],
"lang": "en"
}

4
test/test_compilation/unicode.sb

@ -1,4 +0,0 @@
{
"datadir": ["unicode_datadir"],
"lang": "en"
}

0
test/test_chordpro/__init__.py → test/test_content/__init__.py

1
test/test_content/cwd.control

@ -0,0 +1 @@
["subdir/chordpro.csg"]

1
test/test_content/cwd.source

@ -0,0 +1 @@
[["cwd(subdir)"]]

1
test/test_content/cwd_list.control

@ -0,0 +1 @@
["subdir/chordpro.csg", "exsong.sg"]

1
test/test_content/cwd_list.source

@ -0,0 +1 @@
[["cwd(subdir)", "exsong.sg", "intersong.is", "jsonlist.json", "texfile.tex", "texsong.tsg", "chordpro.csg", "subdir/chordpro.csg"], "exsong.sg"]

51
test/test_content/datadir/songs/chordpro.csg

@ -0,0 +1,51 @@
{lang : en}
{columns : 2}
{ title : Greensleeves}
{subtitle: Test of the chordpro format}
{artist: Traditionnel}
{artist: Prénom Nom}
{cover : traditionnel.jpg }
{album :Angleterre}
{partition : greensleeves.ly}
A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously
And [Am]I have loved [G]you so long
De[Am]lighting [E]in your [Am]companie
{start_of_chorus}
[C]Greensleeves was [G]all my joy
[Am]Greensleeves was [E]my delight
[C]Greensleeves was my [G]heart of gold
And [Am]who but [E]Ladie [Am]Greensleeves
{end_of_chorus}
I [Am]have been ready [G]at your hand
To [Am]grant what ever [E]you would crave
I [Am]have both waged [G]life and land
Your [Am]love and [E]good will [Am]for to have
I [Am]bought thee kerchers [G]to thy head
That [Am]were wrought fine and [E]gallantly
I [Am]kept thee both at [G]boord and bed
Which [Am]cost my [E]purse well [Am]favouredly
I [Am]bought thee peticotes [G]of the best
The [Am]cloth so fine as [E]fine might be
I [Am]gave thee jewels [G]for thy chest
And [Am]all this [E]cost I [Am]spent on thee
{c:test of comment}
{gc: test of guitar comment}
{image: traditionnel.jpg}
Thy [Am]smock of silke, both [G]faire and white
With [Am]gold embrodered [E]gorgeously
Thy [Am]peticote of [G]sendall right
And [Am]this I [E]bought thee [Am]gladly

1
test/test_content/datadir/songs/custom_list.json

@ -0,0 +1 @@
["exsong.sg", "chordpro.csg", "subdir/chordpro.csg"]

0
test/test_compilation/datadir_datadir/songs/datadir2.sg → test/test_content/datadir/songs/exsong.sg

6
test/test_content/datadir/songs/intersong.is

@ -0,0 +1,6 @@
\selectlanguage{french}
\sortassong{}[by={QQ}]
\begin{intersong}
Lorem ipsum
\end{intersong}

0
test/test_chordpro/00.csg.source → test/test_content/datadir/songs/jsonlist.json

51
test/test_content/datadir/songs/subdir/chordpro.csg

@ -0,0 +1,51 @@
{lang : en}
{columns : 2}
{ title : Greensleeves}
{subtitle: Test of the chordpro format}
{artist: Traditionnel}
{artist: Prénom Nom}
{cover : traditionnel.jpg }
{album :Angleterre}
{partition : greensleeves.ly}
A[Am]las, my love, ye [G]do me wrong
To [Am]cast me oft dis[E]curteously
And [Am]I have loved [G]you so long
De[Am]lighting [E]in your [Am]companie
{start_of_chorus}
[C]Greensleeves was [G]all my joy
[Am]Greensleeves was [E]my delight
[C]Greensleeves was my [G]heart of gold
And [Am]who but [E]Ladie [Am]Greensleeves
{end_of_chorus}
I [Am]have been ready [G]at your hand
To [Am]grant what ever [E]you would crave
I [Am]have both waged [G]life and land
Your [Am]love and [E]good will [Am]for to have
I [Am]bought thee kerchers [G]to thy head
That [Am]were wrought fine and [E]gallantly
I [Am]kept thee both at [G]boord and bed
Which [Am]cost my [E]purse well [Am]favouredly
I [Am]bought thee peticotes [G]of the best
The [Am]cloth so fine as [E]fine might be
I [Am]gave thee jewels [G]for thy chest
And [Am]all this [E]cost I [Am]spent on thee
{c:test of comment}
{gc: test of guitar comment}
{image: traditionnel.jpg}
Thy [Am]smock of silke, both [G]faire and white
With [Am]gold embrodered [E]gorgeously
Thy [Am]peticote of [G]sendall right
And [Am]this I [E]bought thee [Am]gladly

70
test/test_content/datadir/songs/texfile.tex

@ -0,0 +1,70 @@
\selectlanguage{french}
\songcolumns{2}
\beginsong{Chevaliers de la table ronde}
[by={Traditionnel},cover={traditionnel},album={France}]
\cover
\gtab{C}{X32010}
\gtab{G7}{320001}
\gtab{F}{1:022100}
\begin{verse}
Cheva\[C]liers de la Table Ronde
Goûtons \[G7]voir si le vin est \[C]bon
\rep{2}
\end{verse}
\begin{chorus}
Goûtons \[F]voir, \echo{oui, oui, oui}
Goûtons \[C]voir, \echo{non, non, non}
Goûtons \[G7]voir si le vin est bon
\rep{2}
\end{chorus}
\begin{verse}
S'il est bon, s'il est agréable
J'en boirai jusqu'à mon plaisir
\end{verse}
\begin{verse}
J'en boirai cinq à six bouteilles
Et encore, ce n'est pas beaucoup
\end{verse}
\begin{verse}
Si je meurs, je veux qu'on m'enterre
Dans une cave où il y a du bon vin
\end{verse}
\begin{verse}
Les deux pieds contre la muraille
Et la tête sous le robinet
\end{verse}
\begin{verse}
Et les quatre plus grands ivrognes
Porteront les quatre coins du drap
\end{verse}
\begin{verse}
Pour donner le discours d'usage
On prendra le bistrot du coin
\end{verse}
\begin{verse}
Et si le tonneau se débouche
J'en boirai jusqu'à mon plaisir
\end{verse}
\begin{verse}
Et s'il en reste quelques gouttes
Ce sera pour nous rafraîchir
\end{verse}
\begin{verse}
Sur ma tombe, je veux qu'on inscrive
\emph{Ici gît le roi des buveurs}
\end{verse}
\endsong

70
test/test_content/datadir/songs/texsong.tsg

@ -0,0 +1,70 @@
\selectlanguage{french}
\songcolumns{2}
\beginsong{Chevaliers de la table ronde}
[by={Traditionnel},cover={traditionnel},album={France}]
\cover
\gtab{C}{X32010}
\gtab{G7}{320001}
\gtab{F}{1:022100}
\begin{verse}
Cheva\[C]liers de la Table Ronde
Goûtons \[G7]voir si le vin est \[C]bon
\rep{2}
\end{verse}
\begin{chorus}
Goûtons \[F]voir, \echo{oui, oui, oui}
Goûtons \[C]voir, \echo{non, non, non}
Goûtons \[G7]voir si le vin est bon
\rep{2}
\end{chorus}
\begin{verse}
S'il est bon, s'il est agréable
J'en boirai jusqu'à mon plaisir
\end{verse}
\begin{verse}
J'en boirai cinq à six bouteilles
Et encore, ce n'est pas beaucoup
\end{verse}
\begin{verse}
Si je meurs, je veux qu'on m'enterre
Dans une cave où il y a du bon vin
\end{verse}
\begin{verse}
Les deux pieds contre la muraille
Et la tête sous le robinet
\end{verse}
\begin{verse}
Et les quatre plus grands ivrognes
Porteront les quatre coins du drap
\end{verse}
\begin{verse}
Pour donner le discours d'usage
On prendra le bistrot du coin
\end{verse}
\begin{verse}
Et si le tonneau se débouche
J'en boirai jusqu'à mon plaisir
\end{verse}
\begin{verse}
Et s'il en reste quelques gouttes
Ce sera pour nous rafraîchir
\end{verse}
\begin{verse}
Sur ma tombe, je veux qu'on inscrive
\emph{Ici gît le roi des buveurs}
\end{verse}
\endsong

1
test/test_content/glob.control

@ -0,0 +1 @@
["chordpro.csg"]

1
test/test_content/glob.source

@ -0,0 +1 @@
["*.csg"]

1
test/test_content/include.control

@ -0,0 +1 @@
["exsong.sg", "chordpro.csg", "subdir/chordpro.csg"]

1
test/test_content/include.source

@ -0,0 +1 @@
[["include" , "custom_list.json"]]

1
test/test_content/sections.control

@ -0,0 +1 @@
["section:Traditional", "exsong.sg", "section:Example", "texsong.tsg", "chordpro.csg", "exsong.sg"]

6
test/test_content/sections.source

@ -0,0 +1,6 @@
[["section", "Traditional"],
"exsong.sg",
["section", "Example"],
"texsong.tsg",
"chordpro.csg",
"exsong.sg"]

1
test/test_content/sections_short.control

@ -0,0 +1 @@
["section:(tradi)Traditional", "exsong.sg", "section*:Example", "texsong.tsg", "chordpro.csg", "exsong.sg"]

6
test/test_content/sections_short.source

@ -0,0 +1,6 @@
[["section", "Traditional", "tradi"],
"exsong.sg",
["section*", "Example"],
"texsong.tsg",
"chordpro.csg",
"exsong.sg"]

1
test/test_content/songs.control

@ -0,0 +1 @@
["exsong.sg", "texsong.tsg", "chordpro.csg", "subdir/chordpro.csg"]

1
test/test_content/songs.source

@ -0,0 +1 @@
["exsong.sg", "intersong.is", "jsonlist.json", "texfile.tex", "texsong.tsg", "chordpro.csg", "subdir/chordpro.csg"]

1
test/test_content/songsection.control

@ -0,0 +1 @@
["songsection:Traditional", "exsong.sg", "songchapter:English", "texsong.tsg", "chordpro.csg", "exsong.sg"]

6
test/test_content/songsection.source

@ -0,0 +1,6 @@
[["songsection", "Traditional"],
"exsong.sg",
["songchapter", "English"],
"texsong.tsg",
"chordpro.csg",
"exsong.sg"]

1
test/test_content/sorted.control

@ -0,0 +1 @@
["chordpro.csg", "exsong.sg", "subdir/chordpro.csg", "texsong.tsg"]

1
test/test_content/sorted.source

@ -0,0 +1 @@
[["sorted(fullpath)"]]

118
test/test_content/test_content.py

@ -0,0 +1,118 @@
"""Tests for the content plugins."""
# pylint: disable=too-few-public-methods
import glob
import os
import unittest
import json
from patacrep.songs import DataSubpath, DEFAULT_CONFIG
from patacrep import content, files
from patacrep.content import song, section, songsection, tex
from .. import logging_reduced
from .. import dynamic # pylint: disable=unused-import
class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
"""Test of the content plugins.
For any given `foo.source`, it parses the content as a json "content"
argument of a .sb file.
It controls that the generated file list is equal to the one in `foo.control`.
"""
maxDiff = None
config = None
@classmethod
def setUpClass(cls):
cls._generate_config()
@classmethod
def _iter_testmethods(cls):
"""Iterate over dynamically generated test methods"""
for source in sorted(glob.glob(os.path.join(
os.path.dirname(__file__),
'*.source',
))):
base = source[:-len(".source")]
yield (
"test_content_{}".format(os.path.basename(base)),
cls._create_content_test(base),
)
@classmethod
def _create_content_test(cls, base):
"""Return a function that `base.source` produces the correct file list"""
def test_content(self):
"""Test that `base.source` produces the correct file list"""
sourcename = "{}.source".format(base)
with open(sourcename, mode="r", encoding="utf8") as sourcefile:
sbcontent = json.load(sourcefile)
with logging_reduced('patacrep.content.song'):
expandedlist = content.process_content(sbcontent, cls.config.copy())
sourcelist = [cls._clean_path(elem) for elem in expandedlist]
controlname = "{}.control".format(base)
if not os.path.exists(controlname):
raise Exception("Missing control:" + str(sourcelist).replace("'", '"'))
with open(controlname, mode="r", encoding="utf8") as controlfile:
controllist = json.load(controlfile)
self.assertEqual(controllist, sourcelist)
test_content.__doc__ = (
"Test that '{base}.source' produces the correct file list"""
).format(base=os.path.basename(base))
return test_content
@classmethod
def _clean_path(cls, elem):
"""Shorten the path relative to the `songs` directory"""
if isinstance(elem, song.SongRenderer):
songpath = os.path.join(os.path.dirname(__file__), 'datadir', 'songs')
return files.path2posix(files.relpath(elem.song.fullpath, songpath))
elif isinstance(elem, section.Section):
if elem.short is None:
return "{}:{}".format(elem.keyword, elem.name)
else:
return "{}:({}){}".format(elem.keyword, elem.short, elem.name)
elif isinstance(elem, songsection.SongSection):
return "{}:{}".format(elem.keyword, elem.name)
elif isinstance(elem, tex.LaTeX):
return files.path2posix(elem.filename)
else:
raise Exception(elem)
@classmethod
def _generate_config(cls):
"""Generate the config to process the content"""
config = DEFAULT_CONFIG.copy()
datadirpaths = [os.path.join(os.path.dirname(__file__), 'datadir')]
config['datadir'] = datadirpaths
config['_songdir'] = [
DataSubpath(path, 'songs')
for path in datadirpaths
]
config['_content_plugins'] = files.load_plugins(
datadirs=datadirpaths,
root_modules=['content'],
keyword='CONTENT_PLUGINS',
)
config['_song_plugins'] = files.load_plugins(
datadirs=datadirpaths,
root_modules=['songs'],
keyword='SONG_RENDERERS',
)['tsg']
cls.config = config

1
test/test_content/tex.control

@ -0,0 +1 @@
["test/test_content/datadir/songs/texfile.tex"]

1
test/test_content/tex.source

@ -0,0 +1 @@
[["tex", "texfile.tex", "chordpro.csg"]]

0
test/test_chordpro/00.csg → test/test_song/00.csg

0
test/test_chordpro/datadir/img/traditionnel.png → test/test_song/00.csg.source

0
test/test_chordpro/00.tsg → test/test_song/00.tsg

0
test/test_chordpro/01.csg → test/test_song/01.csg

0
test/test_chordpro/01.csg.source → test/test_song/01.csg.source

0
test/test_chordpro/01.tsg → test/test_song/01.tsg

0
test/test_chordpro/02.csg → test/test_song/02.csg

0
test/test_chordpro/02.csg.source → test/test_song/02.csg.source

0
test/test_chordpro/02.tsg → test/test_song/02.tsg

0
test/test_chordpro/03.csg → test/test_song/03.csg

0
test/test_chordpro/03.csg.source → test/test_song/03.csg.source

0
test/test_chordpro/03.tsg → test/test_song/03.tsg

0
test/test_chordpro/04.csg → test/test_song/04.csg

0
test/test_chordpro/04.csg.source → test/test_song/04.csg.source

0
test/test_chordpro/04.tsg → test/test_song/04.tsg

0
test/test_chordpro/05.csg → test/test_song/05.csg

0
test/test_chordpro/05.csg.source → test/test_song/05.csg.source

0
test/test_chordpro/05.tsg → test/test_song/05.tsg

0
test/test_chordpro/06.csg → test/test_song/06.csg

0
test/test_chordpro/06.csg.source → test/test_song/06.csg.source

0
test/test_chordpro/06.tsg → test/test_song/06.tsg

0
test/test_chordpro/07.csg → test/test_song/07.csg

0
test/test_chordpro/07.csg.source → test/test_song/07.csg.source

0
test/test_chordpro/07.tsg → test/test_song/07.tsg

0
test/test_chordpro/08.csg → test/test_song/08.csg

0
test/test_chordpro/08.csg.source → test/test_song/08.csg.source

0
test/test_chordpro/08.tsg → test/test_song/08.tsg

0
test/test_chordpro/09.csg → test/test_song/09.csg

0
test/test_chordpro/09.csg.source → test/test_song/09.csg.source

0
test/test_chordpro/09.tsg → test/test_song/09.tsg

0
test/test_chordpro/10.csg → test/test_song/10.csg

0
test/test_chordpro/10.csg.source → test/test_song/10.csg.source

0
test/test_chordpro/10.tsg → test/test_song/10.tsg

0
test/test_chordpro/11.csg → test/test_song/11.csg

0
test/test_chordpro/11.csg.source → test/test_song/11.csg.source

0
test/test_chordpro/11.tsg → test/test_song/11.tsg

0
test/test_chordpro/12.csg → test/test_song/12.csg

0
test/test_chordpro/12.csg.source → test/test_song/12.csg.source

0
test/test_chordpro/12.tsg → test/test_song/12.tsg

0
test/test_chordpro/13.csg → test/test_song/13.csg

0
test/test_chordpro/13.csg.source → test/test_song/13.csg.source

0
test/test_chordpro/13.tsg → test/test_song/13.tsg

0
test/test_chordpro/21.csg → test/test_song/21.csg

0
test/test_chordpro/21.csg.source → test/test_song/21.csg.source

0
test/test_chordpro/21.tsg → test/test_song/21.tsg

0
test/test_chordpro/22.csg → test/test_song/22.csg

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

Loading…
Cancel
Save