Browse Source

Merge pull request #189 from patacrep/patatools

Create a `patatools` binary, gathering miscellaneous patacrep-related tools.
pull/195/head
Louis 9 years ago
parent
commit
392abfd5fe
  1. 6
      .appveyor.yml
  2. 4
      .travis.yml
  3. 8
      patacrep/build.py
  4. 2
      patacrep/errors.py
  5. 54
      patacrep/songbook/__init__.py
  6. 62
      patacrep/songbook/__main__.py
  7. 11
      patacrep/songs/__init__.py
  8. 8
      patacrep/songs/chordpro/syntax.py
  9. 0
      patacrep/tools/__init__.py
  10. 114
      patacrep/tools/__main__.py
  11. 0
      patacrep/tools/cache/__init__.py
  12. 73
      patacrep/tools/cache/__main__.py
  13. 0
      patacrep/tools/convert/__init__.py
  14. 37
      patacrep/tools/convert/__main__.py
  15. 6
      patatools
  16. 1
      setup.py
  17. 6
      songbook
  18. 1
      test/test_patatools/.gitignore
  19. 0
      test/test_patatools/__init__.py
  20. 67
      test/test_patatools/test_cache.py
  21. 3
      test/test_patatools/test_cache.sb
  22. 0
      test/test_patatools/test_cache_datadir/songs/foo.csg
  23. 123
      test/test_patatools/test_convert.py
  24. 122
      test/test_patatools/test_convert_failure/song.csg
  25. 0
      test/test_patatools/test_convert_failure/song.csg.tsg.control
  26. 1
      test/test_patatools/test_convert_success/.gitignore
  27. 45
      test/test_patatools/test_convert_success/greensleeves.csg
  28. 62
      test/test_patatools/test_convert_success/greensleeves.csg.tsg.control
  29. 3
      test/test_song/test_parser.py
  30. 4
      texlive_packages.txt

6
.appveyor.yml

@ -23,7 +23,7 @@ install:
- "python -c \"import struct; print(struct.calcsize('P') * 8)\""
# Download miktex portable (if not cached)
- ps: "If (!(Test-Path miktex-portable.exe)){wget http://mirrors.ctan.org/systems/win32/miktex/setup/miktex-portable-2.9.5719.exe -OutFile ./miktex-portable.exe}"
- ps: "If (!(Test-Path miktex-portable.exe)){wget http://mirrors.ctan.org/systems/win32/miktex/setup/miktex-portable-2.9.5857.exe -OutFile ./miktex-portable.exe}"
# Unzip miktex portable
- "7z x miktex-portable.exe * -aot -omiktex > nul"
@ -32,8 +32,8 @@ install:
- cmd: set PATH=%PATH%;C:\projects\patacrep\miktex\miktex\bin
# Update some packages to prevent ltluatex bug
- cmd: mpm.exe --update=miktex-bin-2.9
- cmd: mpm.exe --update=ltxbase --update=luatexbase --update=luaotfload --update=miktex-luatex-base --update=fontspec
# - cmd: mpm.exe --update=miktex-bin-2.9 --verbose
# - cmd: mpm.exe --update=ltxbase --update=luatexbase --update=luaotfload --update=miktex-luatex-base --update=fontspec
# Manually install required texlive packages
- cmd: mpm.exe --install-some texlive_packages.txt

4
.travis.yml

@ -2,11 +2,11 @@ git:
depth: 1
language: python
python:
- 3.4
- 3.5
install:
- pip install tox
script:
- tox
- tox -e lint,py35
sudo: false
# addons:
# apt:

8
patacrep/build.py

@ -172,11 +172,11 @@ class SongbookBuilder:
# are function; values are return values of functions.
_called_functions = {}
def __init__(self, raw_songbook, basename):
# Representation of the .sb songbook configuration file.
self.songbook = Songbook(raw_songbook, basename)
def __init__(self, raw_songbook):
# Basename of the songbook to be built.
self.basename = basename
self.basename = raw_songbook['_basename']
# Representation of the .sb songbook configuration file.
self.songbook = Songbook(raw_songbook, self.basename)
def _run_once(self, function, *args, **kwargs):
"""Run function if it has not been run yet.

2
patacrep/errors.py

@ -7,7 +7,7 @@ class SongbookError(Exception):
"""
pass
class SBFileError(SongbookError):
class YAMLError(SongbookError):
"""Error during songbook file decoding"""
def __init__(self, message=None):

54
patacrep/songbook/__init__.py

@ -0,0 +1,54 @@
"""Raw songbook utilities"""
import logging
import os
import sys
import yaml
from patacrep import encoding
import patacrep
LOGGER = logging.getLogger()
def open_songbook(filename):
"""Open songbook, and return a raw songbook object.
:param str filename: Filename of the yaml songbook.
:rvalue: dict
:return: Songbook, as a dictionary.
"""
if os.path.exists(filename + ".sb") and not os.path.exists(filename):
filename += ".sb"
try:
with patacrep.encoding.open_read(filename) as songbook_file:
songbook = yaml.load(songbook_file)
if 'encoding' in songbook:
with encoding.open_read(
filename,
encoding=songbook['encoding']
) as songbook_file:
songbook = yaml.load(songbook_file)
except Exception as error: # pylint: disable=broad-except
raise patacrep.errors.YAMLError(str(error))
songbook['_basename'] = os.path.basename(filename)[:-3]
# Gathering datadirs
datadirs = []
if 'datadir' in songbook:
if isinstance(songbook['datadir'], str):
songbook['datadir'] = [songbook['datadir']]
datadirs += [
os.path.join(
os.path.dirname(os.path.abspath(filename)),
path
)
for path in songbook['datadir']
]
# Default value
datadirs.append(os.path.dirname(os.path.abspath(filename)))
songbook['datadir'] = datadirs
return songbook

62
patacrep/songbook/__main__.py

@ -3,16 +3,14 @@
import argparse
import locale
import logging
import os.path
import textwrap
import sys
import yaml
import textwrap
from patacrep.build import SongbookBuilder, DEFAULT_STEPS
from patacrep.utils import yesno
from patacrep import __version__
from patacrep import errors
import patacrep.encoding
from patacrep.songbook import open_songbook
from patacrep.build import SongbookBuilder, DEFAULT_STEPS
from patacrep.utils import yesno
# Logging configuration
logging.basicConfig(level=logging.INFO)
@ -115,9 +113,8 @@ def argument_parser(args):
return options
def main():
def main(args):
"""Main function:"""
# set script locale to match user's
try:
locale.setlocale(locale.LC_ALL, '')
@ -125,51 +122,18 @@ def main():
# Locale is not installed on user's system, or wrongly configured.
LOGGER.error("Locale error: {}\n".format(str(error)))
options = argument_parser(sys.argv[1:])
songbook_path = options.book[-1]
if os.path.exists(songbook_path + ".sb") and not os.path.exists(songbook_path):
songbook_path += ".sb"
basename = os.path.basename(songbook_path)[:-3]
options = argument_parser(args[1:])
try:
with patacrep.encoding.open_read(songbook_path) as songbook_file:
songbook = yaml.load(songbook_file)
if 'encoding' in songbook:
with patacrep.encoding.open_read(
songbook_path,
encoding=songbook['encoding']
) as songbook_file:
songbook = yaml.load(songbook_file)
except Exception as error: # pylint: disable=broad-except
LOGGER.error(error)
LOGGER.error("Error while loading file '{}'.".format(songbook_path))
sys.exit(1)
songbook = open_songbook(options.book[-1])
# Gathering datadirs
datadirs = []
if options.datadir:
# Command line options
datadirs += [item[0] for item in options.datadir]
if 'datadir' in songbook:
if isinstance(songbook['datadir'], str):
songbook['datadir'] = [songbook['datadir']]
datadirs += [
os.path.join(
os.path.dirname(os.path.abspath(songbook_path)),
path
)
for path in songbook['datadir']
]
# Default value
datadirs.append(os.path.dirname(os.path.abspath(songbook_path)))
songbook['datadir'] = datadirs
songbook['_cache'] = options.cache[0]
if options.datadir:
for datadir in reversed(options.datadir):
songbook['datadir'].insert(0, datadir)
songbook['_cache'] = options.cache[0]
try:
sb_builder = SongbookBuilder(songbook, basename)
sb_builder = SongbookBuilder(songbook)
sb_builder.unsafe = True
sb_builder.build_steps(options.steps)
@ -187,4 +151,4 @@ def main():
sys.exit(0)
if __name__ == '__main__':
main()
main(sys.argv)

11
patacrep/songs/__init__.py

@ -194,11 +194,12 @@ class Song:
# https://bugs.python.org/issue1692335
return
cached = {attr: getattr(self, attr) for attr in self.cached_attributes}
pickle.dump(
cached,
open(self.cached_name, 'wb'),
protocol=-1
)
with open(self.cached_name, 'wb') as cache_file:
pickle.dump(
cached,
cache_file,
protocol=-1
)
def __str__(self):
return str(self.fullpath)

8
patacrep/songs/chordpro/syntax.py

@ -38,6 +38,8 @@ class ChordproParser(Parser):
lexer = ChordProLexer(filename=self.filename)
ast.AST.lexer = lexer.lexer
parsed = self.parser.parse(content, lexer=lexer.lexer)
if parsed is None:
raise ContentError(message='Fatal error during song parsing.')
parsed.error_builders.extend(lexer.error_builders)
return parsed
@ -325,8 +327,4 @@ class ChordproParser(Parser):
def parse_song(content, filename=None):
"""Parse song and return its metadata."""
parser = ChordproParser(filename)
parsed_content = parser.parse(content)
if parsed_content is None:
raise ContentError(message='Fatal error during song parsing.')
return parsed_content
return ChordproParser(filename).parse(content)

0
patacrep/tools/__init__.py

114
patacrep/tools/__main__.py

@ -0,0 +1,114 @@
#!/bin/env python3
"""Command line client to :mod:`tools`"""
import argparse
import logging
import operator
import os
import pkgutil
import re
import sys
import patacrep
# Logging configuration
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger("patatools")
def _execlp(program, args):
"""Call :func:`os.execlp`, adding `program` as the first argument to itself."""
return os.execlp(program, program, *args)
def _iter_subcommands():
"""Iterate over subcommands.
The objects returned are tuples of:
- the name of the command;
- its description;
- the function to call to execute the subcommand.
"""
subcommands = []
# Get python subcommands
path = [os.path.join(item, "patacrep", "tools") for item in sys.path]
prefix = "patacrep.tools."
module_re = re.compile(r'{}(?P<subcommand>[^\.]*)\.__main__'.format(prefix))
for module_loader, name, _ in pkgutil.walk_packages(path, prefix):
match = module_re.match(name)
if match:
module = module_loader.find_module(match.string).load_module()
if hasattr(module, "SUBCOMMAND_DESCRIPTION"):
subcommands.append(match.groupdict()['subcommand'])
yield (
match.groupdict()['subcommand'],
getattr(module, "SUBCOMMAND_DESCRIPTION"),
module.main,
)
class ArgumentParser(argparse.ArgumentParser):
"""Proxy class to circumvent an :mod:`argparse` bug.
Contrarily to what documented, the `argparse.REMAINDER
<https://docs.python.org/3/library/argparse.html#nargs>`_ `nargs` setting
does not include the remainder arguments if the first one begins with `-`.
This bug is reperted as `17050 <https://bugs.python.org/issue17050>`_. This
class can be deleted once this bug has been fixed.
"""
def parse_args(self, args=None, namespace=None):
if args is None:
args = sys.argv[1:]
subcommands = [command[0] for command in set(_iter_subcommands())]
if len(args) > 0:
if args[0] in subcommands:
args = [args[0], "--"] + args[1:]
value = super().parse_args(args, namespace)
if hasattr(value, 'remainder'):
value.remainder = value.remainder[1:]
return value
def commandline_parser():
"""Return a command line parser."""
parser = ArgumentParser(
prog="patatools",
description=(
"Miscellaneous tools for patacrep."
),
formatter_class=argparse.RawTextHelpFormatter,
)
parser.add_argument(
'--version',
help='Show version',
action='version',
version='%(prog)s ' + patacrep.__version__
)
subparsers = parser.add_subparsers(
title="Subcommands",
description="List of available subcommands.",
)
subparsers.required = True
subparsers.dest = "subcommand"
for command, message, function in sorted(_iter_subcommands(), key=operator.itemgetter(0)):
sub1 = subparsers.add_parser(command, help=message, add_help=False)
sub1.add_argument('remainder', nargs=argparse.REMAINDER)
sub1.set_defaults(function=function)
return parser
def main(args):
"""Main function"""
parser = commandline_parser()
args = parser.parse_args(args[1:])
args.function(["patatools-{}".format(args.subcommand)] + args.remainder)
if __name__ == "__main__":
main(sys.argv)

0
patacrep/tools/cache/__init__.py

73
patacrep/tools/cache/__main__.py

@ -0,0 +1,73 @@
"""`patatools cache` command: cache manipulation."""
import argparse
import logging
import os
import shutil
import sys
import textwrap
from patacrep import errors
from patacrep.songbook import open_songbook
LOGGER = logging.getLogger("patatools.cache")
SUBCOMMAND_DESCRIPTION = "Perform operations on cache."
def filename(name):
"""Check that argument is an existing, readable file name.
Return the argument for convenience.
"""
if os.path.isfile(name) and os.access(name, os.R_OK):
return name
raise argparse.ArgumentTypeError("Cannot read file '{}'.".format(name))
def commandline_parser():
"""Return a command line parser."""
parser = argparse.ArgumentParser(
prog="patatools cache",
description=SUBCOMMAND_DESCRIPTION,
formatter_class=argparse.RawTextHelpFormatter,
)
subparsers = parser.add_subparsers(
description="",
dest="command",
)
subparsers.required = True
clean = subparsers.add_parser(
"clean",
description="Delete cache.",
help="Delete cache.",
)
clean.add_argument(
'songbook',
metavar="SONGBOOK",
help=textwrap.dedent("""Songbook file to be used to look for cache path."""),
type=filename,
)
clean.set_defaults(command=do_clean)
return parser
def do_clean(namespace):
"""Execute the `patatools cache clean` command."""
for datadir in open_songbook(namespace.songbook)['datadir']:
cachedir = os.path.join(datadir, ".cache")
LOGGER.info("Deleting cache directory '{}'...".format(cachedir))
if os.path.isdir(cachedir):
shutil.rmtree(cachedir)
def main(args):
"""Main function: run from command line."""
options = commandline_parser().parse_args(args[1:])
try:
options.command(options)
except errors.SongbookError as error:
LOGGER.error(str(error))
sys.exit(1)
if __name__ == "__main__":
main(sys.argv)

0
patacrep/tools/convert/__init__.py

37
patacrep/songs/convert/__main__.py → patacrep/tools/convert/__main__.py

@ -1,37 +1,40 @@
"""Conversion between formats
See the :meth:`__usage` method for more information.
"""
"""`patatools.convert` command: convert between song formats"""
import os
import logging
import sys
from patacrep import files
from patacrep.content import ContentError
from patacrep.songs import DEFAULT_CONFIG
from patacrep.utils import yesno
LOGGER = logging.getLogger(__name__)
LOGGER = logging.getLogger("patatools.convert")
SUBCOMMAND_DESCRIPTION = "Convert between song formats"
def __usage():
return "python3 -m patacrep.songs.convert INPUTFORMAT OUTPUTFORMAT FILES"
def _usage():
return "patatools convert INPUTFORMAT OUTPUTFORMAT FILES"
def confirm(destname):
"""Ask whether destination name should be overwrited."""
while True:
try:
return yesno(input("File '{}' already exist. Overwrite? [yn] ".format(destname)))
except ValueError:
continue
if __name__ == "__main__":
if len(sys.argv) < 4:
def main(args=None):
"""Main function: run from command line."""
if args is None:
args = sys.argv
if len(args) < 4:
LOGGER.error("Invalid number of arguments.")
LOGGER.error("Usage: %s", __usage())
LOGGER.error("Usage: %s", _usage())
sys.exit(1)
source = sys.argv[1]
dest = sys.argv[2]
song_files = sys.argv[3:]
source = args[1]
dest = args[2]
song_files = args[3:]
renderers = files.load_plugins(
datadirs=DEFAULT_CONFIG.get('datadir', []),
@ -55,8 +58,8 @@ if __name__ == "__main__":
sys.exit(1)
for file in song_files:
song = renderers[dest][source](file, DEFAULT_CONFIG)
try:
song = renderers[dest][source](file, DEFAULT_CONFIG)
destname = "{}.{}".format(".".join(file.split(".")[:-1]), dest)
if os.path.exists(destname):
if not confirm(destname):
@ -64,6 +67,9 @@ if __name__ == "__main__":
with open(destname, "w") as destfile:
destfile.write(song.render())
except ContentError:
LOGGER.error("Cannot parse file '%s'.", file)
sys.exit(1)
except NotImplementedError:
LOGGER.error("Cannot convert to format '%s'.", dest)
sys.exit(1)
@ -73,3 +79,6 @@ if __name__ == "__main__":
sys.exit(0)
sys.exit(0)
if __name__ == "__main__":
main()

6
patatools

@ -0,0 +1,6 @@
#! /bin/sh
# Do not edit this file. This file is just a helper file for development test.
# It is not part of the distributed software.
python -m patacrep.tools $@

1
setup.py

@ -39,6 +39,7 @@ setup(
entry_points={
'console_scripts': [
"songbook = patacrep.songbook.__main__:main",
"patatools = patacrep.tools.__main__:main",
],
},
classifiers=[

6
songbook

@ -3,8 +3,4 @@
# Do not edit this file. This file is just a helper file for development test.
# It is not part of the distributed software.
if [ "$1" = "purgecache" ]; then
find . -name ".cache" -type d -print -exec rm -r {} +
else
python -m patacrep.songbook "$@"
fi
python -m patacrep.songbook "$@"

1
test/test_patatools/.gitignore

@ -0,0 +1 @@
.cache

0
test/test_patatools/__init__.py

67
test/test_patatools/test_cache.py

@ -0,0 +1,67 @@
"""Tests of the patatools-cache command."""
# pylint: disable=too-few-public-methods
import os
import shutil
import unittest
from patacrep.files import chdir
from patacrep.tools.__main__ import main as tools_main
from patacrep.tools.cache.__main__ import main as cache_main
from patacrep.songbook.__main__ import main as songbook_main
CACHEDIR = os.path.join(os.path.dirname(__file__), "test_cache_datadir", ".cache")
class TestCache(unittest.TestCase):
"""Test of the "patatools cache" subcommand"""
def setUp(self):
"""Remove cache."""
self._remove_cache()
self.assertFalse(os.path.exists(CACHEDIR))
def tearDown(self):
"""Remove cache."""
self._remove_cache()
self.assertFalse(os.path.exists(CACHEDIR))
@staticmethod
def _remove_cache():
"""Delete cache."""
shutil.rmtree(CACHEDIR, ignore_errors=True)
def _system(self, main, args):
with chdir(os.path.dirname(__file__)):
try:
main(args)
except SystemExit as systemexit:
self.assertEqual(systemexit.code, 0)
def test_clean_exists(self):
"""Test of the "patatools cache clean" subcommand"""
for main, args in [
(tools_main, ["patatools", "cache", "clean", "test_cache.sb"]),
(cache_main, ["patatools-cache", "clean", "test_cache.sb"]),
]:
with self.subTest(main=main, args=args):
# First compilation. Ensure that cache exists afterwards
self._system(songbook_main, ["songbook", "--steps", "tex,clean", "test_cache.sb"])
self.assertTrue(os.path.exists(CACHEDIR))
# Clean cache
self._system(main, args)
# Ensure that cache does not exist
self.assertFalse(os.path.exists(CACHEDIR))
def test_clean_not_exists(self):
"""Test of the "patatools cache clean" subcommand"""
# Clean non-existent cache
for main, args in [
(tools_main, ["patatools", "cache", "clean", "test_cache.sb"]),
(cache_main, ["patatools-cache", "clean", "test_cache.sb"]),
]:
with self.subTest(main=main, args=args):
# Clean cache
self._system(main, args)

3
test/test_patatools/test_cache.sb

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

0
test/test_patatools/test_cache_datadir/songs/foo.csg

123
test/test_patatools/test_convert.py

@ -0,0 +1,123 @@
"""Tests of the patatools-convert command."""
# pylint: disable=too-few-public-methods
import contextlib
import glob
import os
import unittest
from pkg_resources import resource_filename
from patacrep import files
from patacrep.tools.__main__ import main as tools_main
from patacrep.encoding import open_read
from patacrep.tools.convert.__main__ import main as convert_main
from .. import dynamic # pylint: disable=unused-import
from .. import logging_reduced
class TestConvert(unittest.TestCase, metaclass=dynamic.DynamicTest):
"""Test of the "patatools convert" subcommand"""
@staticmethod
def _system(main, args):
try:
main(args)
except SystemExit as systemexit:
return systemexit.code
return 0
def assertConvert(self, basename, in_format, out_format): # pylint: disable=invalid-name
"""Test of the "patatools convert" subcommand"""
sourcename = "{}.{}".format(basename, in_format)
destname = "{}.{}".format(basename, out_format)
controlname = "{}.{}.control".format(sourcename, out_format)
for main, args in [
(tools_main, ["patatools", "convert"]),
(convert_main, ["patatools-convert"]),
]:
with self.subTest(main=main, args=args):
with self.chdir("test_convert_success"):
with open_read(controlname) as controlfile:
with logging_reduced():
if os.path.exists(destname):
os.remove(destname)
self.assertEqual(
self._system(main, args + [in_format, out_format, sourcename]),
0,
)
expected = controlfile.read().strip().replace(
"@TEST_FOLDER@",
files.path2posix(resource_filename(__name__, "")),
)
with open_read(destname) as destfile:
self.assertMultiLineEqual(
destfile.read().replace('\r\n', '\n').strip(),
expected.strip(),
)
def assertFailConvert(self, basename, in_format, out_format): # pylint: disable=invalid-name
"""Test of the "patatools convert" subcommand"""
sourcename = "{}.{}".format(basename, in_format)
destname = "{}.{}".format(basename, out_format)
for main, args in [
(tools_main, ["patatools", "convert"]),
(convert_main, ["patatools-convert"]),
]:
with self.subTest(main=main, args=args):
with self.chdir("test_convert_failure"):
with logging_reduced():
if os.path.exists(destname):
os.remove(destname)
self.assertEqual(
self._system(main, args + [in_format, out_format, sourcename]),
1,
)
@staticmethod
@contextlib.contextmanager
def chdir(*pathlist):
"""Temporary change current directory, relative to this file directory"""
with files.chdir(resource_filename(__name__, ""), *pathlist):
yield
@classmethod
def _iter_testmethods(cls):
"""Iterate over song files to test."""
for directory, create_test in [
("test_convert_success", cls._create_test_success),
("test_convert_failure", cls._create_test_failure),
]:
with cls.chdir(directory):
for control in sorted(glob.glob('*.*.*.control')):
[*base, in_format, out_format, _] = control.split('.')
base = '.'.join(base)
yield (
"test_{}_{}_{}".format(base, in_format, out_format),
create_test(base, in_format, out_format),
)
@classmethod
def _create_test_success(cls, base, in_format, out_format):
"""Return a function testing conversion.
:param str base: Base name of the file to convert.
:param str in_format: Source format.
:param str out_format: Destination format.
"""
test_parse_render = lambda self: self.assertConvert(base, in_format, out_format)
test_parse_render.__doc__ = (
"Test that '{base}.{in_format}' is correctly converted to '{out_format}'."
).format(base=os.path.basename(base), in_format=in_format, out_format=out_format)
return test_parse_render
@classmethod
def _create_test_failure(cls, base, in_format, out_format):
"""Return a function testing failing conversions
"""
test_parse_render = lambda self: self.assertFailConvert(base, in_format, out_format)
test_parse_render.__doc__ = (
"Test that '{base}.{in_format}' raises an error when trying to convert it to '{out_format}'." # pylint: disable=line-too-long
).format(base=os.path.basename(base), in_format=in_format, out_format=out_format)
return test_parse_render

122
test/test_patatools/test_convert_failure/song.csg

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

0
test/test_patatools/test_convert_failure/song.csg.tsg.control

1
test/test_patatools/test_convert_success/.gitignore

@ -0,0 +1 @@
greensleeves.tsg

45
test/test_patatools/test_convert_success/greensleeves.csg

@ -0,0 +1,45 @@
{lang: en}
{columns: 2}
{title: Greensleeves}
{title: Un autre sous-titre}
{title: Un sous titre}
{artist: Traditionnel}
{album: Angleterre}
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]Green[B]sleeves 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
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

62
test/test_patatools/test_convert_success/greensleeves.csg.tsg.control

@ -0,0 +1,62 @@
\selectlanguage{english}
\songcolumns{2}
\beginsong{Greensleeves\\
Un autre sous-titre\\
Un sous titre}[
by={
Traditionnel },
album={Angleterre},
]
\begin{verse}
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
\end{verse}
\begin{chorus}
\[C]Green\[B]sleeves 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{chorus}
\begin{verse}
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
\end{verse}
\begin{verse}
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
\end{verse}
\begin{verse}
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
\end{verse}
\begin{verse}
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
\end{verse}
\endsong

3
test/test_song/test_parser.py

@ -48,8 +48,7 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
@staticmethod
@contextlib.contextmanager
def chdir(*path):
"""Context to temporarry change current directory, relative to this file directory
"""
"""Temporary change current directory, relative to this file directory"""
with files.chdir(resource_filename(__name__, ""), *path):
yield

4
texlive_packages.txt

@ -1,11 +1,7 @@
babel-english
babel-esperanto
babel-french
babel-german
babel-italian
babel-latin
babel-portuges
babel-spanish
ctablestack
etoolbox
fancybox

Loading…
Cancel
Save