From f8c84de29c14a0627b2e75f04121953ff3659261 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 14 Nov 2015 19:55:51 +0100 Subject: [PATCH 01/59] Add an `errors` attribute to songs, compiling the song errors First step to #121 --- patacrep/latex/__init__.py | 52 +++++++++++++++++++++-------- patacrep/songs/__init__.py | 1 + patacrep/songs/chordpro/__init__.py | 1 + patacrep/songs/chordpro/ast.py | 6 +++- patacrep/songs/chordpro/syntax.py | 31 +++++++++++++---- patacrep/songs/errors.py | 33 ++++++++++++++++++ patacrep/songs/syntax.py | 13 ++++++-- 7 files changed, 114 insertions(+), 23 deletions(-) create mode 100644 patacrep/songs/errors.py diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index d596abae..3361191e 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -8,6 +8,7 @@ will work on simple cases, but not on complex ones. import logging from collections import OrderedDict +from patacrep import errors from patacrep.latex.syntax import tex2plain, parse_song LOGGER = logging.getLogger(__name__) @@ -77,32 +78,57 @@ BABEL_LANGUAGES = OrderedDict(( # ('??_??', 'welsh'), )) -def lang2babel(lang): - """Return the language used by babel, corresponding to the language code""" +class UnknownLanguage(Exception): + """Error: Unknown language.""" + + def __init__(self, *, original, fallback, message): + super().__init__() + self.original = original + self.fallback = fallback + self.message = message + +def checklanguage(lang): + """Check that `lang` is a known language. + + Raise an :class:`UnknownLanguage` excetipn if not. + """ # Exact match if lang.lower() in BABEL_LANGUAGES: - return BABEL_LANGUAGES[lang.lower()] + return lang.lower() # Only language code is provided (e.g. 'fr') for babel in BABEL_LANGUAGES: if babel.startswith(lang.lower()): - return BABEL_LANGUAGES[babel] + return babel # A non existent country code is provided (e.g. 'fr_CD'). language = lang.lower().split("_")[0] for babel in BABEL_LANGUAGES: if babel.startswith(language): - LOGGER.error( - "Unknown country code '{}'. Using default '{}' instead.".format( + raise UnknownLanguage( + original=lang, + fallback=babel, + message="Unknown country code '{}'. Using default '{}' instead.".format( lang, babel ) ) - return BABEL_LANGUAGES[babel] # Error: no (exact or approximate) match found available = ", ".join(BABEL_LANGUAGES.keys()) - LOGGER.error( - "Unknown language code '{}' (supported: {}). Using default 'english' instead.".format( - lang, - available - ) + raise UnknownLanguage( + original=lang, + fallback="en_us", + message=( + "Unknown language code '{}' (supported: {}). Using " + "default 'english' instead." + ).format( + lang, + available + ) ) - return 'english' + +def lang2babel(lang): + """Return the language used by babel, corresponding to the language code""" + try: + return BABEL_LANGUAGES[checklanguage(lang)] + except UnknownLanguage as error: + LOGGER.error(str(error)) + return BABEL_LANGUAGES[error.fallback] diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index 2b1a5768..55e56eac 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -108,6 +108,7 @@ class Song: self.encoding = config["encoding"] self.default_lang = config["lang"] self.config = config + self.errors = [] if self._cache_retrieved(): return diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 5777b170..c7c0bdf4 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -33,6 +33,7 @@ class ChordproSong(Song): self.titles = song.titles self.lang = song.get_data_argument('language', self.default_lang) self.data = song.meta + self.errors = song.errors self.cached = { 'song': song, } diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 8d287a79..380e6833 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -223,7 +223,7 @@ class Song(AST): "tag": "add_cumulative", } - def __init__(self, filename, directives): + def __init__(self, filename, directives, *, errors=None): super().__init__() self.content = [] self.meta = OrderedDict() @@ -231,6 +231,10 @@ class Song(AST): self._titles = [] self._subtitles = [] self.filename = filename + if errors is None: + self.errors = [] + else: + self.errors = errors for directive in directives: self.add(directive) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 999d31a5..2747d030 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -5,6 +5,7 @@ import ply.yacc as yacc import re from patacrep.songs.syntax import Parser +from patacrep.songs import errors from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer @@ -42,7 +43,11 @@ class ChordproParser(Parser): | empty """ if len(symbols) == 2: - symbols[0] = ast.Song(self.filename, self._directives) + symbols[0] = ast.Song( + self.filename, + directives=self._directives, + errors=self._errors, + ) else: symbols[0] = symbols[2].add(symbols[1]) @@ -140,24 +145,29 @@ class ChordproParser(Parser): if match is None: if argument.strip(): - self.error( + error = errors.SongSyntaxError( line=symbols.lexer.lineno, message="Invalid chord definition '{}'.".format(argument), ) + self.error(line=error.line, message=error.message) else: - self.error( + error = errors.SongSyntaxError( line=symbols.lexer.lineno, message="Invalid empty chord definition.", ) + self.error(line=error.line, message=error.message) + self._errors.append(error) symbols[0] = ast.Error() return define = self._parse_define(match.groupdict()) if define is None: - self.error( + error = errors.SongSyntaxError( line=symbols.lexer.lineno, message="Invalid chord definition '{}'.".format(argument), ) + self.error(line=error.line, message=error.message) + self._errors.append(error) symbols[0] = ast.Error() return self._directives.append(define) @@ -186,10 +196,14 @@ class ChordproParser(Parser): else: symbols[0] = None - @staticmethod - def p_line_error(symbols): + def p_line_error(self, symbols): """line_error : error directive""" - LOGGER.error("Directive can only be preceded or followed by spaces") + error = errors.SongSyntaxError( + line=symbols.lexer.lineno, + message="Directive can only be preceded or followed by spaces", + ) + self._errors.append(error) + LOGGER.error(error.message) symbols[0] = ast.Line() @staticmethod @@ -321,5 +335,8 @@ def parse_song(content, filename=None): lexer=ChordProLexer(filename=filename).lexer, ) if parsed_content is None: + # pylint: disable=fixme + # TODO: Provide a nicer error (an empty song?) + # TODO: Add an error to the song.errors list. raise SyntaxError('Fatal error during song parsing: {}'.format(filename)) return parsed_content diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py new file mode 100644 index 00000000..94aff3ed --- /dev/null +++ b/patacrep/songs/errors.py @@ -0,0 +1,33 @@ +"""Errors in song definition (syntax errors, and so on)""" + +class SongError: + """Generic song error""" + # pylint: disable=too-few-public-methods + + type = "generic" + + def __init__(self, message): + self.message = message + + def __str__(self): + raise NotImplementedError() + +class SongSyntaxError(SongError): + """Syntax error""" + # pylint: disable=too-few-public-methods + + type = "syntax" + + def __init__(self, line, message): + super().__init__(message) + #: Line of error. May be `None` if irrelevant. + self.line = line + + def __str__(self): + return "Line {}: {}".format(self.line, self.message) + +# class FileError(SongError): +# type = "file" +# +# class LanguageError(SongError): +# type = "language" diff --git a/patacrep/songs/syntax.py b/patacrep/songs/syntax.py index ee7d95da..c7e933ec 100644 --- a/patacrep/songs/syntax.py +++ b/patacrep/songs/syntax.py @@ -2,6 +2,8 @@ import logging +from patacrep.songs import errors + LOGGER = logging.getLogger() class Parser: @@ -10,6 +12,7 @@ class Parser: def __init__(self): self.filename = "" # Will be overloaded + self._errors = [] @staticmethod def __find_column(token): @@ -42,11 +45,17 @@ class Parser: def p_error(self, token): """Manage parsing errors.""" if token is None: - self.error( + error = errors.SongSyntaxError( + line=None, message="Unexpected end of file.", ) + self.error(message=error.message) else: - self.error( + error = errors.SongSyntaxError( line=token.lineno, + message="Syntax error", + ) + self.error( + line=error.line, column=self.__find_column(token), ) From 13738f41b9461c67b7a0b0d22fd3db084c6da617 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Mon, 16 Nov 2015 13:55:31 +0100 Subject: [PATCH 02/59] Typo --- patacrep/latex/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 3361191e..5ea81a67 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -90,7 +90,7 @@ class UnknownLanguage(Exception): def checklanguage(lang): """Check that `lang` is a known language. - Raise an :class:`UnknownLanguage` excetipn if not. + Raise an :class:`UnknownLanguage` exception if not. """ # Exact match if lang.lower() in BABEL_LANGUAGES: @@ -118,7 +118,7 @@ def checklanguage(lang): fallback="en_us", message=( "Unknown language code '{}' (supported: {}). Using " - "default 'english' instead." + "default 'en_us' instead." ).format( lang, available From 198db1a0e189cfd072eeb6a3d4d1ebf0cf9d5ab4 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Tue, 17 Nov 2015 14:19:53 +0100 Subject: [PATCH 03/59] Factorize the error recording --- patacrep/songs/chordpro/syntax.py | 16 ++++------------ patacrep/songs/syntax.py | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 2747d030..b0eda1ee 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -5,7 +5,6 @@ import ply.yacc as yacc import re from patacrep.songs.syntax import Parser -from patacrep.songs import errors from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer @@ -145,29 +144,24 @@ class ChordproParser(Parser): if match is None: if argument.strip(): - error = errors.SongSyntaxError( + self.error( line=symbols.lexer.lineno, message="Invalid chord definition '{}'.".format(argument), ) - self.error(line=error.line, message=error.message) else: - error = errors.SongSyntaxError( + self.error( line=symbols.lexer.lineno, message="Invalid empty chord definition.", ) - self.error(line=error.line, message=error.message) - self._errors.append(error) symbols[0] = ast.Error() return define = self._parse_define(match.groupdict()) if define is None: - error = errors.SongSyntaxError( + self.error( line=symbols.lexer.lineno, message="Invalid chord definition '{}'.".format(argument), ) - self.error(line=error.line, message=error.message) - self._errors.append(error) symbols[0] = ast.Error() return self._directives.append(define) @@ -198,12 +192,10 @@ class ChordproParser(Parser): def p_line_error(self, symbols): """line_error : error directive""" - error = errors.SongSyntaxError( + self.error( line=symbols.lexer.lineno, message="Directive can only be preceded or followed by spaces", ) - self._errors.append(error) - LOGGER.error(error.message) symbols[0] = ast.Line() @staticmethod diff --git a/patacrep/songs/syntax.py b/patacrep/songs/syntax.py index c7e933ec..d3b5c0ac 100644 --- a/patacrep/songs/syntax.py +++ b/patacrep/songs/syntax.py @@ -24,7 +24,14 @@ class Parser: return column def error(self, *, line=None, column=None, message=""): - """Display an error message""" + """Record and display an error message""" + self._errors.append( + errors.SongSyntaxError( + line=line, + message=message, + ) + ) + coordinates = [] if line is not None: coordinates.append("line {}".format(line)) @@ -45,17 +52,10 @@ class Parser: def p_error(self, token): """Manage parsing errors.""" if token is None: - error = errors.SongSyntaxError( - line=None, - message="Unexpected end of file.", - ) - self.error(message=error.message) + self.error(message="Unexpected end of file.") else: - error = errors.SongSyntaxError( - line=token.lineno, - message="Syntax error", - ) self.error( - line=error.line, + message="Syntax error", + line=token.lineno, column=self.__find_column(token), ) From 1340bbed021a24d43c3b388709126f1c83fb9cc0 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Tue, 17 Nov 2015 14:28:27 +0100 Subject: [PATCH 04/59] Include line number only if relevant --- patacrep/songs/errors.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index 94aff3ed..ac15c2b8 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -24,7 +24,10 @@ class SongSyntaxError(SongError): self.line = line def __str__(self): - return "Line {}: {}".format(self.line, self.message) + if self.line is not None: + return "Line {}: {}".format(self.line, self.message) + else: + return self.message # class FileError(SongError): # type = "file" From 6d38c9714116730940bf5bcdcab54feee8149001 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Tue, 17 Nov 2015 14:29:24 +0100 Subject: [PATCH 05/59] expand TODO --- patacrep/songs/chordpro/syntax.py | 1 + 1 file changed, 1 insertion(+) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index b0eda1ee..4f00f0a1 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -330,5 +330,6 @@ def parse_song(content, filename=None): # pylint: disable=fixme # TODO: Provide a nicer error (an empty song?) # TODO: Add an error to the song.errors list. + # using parser._errors raise SyntaxError('Fatal error during song parsing: {}'.format(filename)) return parsed_content From 9afd8f7b61be3a80e0ce37e7f2a82fa6ac44db9c Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Tue, 17 Nov 2015 15:14:16 +0100 Subject: [PATCH 06/59] Raise a SongSyntaxError on fatal error --- patacrep/songs/chordpro/syntax.py | 13 ++++++++++++- patacrep/songs/errors.py | 3 ++- test/test_chordpro/test_parser.py | 3 ++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 4f00f0a1..abc79993 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -7,6 +7,7 @@ import re from patacrep.songs.syntax import Parser from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer +from patacrep.songs import errors LOGGER = logging.getLogger() @@ -331,5 +332,15 @@ def parse_song(content, filename=None): # TODO: Provide a nicer error (an empty song?) # TODO: Add an error to the song.errors list. # using parser._errors - raise SyntaxError('Fatal error during song parsing: {}'.format(filename)) + + # pylint: disable=protected-access + if parser._errors: + # The line where it started to go wrong + lineno = parser._errors[-1].line + else: + lineno = None + raise errors.SongSyntaxError( + message='Fatal error during song parsing: {}'.format(filename), + line=lineno, + ) return parsed_content diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index ac15c2b8..21052d64 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -1,12 +1,13 @@ """Errors in song definition (syntax errors, and so on)""" -class SongError: +class SongError(Exception): """Generic song error""" # pylint: disable=too-few-public-methods type = "generic" def __init__(self, message): + super().__init__() self.message = message def __str__(self): diff --git a/test/test_chordpro/test_parser.py b/test/test_chordpro/test_parser.py index cd38afcd..01967233 100644 --- a/test/test_chordpro/test_parser.py +++ b/test/test_chordpro/test_parser.py @@ -11,6 +11,7 @@ from pkg_resources import resource_filename from patacrep import files from patacrep.build import DEFAULT_CONFIG from patacrep.encoding import open_read +from patacrep.songs import errors from .. import disable_logging from .. import dynamic # pylint: disable=unused-import @@ -119,7 +120,7 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest): sourcename = "{}.{}.source".format(base, in_format) with self.chdir('errors'): parser = self.song_plugins[out_format][in_format] - self.assertRaises(SyntaxError, parser, sourcename, self.config) + self.assertRaises(errors.SongSyntaxError, parser, sourcename, self.config) test_parse_failure.__doc__ = ( "Test that '{base}' parsing fails." From efc87a7c8c63828031b1eed33e95027c9bdbcc75 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 28 Nov 2015 17:03:47 +0100 Subject: [PATCH 07/59] Remove `object` superclass (necessary in python2, useless in python3) --- patacrep/build.py | 5 ++--- patacrep/content/__init__.py | 2 +- patacrep/index.py | 2 +- patacrep/songs/__init__.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index 0526d5c2..c432d783 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -39,7 +39,7 @@ DEFAULT_CONFIG = { # pylint: disable=too-few-public-methods -class Songbook(object): +class Songbook: """Represent a songbook (.sb) file. - Low level: provide a Python representation of the values stored in the @@ -48,7 +48,6 @@ class Songbook(object): """ def __init__(self, raw_songbook, basename): - super(Songbook, self).__init__() self.config = raw_songbook self.basename = basename # Some special keys have their value processed. @@ -129,7 +128,7 @@ def _log_pipe(pipe): break LOGGER.debug(line.strip()) -class SongbookBuilder(object): +class SongbookBuilder: """Provide methods to compile a songbook.""" # if False, do not expect anything from stdin. diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 040adbad..948fe017 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -79,7 +79,7 @@ LOGGER = logging.getLogger(__name__) EOL = '\n' #pylint: disable=no-self-use -class Content(object): +class Content: """Content item. Will render to something in the .tex file. The current jinja2.runtime.Context is passed to all function defined diff --git a/patacrep/index.py b/patacrep/index.py index b67897f3..12035cb2 100644 --- a/patacrep/index.py +++ b/patacrep/index.py @@ -46,7 +46,7 @@ def process_sxd(filename): return idx -class Index(object): +class Index: """Title, author or scripture Index representation.""" def __init__(self, indextype): diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index 55e56eac..94b630c7 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -25,7 +25,7 @@ def cached_name(datadir, filename): raise return fullpath -class DataSubpath(object): +class DataSubpath: """A path divided in two path: a datadir, and its subpath. - This object can represent either a file or directory. From 8c86f904cd223f8c9abeccbdb2a6d8390c7a0288 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 29 Nov 2015 03:54:40 +0100 Subject: [PATCH 08/59] Errors that can be ignored are logged as warnings --- patacrep/latex/__init__.py | 2 +- patacrep/latex/lexer.py | 2 +- patacrep/songs/chordpro/lexer.py | 2 +- patacrep/songs/syntax.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 5ea81a67..83d12906 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -130,5 +130,5 @@ def lang2babel(lang): try: return BABEL_LANGUAGES[checklanguage(lang)] except UnknownLanguage as error: - LOGGER.error(str(error)) + LOGGER.warning(str(error)) return BABEL_LANGUAGES[error.fallback] diff --git a/patacrep/latex/lexer.py b/patacrep/latex/lexer.py index f76500f8..0e60e826 100644 --- a/patacrep/latex/lexer.py +++ b/patacrep/latex/lexer.py @@ -72,7 +72,7 @@ class SimpleLexer: @staticmethod def t_error(token): """Manage errors""" - LOGGER.error("Illegal character '{}'".format(token.value[0])) + LOGGER.warning("Illegal character '{}'".format(token.value[0])) token.lexer.skip(1) class SongLexer(SimpleLexer): diff --git a/patacrep/songs/chordpro/lexer.py b/patacrep/songs/chordpro/lexer.py index 16f494b3..ab8d4de6 100644 --- a/patacrep/songs/chordpro/lexer.py +++ b/patacrep/songs/chordpro/lexer.py @@ -142,7 +142,7 @@ class ChordProLexer: ) if self.filename is not None: message = "File {}: {}".format(self.filename, message) - LOGGER.error(message) + LOGGER.warning(message) token.lexer.skip(1) def t_error(self, token): diff --git a/patacrep/songs/syntax.py b/patacrep/songs/syntax.py index d3b5c0ac..0ea5208b 100644 --- a/patacrep/songs/syntax.py +++ b/patacrep/songs/syntax.py @@ -45,9 +45,9 @@ class Parser: else: text += "." if self.filename is None: - LOGGER.error(text) + LOGGER.warning(text) else: - LOGGER.error("File {}: {}".format(self.filename, text)) + LOGGER.warning("File {}: {}".format(self.filename, text)) def p_error(self, token): """Manage parsing errors.""" From 0ae923be3be3cea8105bf52b484020b2ca7c6373 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 29 Nov 2015 04:17:18 +0100 Subject: [PATCH 09/59] Gather songbook errors --- patacrep/build.py | 76 +++++++++++++++++++++++++--------------- patacrep/content/song.py | 4 +++ 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index c432d783..1adbb401 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -48,21 +48,23 @@ class Songbook: """ def __init__(self, raw_songbook, basename): - self.config = raw_songbook + self._raw_config = raw_songbook self.basename = basename + self._errors = list() + self._config = dict() # Some special keys have their value processed. self._set_datadir() def _set_datadir(self): """Set the default values for datadir""" try: - if isinstance(self.config['datadir'], str): - self.config['datadir'] = [self.config['datadir']] + if isinstance(self._raw_config['datadir'], str): + self._raw_config['datadir'] = [self._raw_config['datadir']] except KeyError: # No datadir in the raw_songbook - self.config['datadir'] = [os.path.abspath('.')] + self._raw_config['datadir'] = [os.path.abspath('.')] abs_datadir = [] - for path in self.config['datadir']: + for path in self._raw_config['datadir']: if os.path.exists(path) and os.path.isdir(path): abs_datadir.append(os.path.abspath(path)) else: @@ -70,10 +72,10 @@ class Songbook: "Ignoring non-existent datadir '{}'.".format(path) ) - self.config['datadir'] = abs_datadir - self.config['_songdir'] = [ + self._raw_config['datadir'] = abs_datadir + self._raw_config['_songdir'] = [ DataSubpath(path, 'songs') - for path in self.config['datadir'] + for path in self._raw_config['datadir'] ] def write_tex(self, output): @@ -83,42 +85,60 @@ class Songbook: - output: a file object, in which the file will be written. """ # Updating configuration - config = DEFAULT_CONFIG.copy() - config.update(self.config) + self._config = DEFAULT_CONFIG.copy() + self._config.update(self._raw_config) renderer = TexBookRenderer( - config['template'], - config['datadir'], - config['lang'], - config['encoding'], + self._config['template'], + self._config['datadir'], + self._config['lang'], + self._config['encoding'], ) - config.update(renderer.get_variables()) - config.update(self.config) + self._config.update(renderer.get_variables()) + self._config.update(self._raw_config) - config['_compiled_authwords'] = authors.compile_authwords( - copy.deepcopy(config['authwords']) + self._config['_compiled_authwords'] = authors.compile_authwords( + copy.deepcopy(self._config['authwords']) ) # Loading custom plugins - config['_content_plugins'] = files.load_plugins( - datadirs=config.get('datadir', []), + self._config['_content_plugins'] = files.load_plugins( + datadirs=self._config.get('datadir', []), root_modules=['content'], keyword='CONTENT_PLUGINS', ) - config['_song_plugins'] = files.load_plugins( - datadirs=config.get('datadir', []), + self._config['_song_plugins'] = files.load_plugins( + datadirs=self._config.get('datadir', []), root_modules=['songs'], keyword='SONG_RENDERERS', )['tsg'] # Configuration set - config['render'] = content.render - config['content'] = content.process_content( - config.get('content', []), - config, + self._config['render'] = content.render + self._config['content'] = content.process_content( + self._config.get('content', []), + self._config, ) - config['filename'] = output.name[:-4] + self._config['filename'] = output.name[:-4] - renderer.render_tex(output, config) + renderer.render_tex(output, self._config) + + def has_errors(self): + """Return `True` iff errors have been encountered in the book. + + Note that `foo.has_errors() == False` does not means that the book has + not any errors: it does only mean that no error has been found yet. + """ + for i in self.iter_errors(): + return True + return False + + def iter_errors(self): + """Iterate over errors of book and book content.""" + yield from self._errors + for content in self._config.get('content', list()): + if not hasattr(content, "iter_errors"): + continue + yield from content.iter_errors() def _log_pipe(pipe): """Log content from `pipe`.""" diff --git a/patacrep/content/song.py b/patacrep/content/song.py index 45bbfeac..82491c36 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -18,6 +18,10 @@ class SongRenderer(Content): super().__init__() self.song = song + def iter_errors(self): + """Iterate over song errors.""" + yield from self.song.errors + def begin_new_block(self, previous, __context): """Return a boolean stating if a new block is to be created.""" return not isinstance(previous, SongRenderer) From 5d68bd338f2b6ee43a9b0bebf3e03f8262438afb Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 29 Nov 2015 15:07:52 +0100 Subject: [PATCH 10/59] pylint --- patacrep/build.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index 1adbb401..059143d7 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -128,17 +128,17 @@ class Songbook: Note that `foo.has_errors() == False` does not means that the book has not any errors: it does only mean that no error has been found yet. """ - for i in self.iter_errors(): + for _ in self.iter_errors(): return True return False def iter_errors(self): """Iterate over errors of book and book content.""" yield from self._errors - for content in self._config.get('content', list()): - if not hasattr(content, "iter_errors"): + for item in self._config.get('content', list()): + if not hasattr(item, "iter_errors"): continue - yield from content.iter_errors() + yield from item.iter_errors() def _log_pipe(pipe): """Log content from `pipe`.""" From 362c6b87ce24c6c52ebeb6723db5dfb766d949f6 Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 12 Dec 2015 23:46:11 +0100 Subject: [PATCH 11/59] Song errors contain a reference to the song they occur in --- patacrep/errors.py | 6 ++++++ patacrep/latex/syntax.py | 1 + patacrep/songs/__init__.py | 4 ++-- patacrep/songs/chordpro/__init__.py | 2 +- patacrep/songs/chordpro/ast.py | 4 ++-- patacrep/songs/errors.py | 22 ++++++++++------------ patacrep/songs/syntax.py | 12 ++++++------ 7 files changed, 28 insertions(+), 23 deletions(-) diff --git a/patacrep/errors.py b/patacrep/errors.py index d1d3a74f..2b7e7f06 100644 --- a/patacrep/errors.py +++ b/patacrep/errors.py @@ -104,6 +104,12 @@ class ParsingError(SongbookError): def __str__(self): return self.message +class SharedError(SongbookError): + """Error that is meant to be shared to third party tools using patacrep.""" + + def __str__(self): + raise NotImplementedError() + def notfound(filename, paths, message=None): """Return a string saying that file was not found in paths.""" if message is None: diff --git a/patacrep/latex/syntax.py b/patacrep/latex/syntax.py index 8915816b..2814b0ad 100644 --- a/patacrep/latex/syntax.py +++ b/patacrep/latex/syntax.py @@ -136,6 +136,7 @@ class LatexParser(Parser): symbols[0][symbols[1]] = symbols[3] symbols[0].update(symbols[4]) else: + # TODO put error in self.errors raise ParsingError("Do enclose arguments between braces.") @staticmethod diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index 94b630c7..5c40591a 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -180,8 +180,8 @@ class Song: protocol=-1 ) - def __repr__(self): - return repr((self.titles, self.data, self.fullpath)) + def __str__(self): + return str(self.fullpath) def render(self, *args, **kwargs): """Return the code rendering this song. diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index c7c0bdf4..48a16d95 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -33,7 +33,7 @@ class ChordproSong(Song): self.titles = song.titles self.lang = song.get_data_argument('language', self.default_lang) self.data = song.meta - self.errors = song.errors + self.errors = [error(song=self) for error in song.error_builders] self.cached = { 'song': song, } diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index 380e6833..f18bd395 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -232,9 +232,9 @@ class Song(AST): self._subtitles = [] self.filename = filename if errors is None: - self.errors = [] + self.error_builders = [] else: - self.errors = errors + self.error_builders = errors for directive in directives: self.add(directive) diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index 21052d64..7737225f 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -1,37 +1,35 @@ """Errors in song definition (syntax errors, and so on)""" -class SongError(Exception): +from patacrep.errors import SharedError + +class SongError(SharedError): """Generic song error""" # pylint: disable=too-few-public-methods - type = "generic" - - def __init__(self, message): + def __init__(self, song, message): super().__init__() + self.song = song self.message = message def __str__(self): - raise NotImplementedError() + return "Song {}: {}".format(self.song, self.message) class SongSyntaxError(SongError): """Syntax error""" # pylint: disable=too-few-public-methods - type = "syntax" - - def __init__(self, line, message): - super().__init__(message) + def __init__(self, song, line, message): + super().__init__(song, message) #: Line of error. May be `None` if irrelevant. self.line = line def __str__(self): if self.line is not None: - return "Line {}: {}".format(self.line, self.message) + return "Song {}, line {}: {}".format(self.song, self.line, self.message) else: - return self.message + return "Song {}: {}".format(self.song, self.message) # class FileError(SongError): # type = "file" # # class LanguageError(SongError): -# type = "language" diff --git a/patacrep/songs/syntax.py b/patacrep/songs/syntax.py index 0ea5208b..db6c24c5 100644 --- a/patacrep/songs/syntax.py +++ b/patacrep/songs/syntax.py @@ -1,5 +1,6 @@ """Generic parsing classes and methods""" +import functools import logging from patacrep.songs import errors @@ -25,12 +26,11 @@ class Parser: def error(self, *, line=None, column=None, message=""): """Record and display an error message""" - self._errors.append( - errors.SongSyntaxError( - line=line, - message=message, - ) - ) + self._errors.append(functools.partial( + errors.SongSyntaxError, + line=line, + message=message, + )) coordinates = [] if line is not None: From 61a3df1f11f2849b90ba3ba751ede43da4863baf Mon Sep 17 00:00:00 2001 From: Louis Date: Sat, 12 Dec 2015 23:52:14 +0100 Subject: [PATCH 12/59] Replace LOGGER.error by LOGGER.warning for catchable errors --- patacrep/content/__init__.py | 2 +- patacrep/content/include.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 948fe017..b29c4854 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -145,7 +145,7 @@ def render(context, content): last = None for elem in content: if not isinstance(elem, Content): - LOGGER.error("Ignoring bad content item '{}'.".format(elem)) + LOGGER.warning("Ignoring bad content item '{}'.".format(elem)) continue last = elem diff --git a/patacrep/content/include.py b/patacrep/content/include.py index 30c99238..42b28e2e 100644 --- a/patacrep/content/include.py +++ b/patacrep/content/include.py @@ -50,8 +50,8 @@ def parse(keyword, config, argument, contentlist): ) as content_file: new_content = json.load(content_file) except Exception as error: # pylint: disable=broad-except - LOGGER.error(error) - LOGGER.error("Error while loading file '{}'.".format(filepath)) + LOGGER.warning(error) + LOGGER.warning("Error while loading file '{}'.".format(filepath)) sys.exit(1) config["datadir"].append(os.path.abspath(os.path.dirname(filepath))) From d40fd9bb154f001fdd1e9b1f1086e9489d224c55 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 13 Dec 2015 00:23:17 +0100 Subject: [PATCH 13/59] [song.chordpro] Catch fatal syntax error when a block is not closed --- patacrep/songs/__init__.py | 7 ++++--- patacrep/songs/chordpro/__init__.py | 2 +- patacrep/songs/chordpro/syntax.py | 27 +++++++++++---------------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index 5c40591a..9a357163 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -7,8 +7,10 @@ import os import pickle import re +from patacrep import errors as book_errors from patacrep import files, encoding from patacrep.authors import process_listauthors +from patacrep.songs import errors as song_errors LOGGER = logging.getLogger(__name__) @@ -106,7 +108,7 @@ class Song: self.subpath = subpath self._filehash = None self.encoding = config["encoding"] - self.default_lang = config["lang"] + self.lang = config["lang"] self.config = config self.errors = [] @@ -116,8 +118,7 @@ class Song: # Data extraction from the latex song self.titles = [] self.data = {} - self.cached = None - self.lang = None + self.cached = {} self._parse() # Post processing of data diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 48a16d95..cd3d5933 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -31,7 +31,7 @@ class ChordproSong(Song): song = parse_song(song.read(), self.fullpath) self.authors = song.authors self.titles = song.titles - self.lang = song.get_data_argument('language', self.default_lang) + self.lang = song.get_data_argument('language', self.lang) self.data = song.meta self.errors = [error(song=self) for error in song.error_builders] self.cached = { diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index abc79993..fd54406e 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -1,13 +1,14 @@ """ChordPro parser""" +import functools import logging import ply.yacc as yacc import re -from patacrep.songs.syntax import Parser +from patacrep.songs import errors from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer -from patacrep.songs import errors +from patacrep.songs.syntax import Parser LOGGER = logging.getLogger() @@ -328,19 +329,13 @@ def parse_song(content, filename=None): lexer=ChordProLexer(filename=filename).lexer, ) if parsed_content is None: - # pylint: disable=fixme - # TODO: Provide a nicer error (an empty song?) - # TODO: Add an error to the song.errors list. - # using parser._errors - - # pylint: disable=protected-access - if parser._errors: - # The line where it started to go wrong - lineno = parser._errors[-1].line - else: - lineno = None - raise errors.SongSyntaxError( - message='Fatal error during song parsing: {}'.format(filename), - line=lineno, + return ast.Song( + filename, + [], + errors=[functools.partial( + errors.SongSyntaxError, + line=parser._errors[-1].keywords['line'], + message='Fatal error during song parsing.', + )] ) return parsed_content From d184f037fc13bcf1ab7428c2b700e6465758238b Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 02:05:38 +0100 Subject: [PATCH 14/59] [WIP] Content errors are now gathered * Rename `patacrep.content.Content` to `patacrep.content.ContentItem` * Create pseudo-list `patacrep.content.ContentList`, which has an `errors` attribute. * Songs that cannot be parsed at all no longer produce an empty song. This is highly work in progress: most of the `patacrep.content.*` packages are broken. --- patacrep/content/__init__.py | 68 +++++++++++++++++++++---------- patacrep/content/section.py | 6 +-- patacrep/content/song.py | 22 ++++++---- patacrep/content/songsection.py | 6 +-- patacrep/content/tex.py | 6 +-- patacrep/songs/chordpro/syntax.py | 11 +---- 6 files changed, 72 insertions(+), 47 deletions(-) diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index b29c4854..6ecb93db 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -3,8 +3,8 @@ Content that can be included in a songbook is controlled by plugins. From the user (or .sb file) point of view, each piece of content is introduced by a keyword. This keywold is associated with a plugin (a submodule of this very -module), which parses the content, and return a list of instances of the -Content class. +module), which parses the content, and return a ContentList object, which is +little more than a list of instances of the ContentItem class. # Plugin definition @@ -27,8 +27,8 @@ A parser is a function which takes as arguments: - config: the configuration object of the current songbook. Plugins can change it. -A parser returns a list of instances of the Content class, defined in -this module (or of subclasses of this class). +A parser returns a ContentList object (a list of instances of the ContentItem +class), defined in this module (or of subclasses of this class). Example: When the following piece of content is met @@ -55,13 +55,13 @@ surrounded by parenthesis. It is up to the plugin to parse this argument. For intance, keyword "foo()(( bar()" is a perfectly valid keyword, and the parser associated to "foo" will get as argument the string ")(( bar(". -# Content class +# ContentItem class -The content classes are subclasses of class Content defined in this module. -Content is a perfectly valid class, but instances of it will not generate +The content classes are subclasses of class ContentItem defined in this module. +ContentItem is a perfectly valid class, but instances of it will not generate anything in the resulting .tex. -More documentation in the docstring of Content. +More documentation in the docstring of ContentItem. """ @@ -79,7 +79,7 @@ LOGGER = logging.getLogger(__name__) EOL = '\n' #pylint: disable=no-self-use -class Content: +class ContentItem: """Content item. Will render to something in the .tex file. The current jinja2.runtime.Context is passed to all function defined @@ -100,8 +100,8 @@ class Content: # Arguments - - __previous: the songbook.content.Content object of the previous item. - - __context: see Content() documentation. + - __previous: the songbook.content.ContentItem object of the previous item. + - __context: see ContentItem() documentation. # Return @@ -122,13 +122,37 @@ class Content: class ContentError(SongbookError): """Error in a content plugin.""" - def __init__(self, keyword, message): + def __init__(self, keyword=None, message=None): super(ContentError, self).__init__() self.keyword = keyword self.message = message def __str__(self): - return "Content: {}: {}".format(self.keyword, self.message) + text = "Content" + if self.keyword is not None: + text += ": " + self.keyword + if self.message is not None: + text += ": " + self.message + return text + +class ContentList: + """List of content items""" + + def __init__(self, *args, **kwargs): + self._content = list(*args, **kwargs) + self.errors = [] + + def __iter__(self): + yield from self._content + + def extend(self, iterator): + return self._content.extend(iterator) + + def append(self, item): + return self._content.append(item) + + def __len__(self): + return len(self._content) @jinja2.contextfunction def render(context, content): @@ -137,14 +161,14 @@ def render(context, content): Arguments: - context: the jinja2.runtime.context of the current template compilation. - - content: a list of Content() instances, as the one that was returned by + - content: a list of ContentItem() instances, as the one that was returned by process_content(). """ rendered = "" previous = None last = None for elem in content: - if not isinstance(elem, Content): + if not isinstance(elem, ContentItem): LOGGER.warning("Ignoring bad content item '{}'.".format(elem)) continue @@ -156,23 +180,23 @@ def render(context, content): rendered += elem.render(context) + EOL previous = elem - if isinstance(last, Content): + if isinstance(last, ContentItem): rendered += last.end_block(context) + EOL return rendered def process_content(content, config=None): - """Process content, and return a list of Content() objects. + """Process content, and return a list of ContentItem() objects. Arguments are: - content: the content field of the .sb file, which should be a list, and describe what is to be included in the songbook; - config: the configuration dictionary of the current songbook. - Return: a list of Content objects, corresponding to the content to be + Return: a list of ContentItem objects, corresponding to the content to be included in the .tex file. """ - contentlist = [] + contentlist = ContentList() plugins = config.get('_content_plugins', {}) keyword_re = re.compile(r'^ *(?P\w*) *(\((?P.*)\))? *$') if not content: @@ -185,10 +209,12 @@ def process_content(content, config=None): try: match = keyword_re.match(elem[0]).groupdict() except AttributeError: - raise ContentError(elem[0], "Cannot parse content type.") + contentlist.errors.append(ContentError(elem[0], "Cannot parse content type.")) + continue (keyword, argument) = (match['keyword'], match['argument']) if keyword not in plugins: - raise ContentError(keyword, "Unknown content type.") + contentlist.errors.append(ContentError(keyword, "Unknown content type.")) + continue contentlist.extend(plugins[keyword]( keyword, argument=argument, diff --git a/patacrep/content/section.py b/patacrep/content/section.py index e2ecfd32..250d514c 100755 --- a/patacrep/content/section.py +++ b/patacrep/content/section.py @@ -1,6 +1,6 @@ """Allow LaTeX sections (starred or not) as content of a songbook.""" -from patacrep.content import Content, ContentError +from patacrep.content import ContentItem, ContentError KEYWORDS = [ "part", @@ -13,7 +13,7 @@ KEYWORDS = [ ] FULL_KEYWORDS = KEYWORDS + ["{}*".format(word) for word in KEYWORDS] -class Section(Content): +class Section(ContentItem): """A LaTeX section.""" # pylint: disable=too-few-public-methods @@ -48,7 +48,7 @@ def parse(keyword, argument, contentlist, config): ) if (len(contentlist) not in [1, 2]): raise ContentError(keyword, "Section can have one or two arguments.") - return [Section(keyword, *contentlist)] + return ContentList(Section(keyword, *contentlist)) CONTENT_PLUGINS = dict([ diff --git a/patacrep/content/song.py b/patacrep/content/song.py index 82491c36..5b3196a5 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -6,12 +6,13 @@ import logging import os import textwrap -from patacrep.content import process_content, ContentError, Content +from patacrep.content import process_content +from patacrep.content import ContentError, ContentItem, ContentList from patacrep import files, errors LOGGER = logging.getLogger(__name__) -class SongRenderer(Content): +class SongRenderer(ContentItem): """Render a song in as a tex code.""" def __init__(self, song): @@ -71,7 +72,7 @@ def parse(keyword, argument, contentlist, config): plugins = config['_song_plugins'] if '_langs' not in config: config['_langs'] = set() - songlist = [] + songlist = ContentList() for songdir in config['_songdir']: if contentlist: break @@ -95,11 +96,16 @@ def parse(keyword, argument, contentlist, config): ", ".join(["'.{}'".format(key) for key in plugins.keys()]), )) continue - renderer = SongRenderer(plugins[extension]( - filename, - config, - datadir=songdir.datadir, - )) + try: + renderer = SongRenderer(plugins[extension]( + filename, + config, + datadir=songdir.datadir, + )) + except ContentError as error: + # TODO + songlist.errors.append(error) + continue songlist.append(renderer) config["_langs"].add(renderer.song.lang) if len(songlist) > before: diff --git a/patacrep/content/songsection.py b/patacrep/content/songsection.py index fc185b57..5805939c 100755 --- a/patacrep/content/songsection.py +++ b/patacrep/content/songsection.py @@ -1,13 +1,13 @@ """Allow 'songchapter' and 'songsection' as content of a songbook.""" -from patacrep.content import Content, ContentError +from patacrep.content import ContentItem, ContentError KEYWORDS = [ "songchapter", "songsection", ] -class SongSection(Content): +class SongSection(ContentItem): """A songsection or songchapter.""" # pylint: disable=too-few-public-methods @@ -34,7 +34,7 @@ def parse(keyword, argument, contentlist, config): keyword, "Starred section names must have exactly one argument.", ) - return [SongSection(keyword, contentlist[0])] + return ContentList(SongSection(keyword, contentlist[0])) CONTENT_PLUGINS = dict([ diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py index 0f520c19..b0d4395b 100755 --- a/patacrep/content/tex.py +++ b/patacrep/content/tex.py @@ -5,11 +5,11 @@ import logging import os from patacrep import files, errors -from patacrep.content import Content +from patacrep.content import ContentItem, ContentList LOGGER = logging.getLogger(__name__) -class LaTeX(Content): +class LaTeX(ContentItem): """Inclusion of LaTeX code""" def __init__(self, filename): @@ -35,7 +35,7 @@ def parse(keyword, argument, contentlist, config): LOGGER.warning( "Useless 'tex' content: list of files to include is empty." ) - filelist = [] + filelist = ContentList basefolders = itertools.chain( (path.fullpath for path in config['_songdir']), files.iter_datadirs(config['datadir']), diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index fd54406e..44a20f4f 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -5,6 +5,7 @@ import logging import ply.yacc as yacc import re +from patacrep.content import ContentError from patacrep.songs import errors from patacrep.songs.chordpro import ast from patacrep.songs.chordpro.lexer import tokens, ChordProLexer @@ -329,13 +330,5 @@ def parse_song(content, filename=None): lexer=ChordProLexer(filename=filename).lexer, ) if parsed_content is None: - return ast.Song( - filename, - [], - errors=[functools.partial( - errors.SongSyntaxError, - line=parser._errors[-1].keywords['line'], - message='Fatal error during song parsing.', - )] - ) + raise ContentError(message='Fatal error during song parsing.') return parsed_content From 8b7d99af72a1dd91ce9571942ba53fd5d1aad811 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 18:31:34 +0100 Subject: [PATCH 15/59] [test] Create test for content plugins --- .../errors/invalid_content.csg.source | 3 - test/test_compilation/content.sb | 13 ++ test/test_compilation/content.tex.control | 156 ++++++++++++++++++ .../content_datadir/content/foo.tex | 1 + .../content_datadir/content/inter.isg | 5 + .../content_datadir/content/song.csg | 2 + .../content_datadir/content/song.tsg | 3 + .../content_datadir/songs/include.sbc | 1 + .../content_datadir/songs/inter.isg | 5 + .../content_datadir/songs/song.csg | 3 + .../content_datadir/songs/song.tsg | 3 + 11 files changed, 192 insertions(+), 3 deletions(-) delete mode 100644 test/test_chordpro/errors/invalid_content.csg.source create mode 100644 test/test_compilation/content.sb create mode 100644 test/test_compilation/content.tex.control create mode 100644 test/test_compilation/content_datadir/content/foo.tex create mode 100644 test/test_compilation/content_datadir/content/inter.isg create mode 100644 test/test_compilation/content_datadir/content/song.csg create mode 100644 test/test_compilation/content_datadir/content/song.tsg create mode 100644 test/test_compilation/content_datadir/songs/include.sbc create mode 100644 test/test_compilation/content_datadir/songs/inter.isg create mode 100644 test/test_compilation/content_datadir/songs/song.csg create mode 100644 test/test_compilation/content_datadir/songs/song.tsg diff --git a/test/test_chordpro/errors/invalid_content.csg.source b/test/test_chordpro/errors/invalid_content.csg.source deleted file mode 100644 index 4dba23a7..00000000 --- a/test/test_chordpro/errors/invalid_content.csg.source +++ /dev/null @@ -1,3 +0,0 @@ -{soc} -Chorus -{eoc diff --git a/test/test_compilation/content.sb b/test/test_compilation/content.sb new file mode 100644 index 00000000..99042d25 --- /dev/null +++ b/test/test_compilation/content.sb @@ -0,0 +1,13 @@ +{ +"datadir": ["content_datadir"], +"content": [ + ["section", "Test of section"], + ["sorted"], + ["songsection", "Test of song section"], + ["cwd(content_datadir/content)", + "song.csg", "song.tsg", + ["tex", "foo.tex"] + ], + ["include", "include.sbc"] + ] +} diff --git a/test/test_compilation/content.tex.control b/test/test_compilation/content.tex.control new file mode 100644 index 00000000..11a187d4 --- /dev/null +++ b/test/test_compilation/content.tex.control @@ -0,0 +1,156 @@ + + + + + + +%% Automatically generated document. +%% You may edit this file but all changes will be overwritten. +%% If you want to change this document, have a look at +%% the templating system. +%% +%% Generated using Songbook + +\makeatletter +\def\input@path{ % + {/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/latex/} % + {/home/louis/projets/patacrep/patacrep/test/test_compilation/latex/} % + {/home/louis/projets/patacrep/patacrep/patacrep/data/latex/} % +} +\makeatother + +\documentclass[ + ]{article} + +\usepackage[ + chorded, +diagram, +pictures, +guitar, + ]{patacrep} + +\usepackage{lmodern} + + +\PassOptionsToPackage{english}{babel} +\PassOptionsToPackage{english}{babel} +\usepackage[english]{babel} +\lang{english} + +\usepackage{graphicx} +\graphicspath{ % + {/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/} % + {/home/louis/projets/patacrep/patacrep/test/test_compilation/} % + {/home/louis/projets/patacrep/patacrep/patacrep/data/} % +} + + +\makeatletter +\@ifpackageloaded{hyperref}{}{ + \usepackage{url} + \newcommand{\phantomsection}{} + \newcommand{\hyperlink}[2]{#2} + \newcommand{\href}[2]{\expandafter\url\expandafter{#1}} +} +\makeatother + + +\usepackage{chords} + +\title{Guitar songbook} +\author{The Patacrep Team} + +\newindex{titleidx}{content_title} +\newauthorindex{authidx}{content_auth} + + +\notenamesout{A}{B}{C}{D}{E}{F}{G} + + +\begin{document} + +\maketitle + + +\showindex{\songindexname}{titleidx} +\showindex{\authorindexname}{authidx} + +% list of chords +\ifchorded + \ifdiagram + \phantomsection + \addcontentsline{toc}{section}{\chordlistname} + \chords + \fi +\fi + +\phantomsection +\addcontentsline{toc}{section}{\songlistname} + + +\section{Test of section} + +\begin{songs}{titleidx,authidx} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% songs/./song.tsg + +\import{/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/songs/}{song.tsg} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% songs/./song.csg + +\selectlanguage{english} + +\beginsong{This is a song}[ + by={ + }, +] + + + + +\begin{verse} + Foo +\end{verse} + +\endsong + +\end{songs} + +\songsection{Test of song section} + +\begin{songs}{titleidx,authidx} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% content_datadir/content/song.csg + +\selectlanguage{english} + +\beginsong{Yet another song}[ + by={ + }, +] + + +\begin{verse} + Baz +\end{verse} + +\endsong + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% content_datadir/content/song.tsg + +\import{/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/content/}{song.tsg} + +\end{songs} + +\input{content_datadir/content/foo.tex} + + +\section{This is an included section} + + + + + +\end{document} \ No newline at end of file diff --git a/test/test_compilation/content_datadir/content/foo.tex b/test/test_compilation/content_datadir/content/foo.tex new file mode 100644 index 00000000..38310a6c --- /dev/null +++ b/test/test_compilation/content_datadir/content/foo.tex @@ -0,0 +1 @@ +This is a \LaTeX{} file. diff --git a/test/test_compilation/content_datadir/content/inter.isg b/test/test_compilation/content_datadir/content/inter.isg new file mode 100644 index 00000000..ac68b513 --- /dev/null +++ b/test/test_compilation/content_datadir/content/inter.isg @@ -0,0 +1,5 @@ +\begin{intersong} +\sortassong{}[by={The Patacrep Team}] + +This is another example of an intersong. +\end{intersong} diff --git a/test/test_compilation/content_datadir/content/song.csg b/test/test_compilation/content_datadir/content/song.csg new file mode 100644 index 00000000..02213b60 --- /dev/null +++ b/test/test_compilation/content_datadir/content/song.csg @@ -0,0 +1,2 @@ +{title : Yet another song} +Baz diff --git a/test/test_compilation/content_datadir/content/song.tsg b/test/test_compilation/content_datadir/content/song.tsg new file mode 100644 index 00000000..f8d1de53 --- /dev/null +++ b/test/test_compilation/content_datadir/content/song.tsg @@ -0,0 +1,3 @@ +\beginsong{One last song} + Tagada +\endsong diff --git a/test/test_compilation/content_datadir/songs/include.sbc b/test/test_compilation/content_datadir/songs/include.sbc new file mode 100644 index 00000000..fefa39b1 --- /dev/null +++ b/test/test_compilation/content_datadir/songs/include.sbc @@ -0,0 +1 @@ +[["section", "This is an included section"]] diff --git a/test/test_compilation/content_datadir/songs/inter.isg b/test/test_compilation/content_datadir/songs/inter.isg new file mode 100644 index 00000000..e58fa8ed --- /dev/null +++ b/test/test_compilation/content_datadir/songs/inter.isg @@ -0,0 +1,5 @@ +\begin{intersong} +\sortassong{}[by={The Patacrep Team}] + +This is an example of an intersong. +\end{intersong} diff --git a/test/test_compilation/content_datadir/songs/song.csg b/test/test_compilation/content_datadir/songs/song.csg new file mode 100644 index 00000000..0a4f139c --- /dev/null +++ b/test/test_compilation/content_datadir/songs/song.csg @@ -0,0 +1,3 @@ +{title : This is a song} + +Foo diff --git a/test/test_compilation/content_datadir/songs/song.tsg b/test/test_compilation/content_datadir/songs/song.tsg new file mode 100644 index 00000000..e2455c31 --- /dev/null +++ b/test/test_compilation/content_datadir/songs/song.tsg @@ -0,0 +1,3 @@ +\beginsong{This is another song} + Bar. +\endsong From d5e330ee181a9985d57cb1d5a55a9b55e2e8a97e Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 18:52:39 +0100 Subject: [PATCH 16/59] Correct bug: `cwd` did not work as expected --- patacrep/content/cwd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patacrep/content/cwd.py b/patacrep/content/cwd.py index b4d9c2de..d3b02aab 100755 --- a/patacrep/content/cwd.py +++ b/patacrep/content/cwd.py @@ -24,7 +24,7 @@ def parse(keyword, config, argument, contentlist): """ old_songdir = config['_songdir'] config['_songdir'] = ( - [DataSubpath("", argument)] + + [DataSubpath(".", argument)] + [path.clone().join(argument) for path in config['_songdir']] + config['_songdir'] ) From ad1c9f7157ed74f263b18def48aa836a38f708c7 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 18:53:41 +0100 Subject: [PATCH 17/59] [content/include] Include files must be in the `songs` directory --- patacrep/content/include.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patacrep/content/include.py b/patacrep/content/include.py index 42b28e2e..7c553cdb 100644 --- a/patacrep/content/include.py +++ b/patacrep/content/include.py @@ -19,7 +19,7 @@ def load_from_datadirs(path, datadirs): Raise an exception if it was found if none of the datadirs of 'config'. """ - for filepath in files.iter_datadirs(datadirs, path): + for filepath in files.iter_datadirs(datadirs, "songs", path): if os.path.exists(filepath): return filepath # File not found From b1b38cc77d3f09e2964a41b4ed810e00a038b6c9 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 18:54:39 +0100 Subject: [PATCH 18/59] [test] Skip `errors` tests if there is not any --- test/test_chordpro/test_parser.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/test_chordpro/test_parser.py b/test/test_chordpro/test_parser.py index 01967233..0689adcf 100644 --- a/test/test_chordpro/test_parser.py +++ b/test/test_chordpro/test_parser.py @@ -91,14 +91,15 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest): cls._create_test(base, in_format, out_format), ) - with cls.chdir('errors'): - for source in sorted(glob.glob('*.*.source')): - [*base, in_format, _] = source.split('.') - base = '.'.join(base) - yield ( - "test_{}_{}_failure".format(base, in_format), - cls._create_failure(base, in_format), - ) + if os.path.isdir("errors"): + with cls.chdir('errors'): + for source in sorted(glob.glob('*.*.source')): + [*base, in_format, _] = source.split('.') + base = '.'.join(base) + yield ( + "test_{}_{}_failure".format(base, in_format), + cls._create_failure(base, in_format), + ) @classmethod def _create_test(cls, base, in_format, out_format): From 1d2a28553658416577ec2a5ac6f89b41d8a1d3ad Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 18:54:59 +0100 Subject: [PATCH 19/59] Errors are correctly gathered from content items --- patacrep/build.py | 4 +++- patacrep/content/__init__.py | 31 +++++++++++++++++++++++++------ patacrep/content/include.py | 20 +++++++++++++------- patacrep/content/section.py | 21 ++++++++++++--------- patacrep/content/song.py | 8 +++----- patacrep/content/songsection.py | 17 ++++++++++------- patacrep/content/sorted.py | 6 +++--- patacrep/content/tex.py | 10 +++++----- patacrep/latex/syntax.py | 8 +++++--- 9 files changed, 79 insertions(+), 46 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index 059143d7..53cb0d34 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -135,7 +135,9 @@ class Songbook: def iter_errors(self): """Iterate over errors of book and book content.""" yield from self._errors - for item in self._config.get('content', list()): + content = self._config.get('content', list()) + yield from content.iter_errors() + for item in content: if not hasattr(item, "iter_errors"): continue yield from item.iter_errors() diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 6ecb93db..6d00689b 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -73,7 +73,7 @@ import re import sys from patacrep import files -from patacrep.errors import SongbookError +from patacrep.errors import SharedError LOGGER = logging.getLogger(__name__) EOL = '\n' @@ -120,7 +120,7 @@ class ContentItem: """Return the string to end a block.""" return "" -class ContentError(SongbookError): +class ContentError(SharedError): """Error in a content plugin.""" def __init__(self, keyword=None, message=None): super(ContentError, self).__init__() @@ -140,13 +140,15 @@ class ContentList: def __init__(self, *args, **kwargs): self._content = list(*args, **kwargs) - self.errors = [] + self._errors = [] def __iter__(self): yield from self._content def extend(self, iterator): - return self._content.extend(iterator) + self._content.extend(iterator) + if isinstance(iterator, self.__class__): + self._errors.extend(iterator._errors) def append(self, item): return self._content.append(item) @@ -154,6 +156,23 @@ class ContentList: def __len__(self): return len(self._content) + def append_error(self, error): + LOGGER.warning(error) + self._errors.append(error) + + def extend_error(self, errors): + for error in errors: + self.append_error(error) + + def iter_errors(self): + yield from self._errors + +class EmptyContentList(ContentList): + def __init__(self, *, errors): + super().__init__() + for error in errors: + self.append_error(error) + @jinja2.contextfunction def render(context, content): """Render the content of the songbook as a LaTeX code. @@ -209,11 +228,11 @@ def process_content(content, config=None): try: match = keyword_re.match(elem[0]).groupdict() except AttributeError: - contentlist.errors.append(ContentError(elem[0], "Cannot parse content type.")) + contentlist.append_error(ContentError(elem[0], "Cannot parse content type.")) continue (keyword, argument) = (match['keyword'], match['argument']) if keyword not in plugins: - contentlist.errors.append(ContentError(keyword, "Unknown content type.")) + contentlist.append_error(ContentError(keyword, "Unknown content type.")) continue contentlist.extend(plugins[keyword]( keyword, diff --git a/patacrep/content/include.py b/patacrep/content/include.py index 7c553cdb..a972d5bf 100644 --- a/patacrep/content/include.py +++ b/patacrep/content/include.py @@ -9,7 +9,7 @@ import os import sys import logging -from patacrep.content import process_content, ContentError +from patacrep.content import process_content, ContentError, ContentList from patacrep import encoding, errors, files LOGGER = logging.getLogger(__name__) @@ -38,10 +38,14 @@ def parse(keyword, config, argument, contentlist): - argument: None; - contentlist: a list of file paths to be included. """ - new_contentlist = [] + new_contentlist = ContentList() for path in contentlist: - filepath = load_from_datadirs(path, config.get('datadir', [])) + try: + filepath = load_from_datadirs(path, config.get('datadir', [])) + except ContentError as error: + new_contentlist.append_error(error) + continue content_file = None try: with encoding.open_read( @@ -50,12 +54,14 @@ def parse(keyword, config, argument, contentlist): ) as content_file: new_content = json.load(content_file) except Exception as error: # pylint: disable=broad-except - LOGGER.warning(error) - LOGGER.warning("Error while loading file '{}'.".format(filepath)) - sys.exit(1) + new_contentlist.append_error(ContentError( + keyword="include", + message="Error while loading file '{}': {}".format(filepath, error), + )) + continue config["datadir"].append(os.path.abspath(os.path.dirname(filepath))) - new_contentlist += process_content(new_content, config) + new_contentlist.extend(process_content(new_content, config)) config["datadir"].pop() return new_contentlist diff --git a/patacrep/content/section.py b/patacrep/content/section.py index 250d514c..fe5cd0b1 100755 --- a/patacrep/content/section.py +++ b/patacrep/content/section.py @@ -1,6 +1,6 @@ """Allow LaTeX sections (starred or not) as content of a songbook.""" -from patacrep.content import ContentItem, ContentError +from patacrep.content import ContentItem, ContentError, ContentList, EmptyContentList KEYWORDS = [ "part", @@ -41,14 +41,17 @@ def parse(keyword, argument, contentlist, config): and long) of the section; - config: configuration dictionary of the current songbook. """ - if (keyword not in KEYWORDS) and (len(contentlist) != 1): - raise ContentError( - keyword, - "Starred section names must have exactly one argument." - ) - if (len(contentlist) not in [1, 2]): - raise ContentError(keyword, "Section can have one or two arguments.") - return ContentList(Section(keyword, *contentlist)) + try: + if (keyword not in KEYWORDS) and (len(contentlist) != 1): + raise ContentError( + keyword, + "Starred section names must have exactly one argument." + ) + if (len(contentlist) not in [1, 2]): + raise ContentError(keyword, "Section can have one or two arguments.") + return ContentList([Section(keyword, *contentlist)]) + except ContentError as error: + return EmptyContentList(errors=[error]) CONTENT_PLUGINS = dict([ diff --git a/patacrep/content/song.py b/patacrep/content/song.py index 5b3196a5..fe2297b2 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -87,14 +87,13 @@ def parse(keyword, argument, contentlist, config): LOGGER.debug('Parsing file "{}"…'.format(filename)) extension = filename.split(".")[-1] if extension not in plugins: - LOGGER.warning( - ( + songlist.append_error(ContentError(message=( 'I do not know how to parse "{}": name does ' 'not end with one of {}. Ignored.' ).format( os.path.join(songdir.datadir, filename), ", ".join(["'.{}'".format(key) for key in plugins.keys()]), - )) + ))) continue try: renderer = SongRenderer(plugins[extension]( @@ -103,8 +102,7 @@ def parse(keyword, argument, contentlist, config): datadir=songdir.datadir, )) except ContentError as error: - # TODO - songlist.errors.append(error) + songlist.append_error(error) continue songlist.append(renderer) config["_langs"].add(renderer.song.lang) diff --git a/patacrep/content/songsection.py b/patacrep/content/songsection.py index 5805939c..2a5ba2d5 100755 --- a/patacrep/content/songsection.py +++ b/patacrep/content/songsection.py @@ -1,6 +1,6 @@ """Allow 'songchapter' and 'songsection' as content of a songbook.""" -from patacrep.content import ContentItem, ContentError +from patacrep.content import ContentItem, ContentError, ContentList, EmptyContentList KEYWORDS = [ "songchapter", @@ -29,12 +29,15 @@ def parse(keyword, argument, contentlist, config): - contentlist: a list of one string, which is the name of the section; - config: configuration dictionary of the current songbook. """ - if (keyword not in KEYWORDS) and (len(contentlist) != 1): - raise ContentError( - keyword, - "Starred section names must have exactly one argument.", - ) - return ContentList(SongSection(keyword, contentlist[0])) + try: + if (keyword not in KEYWORDS) and (len(contentlist) != 1): + raise ContentError( + keyword, + "Starred section names must have exactly one argument.", + ) + return ContentList([SongSection(keyword, contentlist[0])]) + except ContentError as error: + return EmptyContentList(errors=[error]) CONTENT_PLUGINS = dict([ diff --git a/patacrep/content/sorted.py b/patacrep/content/sorted.py index 4fac210d..41052770 100755 --- a/patacrep/content/sorted.py +++ b/patacrep/content/sorted.py @@ -9,7 +9,7 @@ import logging import unidecode from patacrep import files -from patacrep.content import ContentError +from patacrep.content import ContentError, ContentList from patacrep.content.song import OnlySongsError, process_songs LOGGER = logging.getLogger(__name__) @@ -85,11 +85,11 @@ def parse(keyword, config, argument, contentlist): try: songlist = process_songs(contentlist, config) except OnlySongsError as error: - raise ContentError(keyword, ( + return EmptyContentError(errors=[ContentError(keyword, ( "Content list of this keyword can be only songs (or content " "that result into songs), and the following are not:" + str(error.not_songs) - )) + ))]) return sorted(songlist, key=key_generator(sort)) CONTENT_PLUGINS = {'sorted': parse} diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py index b0d4395b..efacb838 100755 --- a/patacrep/content/tex.py +++ b/patacrep/content/tex.py @@ -5,7 +5,7 @@ import logging import os from patacrep import files, errors -from patacrep.content import ContentItem, ContentList +from patacrep.content import ContentItem, ContentList, ContentError LOGGER = logging.getLogger(__name__) @@ -35,7 +35,7 @@ def parse(keyword, argument, contentlist, config): LOGGER.warning( "Useless 'tex' content: list of files to include is empty." ) - filelist = ContentList + filelist = ContentList() basefolders = itertools.chain( (path.fullpath for path in config['_songdir']), files.iter_datadirs(config['datadir']), @@ -51,9 +51,9 @@ def parse(keyword, argument, contentlist, config): )) break if not checked_file: - LOGGER.warning( - "{} Compilation may fail later.".format( - errors.notfound(filename, basefolders) + filelist.append_error(ContentError( + keyword="tex", + message=errors.notfound(filename, basefolders), ) ) continue diff --git a/patacrep/latex/syntax.py b/patacrep/latex/syntax.py index 2814b0ad..992c33a1 100644 --- a/patacrep/latex/syntax.py +++ b/patacrep/latex/syntax.py @@ -131,13 +131,15 @@ class LatexParser(Parser): """dictionary : identifier EQUAL braces dictionary_next | identifier EQUAL error dictionary_next """ + symbols[0] = {} if isinstance(symbols[3], ast.Expression): - symbols[0] = {} symbols[0][symbols[1]] = symbols[3] symbols[0].update(symbols[4]) else: - # TODO put error in self.errors - raise ParsingError("Do enclose arguments between braces.") + self.error( + line=symbols.lexer.lineno, + message="Argument '{}' should be enclosed between braces.".format(symbols[1]), + ) @staticmethod def p_identifier(symbols): From 98c284975add2cbb33ea357ea0c4e80df5ee80af Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 24 Dec 2015 19:21:58 +0100 Subject: [PATCH 20/59] pylint --- patacrep/build.py | 6 +++--- patacrep/content/__init__.py | 14 ++++++++++++-- patacrep/content/include.py | 1 - patacrep/content/song.py | 4 ++-- patacrep/content/songsection.py | 4 ++-- patacrep/content/sorted.py | 2 +- patacrep/content/tex.py | 9 +++++---- patacrep/latex/syntax.py | 8 +++----- patacrep/songs/chordpro/syntax.py | 1 - 9 files changed, 28 insertions(+), 21 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index 7ec0d739..6c61583b 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -128,9 +128,9 @@ class Songbook: def iter_errors(self): """Iterate over errors of book and book content.""" yield from self._errors - content = self._config.get('content', list()) - yield from content.iter_errors() - for item in content: + contentlist = self._config.get('content', list()) + yield from contentlist.iter_errors() + for item in contentlist: if not hasattr(item, "iter_errors"): continue yield from item.iter_errors() diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index ef389bfd..1aaf5043 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -147,28 +147,38 @@ class ContentList: yield from self._content def extend(self, iterator): + """Extend content list with an iterator. + + If the argument is of the same type, the list of errors is + also extended. + """ self._content.extend(iterator) - if isinstance(iterator, self.__class__): - self._errors.extend(iterator._errors) + if isinstance(iterator, ContentList): + self._errors.extend(iterator.iter_errors()) def append(self, item): + """Append an item to the content list.""" return self._content.append(item) def __len__(self): return len(self._content) def append_error(self, error): + """Log and append an error to the error list.""" LOGGER.warning(error) self._errors.append(error) def extend_error(self, errors): + """Extend the error list with the argument, which is logged.""" for error in errors: self.append_error(error) def iter_errors(self): + """Iterate over errors.""" yield from self._errors class EmptyContentList(ContentList): + """Empty content list: contain only errors.""" def __init__(self, *, errors): super().__init__() for error in errors: diff --git a/patacrep/content/include.py b/patacrep/content/include.py index a972d5bf..92f060ab 100644 --- a/patacrep/content/include.py +++ b/patacrep/content/include.py @@ -6,7 +6,6 @@ songs in JSON format. import json import os -import sys import logging from patacrep.content import process_content, ContentError, ContentList diff --git a/patacrep/content/song.py b/patacrep/content/song.py index a2cce2c0..dbdbe6c8 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -91,8 +91,8 @@ def parse(keyword, argument, contentlist, config): extension = filename.split(".")[-1] if extension not in plugins: songlist.append_error(ContentError(message=( - 'I do not know how to parse "{}": name does ' - 'not end with one of {}. Ignored.' + 'I do not know how to parse "{}": name does ' + 'not end with one of {}. Ignored.' ).format( os.path.join(songdir.datadir, filename), ", ".join(["'.{}'".format(key) for key in plugins.keys()]), diff --git a/patacrep/content/songsection.py b/patacrep/content/songsection.py index 2a5ba2d5..c5fea4ab 100755 --- a/patacrep/content/songsection.py +++ b/patacrep/content/songsection.py @@ -41,7 +41,7 @@ def parse(keyword, argument, contentlist, config): CONTENT_PLUGINS = dict([ - (word, parse) - for word + (keyword, parse) + for keyword in KEYWORDS ]) diff --git a/patacrep/content/sorted.py b/patacrep/content/sorted.py index cdb0dcd2..9f9fded9 100755 --- a/patacrep/content/sorted.py +++ b/patacrep/content/sorted.py @@ -9,7 +9,7 @@ import logging import unidecode from patacrep import files -from patacrep.content import ContentError, ContentList, EmptyContentList +from patacrep.content import ContentError, EmptyContentList from patacrep.content import process_content from patacrep.content.song import OnlySongsError diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py index efacb838..f32880cf 100755 --- a/patacrep/content/tex.py +++ b/patacrep/content/tex.py @@ -51,11 +51,12 @@ def parse(keyword, argument, contentlist, config): )) break if not checked_file: - filelist.append_error(ContentError( - keyword="tex", - message=errors.notfound(filename, basefolders), + filelist.append_error( + ContentError( + keyword="tex", + message=errors.notfound(filename, basefolders), + ) ) - ) continue filelist.append(LaTeX(checked_file)) diff --git a/patacrep/latex/syntax.py b/patacrep/latex/syntax.py index 890ca79c..edf9ceb1 100644 --- a/patacrep/latex/syntax.py +++ b/patacrep/latex/syntax.py @@ -3,11 +3,10 @@ import logging import ply.yacc as yacc -from patacrep.songs.syntax import Parser -from patacrep.latex.lexer import tokens, SimpleLexer, SongLexer from patacrep.latex import ast -from patacrep.errors import ParsingError from patacrep.latex.detex import detex +from patacrep.latex.lexer import tokens, SimpleLexer, SongLexer +from patacrep.songs.syntax import Parser LOGGER = logging.getLogger() @@ -126,8 +125,7 @@ class LatexParser(Parser): else: symbols[0] = [] - @staticmethod - def p_dictionary(symbols): + def p_dictionary(self, symbols): """dictionary : identifier EQUAL braces dictionary_next | identifier EQUAL error dictionary_next | empty diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 7fcf5d4b..8c17b3da 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -1,6 +1,5 @@ """ChordPro parser""" -import functools import logging import re From e1ab8daa7fda598a8f08f4bd5ce2724536f86be3 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 02:29:05 +0100 Subject: [PATCH 21/59] Turn an error into a warning --- patacrep/content/song.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/patacrep/content/song.py b/patacrep/content/song.py index dbdbe6c8..b1bf4378 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -90,13 +90,14 @@ def parse(keyword, argument, contentlist, config): LOGGER.debug('Parsing file "{}"…'.format(filename)) extension = filename.split(".")[-1] if extension not in plugins: - songlist.append_error(ContentError(message=( - 'I do not know how to parse "{}": name does ' - 'not end with one of {}. Ignored.' - ).format( - os.path.join(songdir.datadir, filename), - ", ".join(["'.{}'".format(key) for key in plugins.keys()]), - ))) + LOGGER.info( + ( + 'I do not know how to parse "%s": name does ' + 'not end with one of %s. Ignored.' + ), + os.path.join(songdir.datadir, filename), + ", ".join(["'.{}'".format(key) for key in plugins.keys()]) + ) continue try: renderer = SongRenderer(plugins[extension]( From 86833febf19c0c1298ec3476ad848fb0c840cce0 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 02:54:33 +0100 Subject: [PATCH 22/59] [chordpro] Lexer errors are added to the songbook errors --- patacrep/songs/chordpro/lexer.py | 10 ++++++++++ patacrep/songs/chordpro/syntax.py | 15 +++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/patacrep/songs/chordpro/lexer.py b/patacrep/songs/chordpro/lexer.py index ab8d4de6..7626a03c 100644 --- a/patacrep/songs/chordpro/lexer.py +++ b/patacrep/songs/chordpro/lexer.py @@ -1,8 +1,12 @@ """ChordPro lexer""" +import functools import logging + import ply.lex as lex +from patacrep.songs import errors + LOGGER = logging.getLogger() #pylint: disable=invalid-name @@ -85,6 +89,7 @@ class ChordProLexer: def __init__(self, *, filename=None): self.__class__.lexer = lex.lex(module=self) + self.error_builders = [] self.filename = filename # Define a rule so we can track line numbers @@ -140,6 +145,11 @@ class ChordProLexer: char=token.value[0], more=more, ) + self.error_builders.append(functools.partial( + errors.SongSyntaxError, + line=token.lexer.lineno, + message=message, + )) if self.filename is not None: message = "File {}: {}".format(self.filename, message) LOGGER.warning(message) diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 8c17b3da..0315ad1f 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -29,15 +29,17 @@ class ChordproParser(Parser): write_tables=0, ) - def parse(self, content, *, lexer): + def parse(self, content): """Parse file This is a shortcut to `yacc.yacc(...).parse()`. The arguments are transmitted to this method. """ - lexer = ChordProLexer(filename=self.filename).lexer - ast.AST.lexer = lexer - return self.parser.parse(content, lexer=lexer) + lexer = ChordProLexer(filename=self.filename) + ast.AST.lexer = lexer.lexer + parsed = self.parser.parse(content, lexer=lexer.lexer) + parsed.error_builders.extend(lexer.error_builders) + return parsed def p_song(self, symbols): """song : block song @@ -324,10 +326,7 @@ class ChordproParser(Parser): def parse_song(content, filename=None): """Parse song and return its metadata.""" parser = ChordproParser(filename) - parsed_content = parser.parse( - content, - lexer=ChordProLexer(filename=filename).lexer, - ) + parsed_content = parser.parse(content) if parsed_content is None: raise ContentError(message='Fatal error during song parsing.') return parsed_content From 0af000e49da829afc304fb70dba4d3783a7a2dcb Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 02:57:39 +0100 Subject: [PATCH 23/59] More verbose exception --- patacrep/latex/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 83d12906..1f48a0e4 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -87,6 +87,9 @@ class UnknownLanguage(Exception): self.fallback = fallback self.message = message + def __str__(self): + return self.message + def checklanguage(lang): """Check that `lang` is a known language. From c6e04372c1ffe70859fee60d77708c49c17d9558 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 20:14:02 +0100 Subject: [PATCH 24/59] "Illegal character in directive" now added to song errors --- patacrep/songs/chordpro/ast.py | 17 +++++++++++++---- patacrep/songs/chordpro/syntax.py | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index f18bd395..ff0bc4cb 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -3,8 +3,11 @@ # pylint: disable=too-few-public-methods from collections import OrderedDict +import functools import logging +from patacrep.songs import errors + LOGGER = logging.getLogger() def _indent(string): @@ -223,7 +226,7 @@ class Song(AST): "tag": "add_cumulative", } - def __init__(self, filename, directives, *, errors=None): + def __init__(self, filename, directives, *, error_builders=None): super().__init__() self.content = [] self.meta = OrderedDict() @@ -231,10 +234,10 @@ class Song(AST): self._titles = [] self._subtitles = [] self.filename = filename - if errors is None: + if error_builders is None: self.error_builders = [] else: - self.error_builders = errors + self.error_builders = error_builders for directive in directives: self.add(directive) @@ -265,7 +268,13 @@ class Song(AST): # Add a metadata directive. Some of them are added using special # methods listed in ``METADATA_ADD``. if data.keyword not in AVAILABLE_DIRECTIVES: - LOGGER.warning("Ignoring unknown directive '{}'.".format(data.keyword)) + message = "Ignoring unknown directive '{}'.".format(data.keyword) + LOGGER.warning("File {}, line {}: {}".format(self.filename, data.lineno, message)) + self.error_builders.append(functools.partial( + errors.SongSyntaxError, + line=data.lineno, + message=message, + )) if data.keyword in self.METADATA_ADD: getattr(self, self.METADATA_ADD[data.keyword])(data) else: diff --git a/patacrep/songs/chordpro/syntax.py b/patacrep/songs/chordpro/syntax.py index 0315ad1f..04104ac6 100644 --- a/patacrep/songs/chordpro/syntax.py +++ b/patacrep/songs/chordpro/syntax.py @@ -49,7 +49,7 @@ class ChordproParser(Parser): symbols[0] = ast.Song( self.filename, directives=self._directives, - errors=self._errors, + error_builders=self._errors, ) else: symbols[0] = symbols[2].add(symbols[1]) From e596013ed3d242270fe49dac0e19d58ed02698e1 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 20:14:23 +0100 Subject: [PATCH 25/59] Fix bug: line number appeard twice in error message --- patacrep/songs/chordpro/lexer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/patacrep/songs/chordpro/lexer.py b/patacrep/songs/chordpro/lexer.py index 7626a03c..93214448 100644 --- a/patacrep/songs/chordpro/lexer.py +++ b/patacrep/songs/chordpro/lexer.py @@ -140,8 +140,7 @@ class ChordProLexer: def error(self, token, more=""): """Display error message, and skip illegal token.""" - message = "Line {line}: Illegal character '{char}'{more}.".format( - line=token.lexer.lineno, + message = "Illegal character '{char}'{more}.".format( char=token.value[0], more=more, ) @@ -151,7 +150,9 @@ class ChordProLexer: message=message, )) if self.filename is not None: - message = "File {}: {}".format(self.filename, message) + message = "File {}, line {}: {}".format(self.filename, token.lexer.lineno, message) + else: + message = "Line {}: {}".format(token.lexer.lineno, message) LOGGER.warning(message) token.lexer.skip(1) From 53fe79642125e088a1efd95170806efba2865853 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 22:23:08 +0100 Subject: [PATCH 26/59] [examples] Fix and improve test examples --- examples/example-test.sb | 3 ++- examples/songs/tests/errors.csg | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/example-test.sb b/examples/example-test.sb index 4190c003..81b7420b 100644 --- a/examples/example-test.sb +++ b/examples/example-test.sb @@ -5,9 +5,10 @@ "lilypond", "pictures" ], +"lang": "ERROR", "booktype" : "chorded", "template" : "patacrep.tex", "encoding": "utf8", - "content": ["tests/*.sg", "tests/*.sgc"] + "content": ["tests/*.csg", "tests/*.tsg"] } diff --git a/examples/songs/tests/errors.csg b/examples/songs/tests/errors.csg index 3b682f4c..62bc7ee2 100644 --- a/examples/songs/tests/errors.csg +++ b/examples/songs/tests/errors.csg @@ -1,4 +1,4 @@ -{lang : en} +{lang : xx_XX} {columns : 2} { title : Error} {subtitle: A chordpro file with many errors} @@ -12,4 +12,5 @@ Bla [H]bla {fo#: bar} - +{image: foo} +{partition: bar} From 723cf9e805421f6c164699ce3075abb29c5fa8c7 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 22:27:33 +0100 Subject: [PATCH 27/59] Language errors are now added to song errors --- patacrep/latex/__init__.py | 13 +++++++++--- patacrep/songs/chordpro/__init__.py | 32 ++++++++++++++++++++++++++--- patacrep/songs/errors.py | 2 -- patacrep/templates.py | 14 ++++++++++--- 4 files changed, 50 insertions(+), 11 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 1f48a0e4..c43ba3a0 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -78,7 +78,7 @@ BABEL_LANGUAGES = OrderedDict(( # ('??_??', 'welsh'), )) -class UnknownLanguage(Exception): +class UnknownLanguage(errors.SharedError): """Error: Unknown language.""" def __init__(self, *, original, fallback, message): @@ -87,6 +87,11 @@ class UnknownLanguage(Exception): self.fallback = fallback self.message = message + @property + def babel(self): + """Return the fallback babel language.""" + return BABEL_LANGUAGES[self.fallback] + def __str__(self): return self.message @@ -128,10 +133,12 @@ def checklanguage(lang): ) ) -def lang2babel(lang): +def lang2babel(lang, *, raise_unknown=False): """Return the language used by babel, corresponding to the language code""" try: return BABEL_LANGUAGES[checklanguage(lang)] except UnknownLanguage as error: LOGGER.warning(str(error)) - return BABEL_LANGUAGES[error.fallback] + if raise_unknown: + raise + return error.babel diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 7ce51138..ba18041c 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -4,14 +4,15 @@ import logging import operator import os -from jinja2 import Environment, FileSystemLoader, contextfunction, ChoiceLoader +from jinja2 import Environment, FileSystemLoader, ChoiceLoader +from jinja2 import contextfunction import jinja2 from patacrep import encoding, files, pkg_datapath from patacrep.songs import Song from patacrep.songs.chordpro.syntax import parse_song from patacrep.templates import Renderer -from patacrep.latex import lang2babel +from patacrep.latex import lang2babel, UnknownLanguage from patacrep.files import path2posix LOGGER = logging.getLogger(__name__) @@ -39,6 +40,11 @@ class ChordproSong(Song): 'song': song, } + @staticmethod + def _jinja2_filters(): + """Return additional jinja2 filters.""" + return {} + def render(self, template="song"): # pylint: disable=arguments-differ context = { 'lang': self.lang, @@ -54,9 +60,9 @@ class ChordproSong(Song): )) jinjaenv.filters['search_image'] = self.search_image jinjaenv.filters['search_partition'] = self.search_partition - jinjaenv.filters['lang2babel'] = lang2babel jinjaenv.filters['sortargs'] = sort_directive_argument jinjaenv.filters['path2posix'] = path2posix + jinjaenv.filters.update(self._jinja2_filters()) try: return Renderer( @@ -123,6 +129,26 @@ class Chordpro2LatexSong(ChordproSong): ) return None + def _jinja2_filters(self): + return { + 'lang2babel': self.lang2babel, + } + + def lang2babel(self, lang): + """Return the LaTeX babel code corresponding to `lang`. + + Add an error to the list of errors if argument is invalid. + """ + try: + return lang2babel(lang, raise_unknown=True) + except UnknownLanguage as error: + error.message = "Song {}: {}".format( + self.fullpath, + error.message, + ) + self.errors.append(error) + return error.babel + class Chordpro2ChordproSong(ChordproSong): """Render chordpro song to chordpro code""" diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index 7737225f..289aa099 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -31,5 +31,3 @@ class SongSyntaxError(SongError): # class FileError(SongError): # type = "file" -# -# class LanguageError(SongError): diff --git a/patacrep/templates.py b/patacrep/templates.py index 8e4e32fd..8d82d4ba 100644 --- a/patacrep/templates.py +++ b/patacrep/templates.py @@ -84,11 +84,19 @@ class Renderer: self.jinjaenv.filters['escape_tex'] = _escape_tex self.jinjaenv.trim_blocks = True self.jinjaenv.lstrip_blocks = True - self.jinjaenv.filters["path2posix"] = files.path2posix - self.jinjaenv.filters["iter_datadirs"] = files.iter_datadirs - self.jinjaenv.filters["lang2babel"] = lang2babel + self._fill_filters() self.template = self.jinjaenv.get_template(template) + def _fill_filters(self): + """Define some jinja2 filters, if not set yet.""" + for key, value in [ + ("path2posix", files.path2posix), + ("iter_datadirs", files.iter_datadirs), + ("lang2babel", lang2babel), + ]: + if key not in self.jinjaenv.filters: + self.jinjaenv.filters[key] = value + class TexBookRenderer(Renderer): """Tex renderer for the whole songbook""" From d639cdc375b37ffb0bd5a1275fb070fec585528c Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 22:53:08 +0100 Subject: [PATCH 28/59] Error "Invalid songbook language" is now gathered into list songbook errors. --- patacrep/build.py | 1 + patacrep/templates.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index 6c61583b..6c06f7cf 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -114,6 +114,7 @@ class Songbook: self._config['filename'] = output.name[:-4] renderer.render_tex(output, self._config) + self._errors.extend(renderer.errors) def has_errors(self): """Return `True` iff errors have been encountered in the book. diff --git a/patacrep/templates.py b/patacrep/templates.py index 8d82d4ba..e1ba8ea3 100644 --- a/patacrep/templates.py +++ b/patacrep/templates.py @@ -9,7 +9,7 @@ from jinja2.ext import Extension from jinja2.meta import find_referenced_templates as find_templates from patacrep import errors, files -from patacrep.latex import lang2babel +from patacrep.latex import lang2babel, UnknownLanguage import patacrep.encoding _LATEX_SUBS = ( @@ -72,6 +72,7 @@ class Renderer: # pylint: disable=too-few-public-methods def __init__(self, template, jinjaenv, encoding=None): + self.errors = [] self.encoding = encoding self.jinjaenv = jinjaenv self.jinjaenv.block_start_string = '(*' @@ -92,11 +93,22 @@ class Renderer: for key, value in [ ("path2posix", files.path2posix), ("iter_datadirs", files.iter_datadirs), - ("lang2babel", lang2babel), + ("lang2babel", self.lang2babel), ]: if key not in self.jinjaenv.filters: self.jinjaenv.filters[key] = value + def lang2babel(self, lang): + """Return the LaTeX babel code corresponding to `lang`. + + Add an error to the list of errors if argument is invalid. + """ + try: + return lang2babel(lang, raise_unknown=True) + except UnknownLanguage as error: + error.message = "Songbook: {}".format(error.message) + self.errors.append(error) + return error.babel class TexBookRenderer(Renderer): """Tex renderer for the whole songbook""" From 5c471af26d64bb0baaf7f3abf20ab566800cf2c8 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 22:54:06 +0100 Subject: [PATCH 29/59] "File not found" errors are now stored into the list of songbook errors --- patacrep/songs/chordpro/__init__.py | 15 +++++++++------ patacrep/songs/errors.py | 7 +++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index ba18041c..3abf58cd 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -11,6 +11,7 @@ import jinja2 from patacrep import encoding, files, pkg_datapath from patacrep.songs import Song from patacrep.songs.chordpro.syntax import parse_song +from patacrep.songs.errors import FileNotFound from patacrep.templates import Renderer from patacrep.latex import lang2babel, UnknownLanguage from patacrep.files import path2posix @@ -113,20 +114,22 @@ class Chordpro2LatexSong(ChordproSong): try: return os.path.join("scores", super().search_partition(filename)) except FileNotFoundError: - LOGGER.warning( - "Song '%s' (datadir '%s'): Score '%s' not found.", - self.subpath, self.datadir, filename, + message = "Song '{}' (datadir '{}'): Score '{}' not found.".format( + self.subpath, self.datadir, filename ) + self.errors.append(FileNotFound(self, filename)) + LOGGER.warning(message) return None def search_image(self, filename): try: return os.path.join("img", super().search_image(filename)) except FileNotFoundError: - LOGGER.warning( - "Song '%s' (datadir '%s'): Image '%s' not found.", - self.subpath, self.datadir, filename, + message = "Song '{}' (datadir '{}'): Image '{}' not found.".format( + self.subpath, self.datadir, filename ) + self.errors.append(FileNotFound(self, filename)) + LOGGER.warning(message) return None def _jinja2_filters(self): diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index 289aa099..7c9bac4b 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -29,5 +29,8 @@ class SongSyntaxError(SongError): else: return "Song {}: {}".format(self.song, self.message) -# class FileError(SongError): -# type = "file" +class FileNotFound(SongError): + """File not found error""" + + def __init__(self, song, filename): + super().__init__(song, "File '{}' not found.".format(filename)) From 10578710ef3dda5fec19475a1f300427d941c6b6 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 27 Dec 2015 23:10:10 +0100 Subject: [PATCH 30/59] Better error messages --- patacrep/latex/__init__.py | 1 - patacrep/songs/chordpro/__init__.py | 11 +++++++---- patacrep/songs/chordpro/ast.py | 2 +- patacrep/songs/chordpro/lexer.py | 2 +- patacrep/songs/errors.py | 20 +++++++++++++++++--- patacrep/songs/syntax.py | 2 +- patacrep/templates.py | 4 ++++ 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index c43ba3a0..f1755186 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -138,7 +138,6 @@ def lang2babel(lang, *, raise_unknown=False): try: return BABEL_LANGUAGES[checklanguage(lang)] except UnknownLanguage as error: - LOGGER.warning(str(error)) if raise_unknown: raise return error.babel diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 3abf58cd..e61fbd1e 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -11,7 +11,7 @@ import jinja2 from patacrep import encoding, files, pkg_datapath from patacrep.songs import Song from patacrep.songs.chordpro.syntax import parse_song -from patacrep.songs.errors import FileNotFound +from patacrep.songs.errors import FileNotFound, SongUnknownLanguage from patacrep.templates import Renderer from patacrep.latex import lang2babel, UnknownLanguage from patacrep.files import path2posix @@ -145,11 +145,14 @@ class Chordpro2LatexSong(ChordproSong): try: return lang2babel(lang, raise_unknown=True) except UnknownLanguage as error: - error.message = "Song {}: {}".format( - self.fullpath, + new_error = SongUnknownLanguage( + self, + error.original, + error.fallback, error.message, ) - self.errors.append(error) + LOGGER.warning(new_error) + self.errors.append(new_error) return error.babel class Chordpro2ChordproSong(ChordproSong): diff --git a/patacrep/songs/chordpro/ast.py b/patacrep/songs/chordpro/ast.py index ff0bc4cb..769803fa 100644 --- a/patacrep/songs/chordpro/ast.py +++ b/patacrep/songs/chordpro/ast.py @@ -269,7 +269,7 @@ class Song(AST): # methods listed in ``METADATA_ADD``. if data.keyword not in AVAILABLE_DIRECTIVES: message = "Ignoring unknown directive '{}'.".format(data.keyword) - LOGGER.warning("File {}, line {}: {}".format(self.filename, data.lineno, message)) + LOGGER.warning("Song {}, line {}: {}".format(self.filename, data.lineno, message)) self.error_builders.append(functools.partial( errors.SongSyntaxError, line=data.lineno, diff --git a/patacrep/songs/chordpro/lexer.py b/patacrep/songs/chordpro/lexer.py index 93214448..9d0f3b89 100644 --- a/patacrep/songs/chordpro/lexer.py +++ b/patacrep/songs/chordpro/lexer.py @@ -150,7 +150,7 @@ class ChordProLexer: message=message, )) if self.filename is not None: - message = "File {}, line {}: {}".format(self.filename, token.lexer.lineno, message) + message = "Song {}, line {}: {}".format(self.filename, token.lexer.lineno, message) else: message = "Line {}: {}".format(token.lexer.lineno, message) LOGGER.warning(message) diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index 7c9bac4b..cee94b65 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -12,7 +12,13 @@ class SongError(SharedError): self.message = message def __str__(self): - return "Song {}: {}".format(self.song, self.message) + return "{}: {}".format(self._human_song(), self.message) + + def _human_song(self): + return "Datadir '{}', song '{}'".format( + self.song.datadir, + self.song.subpath, + ) class SongSyntaxError(SongError): """Syntax error""" @@ -25,12 +31,20 @@ class SongSyntaxError(SongError): def __str__(self): if self.line is not None: - return "Song {}, line {}: {}".format(self.song, self.line, self.message) + return "{}, line {}: {}".format(self._human_song(), self.line, self.message) else: - return "Song {}: {}".format(self.song, self.message) + return "{}: {}".format(self._human_song(), self.message) class FileNotFound(SongError): """File not found error""" def __init__(self, song, filename): super().__init__(song, "File '{}' not found.".format(filename)) + +class SongUnknownLanguage(SongError): + """Song language is not known.""" + + def __init__(self, song, original, fallback, message): + super().__init__(song, message) + self.original = original + self.fallback = fallback diff --git a/patacrep/songs/syntax.py b/patacrep/songs/syntax.py index db6c24c5..aeeb85a1 100644 --- a/patacrep/songs/syntax.py +++ b/patacrep/songs/syntax.py @@ -47,7 +47,7 @@ class Parser: if self.filename is None: LOGGER.warning(text) else: - LOGGER.warning("File {}: {}".format(self.filename, text)) + LOGGER.warning("Song {}: {}".format(self.filename, text)) def p_error(self, token): """Manage parsing errors.""" diff --git a/patacrep/templates.py b/patacrep/templates.py index e1ba8ea3..0b4d981b 100644 --- a/patacrep/templates.py +++ b/patacrep/templates.py @@ -1,5 +1,6 @@ """Template for .tex generation settings and utilities""" +import logging import re import json @@ -12,6 +13,8 @@ from patacrep import errors, files from patacrep.latex import lang2babel, UnknownLanguage import patacrep.encoding +LOGGER = logging.getLogger(__name__) + _LATEX_SUBS = ( (re.compile(r'\\'), r'\\textbackslash'), (re.compile(r'([{}_#%&$])'), r'\\\1'), @@ -107,6 +110,7 @@ class Renderer: return lang2babel(lang, raise_unknown=True) except UnknownLanguage as error: error.message = "Songbook: {}".format(error.message) + LOGGER.warning(error.message) self.errors.append(error) return error.babel From 38f87923d86a910838cf7a2c7342d0f3d7c75b37 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 28 Dec 2015 00:12:49 +0100 Subject: [PATCH 31/59] Song errors are cached only if there isn't any of them --- patacrep/songs/__init__.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py index e1b14c05..3745ab9e 100644 --- a/patacrep/songs/__init__.py +++ b/patacrep/songs/__init__.py @@ -90,7 +90,7 @@ class Song: # Version format of cached song. Increment this number if we update # information stored in cache. - CACHE_VERSION = 3 + CACHE_VERSION = 4 # List of attributes to cache cached_attributes = [ @@ -98,6 +98,7 @@ class Song: "unprefixed_titles", "cached", "data", + "errors", "lang", "authors", "_filehash", @@ -185,13 +186,19 @@ class Song: def _write_cache(self): """If relevant, write a dumbed down version of self to the cache.""" - if self.use_cache: - cached = {attr: getattr(self, attr) for attr in self.cached_attributes} - pickle.dump( - cached, - open(self.cached_name, 'wb'), - protocol=-1 - ) + if not self.use_cache: + return + if self.errors: + # As errors are exceptions, we cannot cache them because of a Python + # bug. When this bug is fixed, we will cache errors. + # 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 + ) def __str__(self): return str(self.fullpath) From 6fce49ae1bd126adb5ca7e0d8d40d2e1da328f35 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 28 Dec 2015 12:23:16 +0100 Subject: [PATCH 32/59] Fix type error --- patacrep/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patacrep/build.py b/patacrep/build.py index 6c06f7cf..f2c50036 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -129,7 +129,7 @@ class Songbook: def iter_errors(self): """Iterate over errors of book and book content.""" yield from self._errors - contentlist = self._config.get('content', list()) + contentlist = self._config.get('content', content.ContentList()) yield from contentlist.iter_errors() for item in contentlist: if not hasattr(item, "iter_errors"): From 61721f3425591948761df0c2c1eebe78f5b0cf79 Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 28 Dec 2015 13:05:10 +0100 Subject: [PATCH 33/59] Shared errors can be turned into dictionaries --- patacrep/content/__init__.py | 9 +++++++++ patacrep/errors.py | 8 ++++++++ patacrep/latex/__init__.py | 9 +++++++++ patacrep/songs/errors.py | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 1aaf5043..7cdc7061 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -136,6 +136,15 @@ class ContentError(SharedError): text += ": " + self.message return text + @property + def __dict__(self): + parent = vars(super()) + parent.update({ + 'keyword': self.keyword, + 'message': self.message, + }) + return parent + class ContentList: """List of content items""" diff --git a/patacrep/errors.py b/patacrep/errors.py index 0db5702b..08eebc6a 100644 --- a/patacrep/errors.py +++ b/patacrep/errors.py @@ -110,6 +110,14 @@ class SharedError(SongbookError): def __str__(self): raise NotImplementedError() + @property + def __dict__(self): + return { + 'type': self.__class__.__name__, + 'message': str(self), + 'full_message': str(self), + } + def notfound(filename, paths, message=None): """Return a string saying that file was not found in paths.""" if message is None: diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index f1755186..2aae8813 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -95,6 +95,15 @@ class UnknownLanguage(errors.SharedError): def __str__(self): return self.message + @property + def __dict__(self): + parent = vars(super()) + parent.update({ + 'original': self.original, + 'fallback': self.fallback, + }) + return parent + def checklanguage(lang): """Check that `lang` is a known language. diff --git a/patacrep/songs/errors.py b/patacrep/songs/errors.py index cee94b65..3a53ba6c 100644 --- a/patacrep/songs/errors.py +++ b/patacrep/songs/errors.py @@ -20,6 +20,17 @@ class SongError(SharedError): self.song.subpath, ) + @property + def __dict__(self): + parent = vars(super()) + parent.update({ + 'datadir': self.song.datadir, + 'subpath': self.song.subpath, + 'message': self.message, + 'full_message': str(self), + }) + return parent + class SongSyntaxError(SongError): """Syntax error""" # pylint: disable=too-few-public-methods @@ -35,11 +46,29 @@ class SongSyntaxError(SongError): else: return "{}: {}".format(self._human_song(), self.message) + @property + def __dict__(self): + parent = vars(super()) + if self.line is not None: + parent.update({ + 'line': self.line, + }) + return parent + class FileNotFound(SongError): """File not found error""" def __init__(self, song, filename): super().__init__(song, "File '{}' not found.".format(filename)) + self.filename = filename + + @property + def __dict__(self): + parent = vars(super()) + parent.update({ + 'filename': self.filename, + }) + return parent class SongUnknownLanguage(SongError): """Song language is not known.""" @@ -48,3 +77,12 @@ class SongUnknownLanguage(SongError): super().__init__(song, message) self.original = original self.fallback = fallback + + @property + def __dict__(self): + parent = vars(super()) + parent.update({ + 'original': self.original, + 'fallback': self.fallback, + }) + return parent From fbc8ac5a95938bd09617c3c6690f2237220c9e1c Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 28 Dec 2015 13:13:08 +0100 Subject: [PATCH 34/59] Tricking pylint --- patacrep/latex/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 2aae8813..0836a8bb 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -99,8 +99,8 @@ class UnknownLanguage(errors.SharedError): def __dict__(self): parent = vars(super()) parent.update({ - 'original': self.original, 'fallback': self.fallback, + 'original': self.original, }) return parent From 49080bc7f161c0b6ee522f23ace5e398b5237b5b Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 29 Dec 2015 10:00:56 +0100 Subject: [PATCH 35/59] Move iteration of sub-content errors into the ContentList.iter_errors() iterator --- patacrep/build.py | 4 ---- patacrep/content/__init__.py | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/patacrep/build.py b/patacrep/build.py index f2c50036..12315447 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -131,10 +131,6 @@ class Songbook: yield from self._errors contentlist = self._config.get('content', content.ContentList()) yield from contentlist.iter_errors() - for item in contentlist: - if not hasattr(item, "iter_errors"): - continue - yield from item.iter_errors() def requires_lilypond(self): """Tell if lilypond is part of the bookoptions""" diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 7cdc7061..85f6b7a4 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -185,6 +185,10 @@ class ContentList: def iter_errors(self): """Iterate over errors.""" yield from self._errors + for item in self: + if not hasattr(item, "iter_errors"): + continue + yield from item.iter_errors() class EmptyContentList(ContentList): """Empty content list: contain only errors.""" From 538aed16c9f77986f3e428c6a5ed7661cac5135a Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 20:22:50 +0100 Subject: [PATCH 36/59] Rephrase error --- patacrep/content/song.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patacrep/content/song.py b/patacrep/content/song.py index b1bf4378..8e9519dc 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -92,8 +92,8 @@ def parse(keyword, argument, contentlist, config): if extension not in plugins: LOGGER.info( ( - 'I do not know how to parse "%s": name does ' - 'not end with one of %s. Ignored.' + 'Cannot parse "%s": name does not end with one ' + 'of %s. Ignored.' ), os.path.join(songdir.datadir, filename), ", ".join(["'.{}'".format(key) for key in plugins.keys()]) From f9208e8581b1173e9e67a991a2538e3758e8fa28 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 20:28:50 +0100 Subject: [PATCH 37/59] Simplify test --- patacrep/content/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index 85f6b7a4..a2268ee7 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -223,7 +223,7 @@ def render(context, content): rendered += elem.render(context) + EOL previous = elem - if isinstance(last, ContentItem): + if last is not None: rendered += last.end_block(context) + EOL return rendered From a2658eed4e948be293aba77c5bd6ca696222aaaf Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 20:29:54 +0100 Subject: [PATCH 38/59] Fix and move test --- .../content.sb | 0 .../content.tex.control | 18 +++++++++--------- .../content_datadir/content/foo.tex | 0 .../content_datadir/content/inter.isg | 0 .../content_datadir/content/song.csg | 0 .../content_datadir/content/song.tsg | 0 .../content_datadir/songs/include.sbc | 0 .../content_datadir/songs/inter.isg | 0 .../content_datadir/songs/song.csg | 0 .../content_datadir/songs/song.tsg | 0 10 files changed, 9 insertions(+), 9 deletions(-) rename test/{test_compilation => test_songbook}/content.sb (100%) rename test/{test_compilation => test_songbook}/content.tex.control (76%) rename test/{test_compilation => test_songbook}/content_datadir/content/foo.tex (100%) rename test/{test_compilation => test_songbook}/content_datadir/content/inter.isg (100%) rename test/{test_compilation => test_songbook}/content_datadir/content/song.csg (100%) rename test/{test_compilation => test_songbook}/content_datadir/content/song.tsg (100%) rename test/{test_compilation => test_songbook}/content_datadir/songs/include.sbc (100%) rename test/{test_compilation => test_songbook}/content_datadir/songs/inter.isg (100%) rename test/{test_compilation => test_songbook}/content_datadir/songs/song.csg (100%) rename test/{test_compilation => test_songbook}/content_datadir/songs/song.tsg (100%) diff --git a/test/test_compilation/content.sb b/test/test_songbook/content.sb similarity index 100% rename from test/test_compilation/content.sb rename to test/test_songbook/content.sb diff --git a/test/test_compilation/content.tex.control b/test/test_songbook/content.tex.control similarity index 76% rename from test/test_compilation/content.tex.control rename to test/test_songbook/content.tex.control index 11a187d4..9dfa5b64 100644 --- a/test/test_compilation/content.tex.control +++ b/test/test_songbook/content.tex.control @@ -13,9 +13,9 @@ \makeatletter \def\input@path{ % - {/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/latex/} % - {/home/louis/projets/patacrep/patacrep/test/test_compilation/latex/} % - {/home/louis/projets/patacrep/patacrep/patacrep/data/latex/} % + {@TEST_FOLDER@/content_datadir/latex/} % + {@TEST_FOLDER@/latex/} % + {@DATA_FOLDER@/latex/} % } \makeatother @@ -39,9 +39,9 @@ guitar, \usepackage{graphicx} \graphicspath{ % - {/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/} % - {/home/louis/projets/patacrep/patacrep/test/test_compilation/} % - {/home/louis/projets/patacrep/patacrep/patacrep/data/} % + {@TEST_FOLDER@/content_datadir/} % + {@TEST_FOLDER@/} % + {@DATA_FOLDER@/} % } @@ -94,7 +94,7 @@ guitar, %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% songs/./song.tsg -\import{/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/songs/}{song.tsg} +\import{@TEST_FOLDER@/content_datadir/songs/}{song.tsg} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% songs/./song.csg @@ -140,7 +140,7 @@ guitar, %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% content_datadir/content/song.tsg -\import{/home/louis/projets/patacrep/patacrep/test/test_compilation/content_datadir/content/}{song.tsg} +\import{@TEST_FOLDER@/content_datadir/content/}{song.tsg} \end{songs} @@ -153,4 +153,4 @@ guitar, -\end{document} \ No newline at end of file +\end{document} diff --git a/test/test_compilation/content_datadir/content/foo.tex b/test/test_songbook/content_datadir/content/foo.tex similarity index 100% rename from test/test_compilation/content_datadir/content/foo.tex rename to test/test_songbook/content_datadir/content/foo.tex diff --git a/test/test_compilation/content_datadir/content/inter.isg b/test/test_songbook/content_datadir/content/inter.isg similarity index 100% rename from test/test_compilation/content_datadir/content/inter.isg rename to test/test_songbook/content_datadir/content/inter.isg diff --git a/test/test_compilation/content_datadir/content/song.csg b/test/test_songbook/content_datadir/content/song.csg similarity index 100% rename from test/test_compilation/content_datadir/content/song.csg rename to test/test_songbook/content_datadir/content/song.csg diff --git a/test/test_compilation/content_datadir/content/song.tsg b/test/test_songbook/content_datadir/content/song.tsg similarity index 100% rename from test/test_compilation/content_datadir/content/song.tsg rename to test/test_songbook/content_datadir/content/song.tsg diff --git a/test/test_compilation/content_datadir/songs/include.sbc b/test/test_songbook/content_datadir/songs/include.sbc similarity index 100% rename from test/test_compilation/content_datadir/songs/include.sbc rename to test/test_songbook/content_datadir/songs/include.sbc diff --git a/test/test_compilation/content_datadir/songs/inter.isg b/test/test_songbook/content_datadir/songs/inter.isg similarity index 100% rename from test/test_compilation/content_datadir/songs/inter.isg rename to test/test_songbook/content_datadir/songs/inter.isg diff --git a/test/test_compilation/content_datadir/songs/song.csg b/test/test_songbook/content_datadir/songs/song.csg similarity index 100% rename from test/test_compilation/content_datadir/songs/song.csg rename to test/test_songbook/content_datadir/songs/song.csg diff --git a/test/test_compilation/content_datadir/songs/song.tsg b/test/test_songbook/content_datadir/songs/song.tsg similarity index 100% rename from test/test_compilation/content_datadir/songs/song.tsg rename to test/test_songbook/content_datadir/songs/song.tsg From dd2147c6126915eb0a3bf1e44aded04c97a9c8d9 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 21:32:52 +0100 Subject: [PATCH 39/59] Add an `iter_flat_error()` method --- patacrep/build.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/patacrep/build.py b/patacrep/build.py index 12315447..2fed6c31 100644 --- a/patacrep/build.py +++ b/patacrep/build.py @@ -132,6 +132,21 @@ class Songbook: contentlist = self._config.get('content', content.ContentList()) yield from contentlist.iter_errors() + def iter_flat_errors(self): + """Iterate over errors, in an exportable format. + + This function does the same job as :func:`iter_errors`, exepted that + the errors are represented as dictionaries of standard python types. + + Each error (dictionary) contains the following keys: + - `type`: the error type (as the class name of the error); + - `message`: Error message, that does not include the error location (datadir, song, etc.); + - `full_message`: Error message, containing the full error location; + - depending on the error type, more keys may be present in the error. + """ + for error in self.iter_errors(): + yield vars(error) + def requires_lilypond(self): """Tell if lilypond is part of the bookoptions""" return 'lilypond' in self.config.get('bookoptions', []) From 251deb889a1526788ce3bf6b221afd167315a157 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 21:36:18 +0100 Subject: [PATCH 40/59] Define a constant for the default language --- patacrep/latex/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 0836a8bb..8e4abd5f 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -13,6 +13,8 @@ from patacrep.latex.syntax import tex2plain, parse_song LOGGER = logging.getLogger(__name__) +DEFAULT_LANGUAGE = "en_us" + BABEL_LANGUAGES = OrderedDict(( ('de_de', 'german'), ('de_at', 'austrian'), @@ -132,13 +134,14 @@ def checklanguage(lang): available = ", ".join(BABEL_LANGUAGES.keys()) raise UnknownLanguage( original=lang, - fallback="en_us", + fallback=DEFAULT_LANGUAGE, message=( "Unknown language code '{}' (supported: {}). Using " - "default 'en_us' instead." + "default '{}' instead." ).format( lang, - available + available, + DEFAULT_LANGUAGE, ) ) From 5cfe8cd0f1c2d25e238f1ea9d2923a0bf2ca053b Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 30 Dec 2015 21:38:13 +0100 Subject: [PATCH 41/59] Remove unused argument, and improve documentation --- patacrep/latex/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/patacrep/latex/__init__.py b/patacrep/latex/__init__.py index 8e4abd5f..d2a57fc8 100644 --- a/patacrep/latex/__init__.py +++ b/patacrep/latex/__init__.py @@ -145,11 +145,11 @@ def checklanguage(lang): ) ) -def lang2babel(lang, *, raise_unknown=False): - """Return the language used by babel, corresponding to the language code""" - try: - return BABEL_LANGUAGES[checklanguage(lang)] - except UnknownLanguage as error: - if raise_unknown: - raise - return error.babel +def lang2babel(lang): + """Return the language used by babel, corresponding to the language code + + Raises an `UnknownLanguage` exception if the `lang` argument is not known, + the :attr:`fallback` attribute of the exception being the existing + alternative language that can be used instead. + """ + return BABEL_LANGUAGES[checklanguage(lang)] From b2a9f4c81a5ffc10d763b3b97e4e3287aab8570c Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 31 Dec 2015 10:08:44 +0100 Subject: [PATCH 42/59] Remove useless (and wrong) argument --- patacrep/songs/chordpro/__init__.py | 2 +- patacrep/templates.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index e61fbd1e..0df82d42 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -143,7 +143,7 @@ class Chordpro2LatexSong(ChordproSong): Add an error to the list of errors if argument is invalid. """ try: - return lang2babel(lang, raise_unknown=True) + return lang2babel(lang) except UnknownLanguage as error: new_error = SongUnknownLanguage( self, diff --git a/patacrep/templates.py b/patacrep/templates.py index 0b4d981b..ffef0b4a 100644 --- a/patacrep/templates.py +++ b/patacrep/templates.py @@ -107,7 +107,7 @@ class Renderer: Add an error to the list of errors if argument is invalid. """ try: - return lang2babel(lang, raise_unknown=True) + return lang2babel(lang) except UnknownLanguage as error: error.message = "Songbook: {}".format(error.message) LOGGER.warning(error.message) From 0f2c0938bc248d54e8cda453d37247d83a1d0dd5 Mon Sep 17 00:00:00 2001 From: Louis Date: Fri, 1 Jan 2016 16:52:30 +0100 Subject: [PATCH 43/59] Templates: Reorganise default filters --- patacrep/songs/chordpro/__init__.py | 29 +++++++++++-------- patacrep/templates.py | 43 ++++++++++++++++------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/patacrep/songs/chordpro/__init__.py b/patacrep/songs/chordpro/__init__.py index 0df82d42..a7fd3e3b 100644 --- a/patacrep/songs/chordpro/__init__.py +++ b/patacrep/songs/chordpro/__init__.py @@ -14,7 +14,6 @@ from patacrep.songs.chordpro.syntax import parse_song from patacrep.songs.errors import FileNotFound, SongUnknownLanguage from patacrep.templates import Renderer from patacrep.latex import lang2babel, UnknownLanguage -from patacrep.files import path2posix LOGGER = logging.getLogger(__name__) @@ -22,6 +21,10 @@ def sort_directive_argument(directives): """Sort directives by their argument.""" return sorted(directives, key=operator.attrgetter("argument")) +DEFAULT_FILTERS = { + 'sortargs': sort_directive_argument, + } + class ChordproSong(Song): """Chordpro song parser""" # pylint: disable=abstract-method @@ -41,10 +44,14 @@ class ChordproSong(Song): 'song': song, } - @staticmethod - def _jinja2_filters(): + def _filters(self): """Return additional jinja2 filters.""" - return {} + filters = DEFAULT_FILTERS.copy() + filters.update({ + 'search_image': self.search_image, + 'search_partition': self.search_partition, + }) + return filters def render(self, template="song"): # pylint: disable=arguments-differ context = { @@ -59,11 +66,7 @@ class ChordproSong(Song): jinjaenv = Environment(loader=FileSystemLoader( self.iter_datadirs("templates", "songs", "chordpro", self.output_language) )) - jinjaenv.filters['search_image'] = self.search_image - jinjaenv.filters['search_partition'] = self.search_partition - jinjaenv.filters['sortargs'] = sort_directive_argument - jinjaenv.filters['path2posix'] = path2posix - jinjaenv.filters.update(self._jinja2_filters()) + jinjaenv.filters.update(self._filters()) try: return Renderer( @@ -132,10 +135,12 @@ class Chordpro2LatexSong(ChordproSong): LOGGER.warning(message) return None - def _jinja2_filters(self): - return { + def _filters(self): + parent = super()._filters() + parent.update({ 'lang2babel': self.lang2babel, - } + }) + return parent def lang2babel(self, lang): """Return the LaTeX babel code corresponding to `lang`. diff --git a/patacrep/templates.py b/patacrep/templates.py index ffef0b4a..9194cf7f 100644 --- a/patacrep/templates.py +++ b/patacrep/templates.py @@ -45,6 +45,18 @@ _VARIABLE_REGEXP = re.compile( """, re.VERBOSE|re.DOTALL) +def _escape_tex(value): + '''Escape TeX special characters''' + newval = value + for pattern, replacement in _LATEX_SUBS: + newval = pattern.sub(replacement, newval) + return newval + +DEFAULT_FILTERS = { + "escape_tex": _escape_tex, + "iter_datadirs": files.iter_datadirs, + "path2posix": files.path2posix, + } class VariablesExtension(Extension): """Extension to jinja2 to silently ignore variable block. @@ -62,14 +74,6 @@ class VariablesExtension(Extension): return nodes.Const("") # pylint: disable=no-value-for-parameter -def _escape_tex(value): - '''Escape TeX special characters''' - newval = value - for pattern, replacement in _LATEX_SUBS: - newval = pattern.sub(replacement, newval) - return newval - - class Renderer: """Render a template to a LaTeX file.""" # pylint: disable=too-few-public-methods @@ -85,22 +89,23 @@ class Renderer: self.jinjaenv.comment_start_string = '(% comment %)' self.jinjaenv.comment_end_string = '(% endcomment %)' self.jinjaenv.line_comment_prefix = '%!' - self.jinjaenv.filters['escape_tex'] = _escape_tex self.jinjaenv.trim_blocks = True self.jinjaenv.lstrip_blocks = True - self._fill_filters() - self.template = self.jinjaenv.get_template(template) - - def _fill_filters(self): - """Define some jinja2 filters, if not set yet.""" - for key, value in [ - ("path2posix", files.path2posix), - ("iter_datadirs", files.iter_datadirs), - ("lang2babel", self.lang2babel), - ]: + # Fill default filters + for key, value in self.filters().items(): if key not in self.jinjaenv.filters: self.jinjaenv.filters[key] = value + self.template = self.jinjaenv.get_template(template) + + def filters(self): + """Return a dictionary of jinja2 filters.""" + filters = DEFAULT_FILTERS.copy() + filters.update({ + "lang2babel": self.lang2babel, + }) + return filters + def lang2babel(self, lang): """Return the LaTeX babel code corresponding to `lang`. From 1a9309eea4493f65c7c9dfe9aa1a035a030ca2e9 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 18:56:15 +0100 Subject: [PATCH 44/59] Update AppVeyor packages --- .appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index 913363b9..c3aa5808 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -37,6 +37,9 @@ install: # Manually install required texlive packages - cmd: mpm.exe --install-some texlive_packages.txt + # Update all packages + - cmd: mpm.exe --update + build: false # Not a C# project, build stuff at the test step instead. before_test: From 5072cab9d32de6721a7cb4815340f79cf2d210d6 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 19:12:00 +0100 Subject: [PATCH 45/59] Make appveyor more verbose --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index c3aa5808..2ea97612 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -38,7 +38,7 @@ install: - cmd: mpm.exe --install-some texlive_packages.txt # Update all packages - - cmd: mpm.exe --update + - cmd: mpm.exe --update-db --update --verbose build: false # Not a C# project, build stuff at the test step instead. From 6fb4e19eafe9660e4a2a48f071a58f3330cc9c62 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 19:31:45 +0100 Subject: [PATCH 46/59] Display log in case of failure --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 2ea97612..b2991a80 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -38,7 +38,7 @@ install: - cmd: mpm.exe --install-some texlive_packages.txt # Update all packages - - cmd: mpm.exe --update-db --update --verbose + - cmd: mpm.exe --update-db --update --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log build: false # Not a C# project, build stuff at the test step instead. From 313f4d0a310be32ce0962e1aa76a7d898b7cd4c8 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 20:16:22 +0100 Subject: [PATCH 47/59] Try mpm with admin rights --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index b2991a80..f269e475 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -38,7 +38,7 @@ install: - cmd: mpm.exe --install-some texlive_packages.txt # Update all packages - - cmd: mpm.exe --update-db --update --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log + - cmd: mpm.exe --admin --update --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log build: false # Not a C# project, build stuff at the test step instead. From f925094270e2f921befa5d83473f705416e5d911 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 20:25:34 +0100 Subject: [PATCH 48/59] List updatable packages --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index f269e475..34e12a5d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -38,7 +38,7 @@ install: - cmd: mpm.exe --install-some texlive_packages.txt # Update all packages - - cmd: mpm.exe --admin --update --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log + - cmd: mpm.exe --find-updates --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log build: false # Not a C# project, build stuff at the test step instead. From 0759f3c9ce8da8783b39df46a832e905557d26e5 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 20:32:15 +0100 Subject: [PATCH 49/59] Selective update --- .appveyor.yml | 4 ++-- texlive_packages_updates.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 texlive_packages_updates.txt diff --git a/.appveyor.yml b/.appveyor.yml index 34e12a5d..ed96c9e3 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -37,8 +37,8 @@ install: # Manually install required texlive packages - cmd: mpm.exe --install-some texlive_packages.txt - # Update all packages - - cmd: mpm.exe --find-updates --verbose || cat C:/projects/patacrep/miktex/miktex/log/mpmcli.log + # Update some packages + - cmd: mpm.exe --update-some texlive_packages_updates.txt build: false # Not a C# project, build stuff at the test step instead. diff --git a/texlive_packages_updates.txt b/texlive_packages_updates.txt new file mode 100644 index 00000000..e489c9eb --- /dev/null +++ b/texlive_packages_updates.txt @@ -0,0 +1 @@ +luatexbase \ No newline at end of file From 2aefd1747efcc4070694b972c00245f830052b3b Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 20:43:58 +0100 Subject: [PATCH 50/59] Add ltxbase --- texlive_packages_updates.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/texlive_packages_updates.txt b/texlive_packages_updates.txt index e489c9eb..22ed6eb9 100644 --- a/texlive_packages_updates.txt +++ b/texlive_packages_updates.txt @@ -1 +1,2 @@ -luatexbase \ No newline at end of file +luatexbase +ltxbase \ No newline at end of file From 9c237846af24a8d6028ac03257d11ebcc3d0660a Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Sun, 3 Jan 2016 20:58:13 +0100 Subject: [PATCH 51/59] Test with other packages --- texlive_packages_updates.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/texlive_packages_updates.txt b/texlive_packages_updates.txt index 22ed6eb9..53bf35a6 100644 --- a/texlive_packages_updates.txt +++ b/texlive_packages_updates.txt @@ -1,2 +1,10 @@ +fontspec +ltxbase +lualibs +luamplib +luaotfload luatexbase -ltxbase \ No newline at end of file + +miktex-lua52-bin-2.9 +miktex-luatex-base +miktex-luatex-bin-2.9 \ No newline at end of file From e2a4aed90afcb3717569fa3e4bd8ba8310002f50 Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Mon, 4 Jan 2016 00:21:39 +0100 Subject: [PATCH 52/59] update before install --- .appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index ed96c9e3..126e9eaf 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -34,12 +34,12 @@ install: # Update fonts - cmd: luaotfload-tool.exe --update - # Manually install required texlive packages - - cmd: mpm.exe --install-some texlive_packages.txt - # Update some packages - cmd: mpm.exe --update-some texlive_packages_updates.txt + # Manually install required texlive packages + - cmd: mpm.exe --install-some texlive_packages.txt + build: false # Not a C# project, build stuff at the test step instead. before_test: From e9f5fe435a2a769085765a79db3c187ae56c527d Mon Sep 17 00:00:00 2001 From: Oliverpool Date: Mon, 4 Jan 2016 10:00:04 +0100 Subject: [PATCH 53/59] update all but mpmcli-bin --- texlive_packages_updates.txt | 69 ++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/texlive_packages_updates.txt b/texlive_packages_updates.txt index 53bf35a6..7936f7c4 100644 --- a/texlive_packages_updates.txt +++ b/texlive_packages_updates.txt @@ -1,10 +1,75 @@ +bidi fontspec +hyph-utf8 +latex2e-help-texinfo ltxbase lualibs luamplib luaotfload luatexbase - +miktex-arctrl-bin-2.9 +miktex-biber-bin +miktex-bibtex-bin-2.9 +miktex-bibtex8bit-bin-2.9 +miktex-bin-2.9 +miktex-cairo-bin-2.9 +miktex-cjkutils-bin-2.9 +miktex-cweb-bin-2.9 +miktex-devnag-bin-2.9 +miktex-doc-2.9 +miktex-dvicopy-bin-2.9 +miktex-dvipdfmx-bin-2.9 +miktex-dvipng-bin-2.9 +miktex-dvips-bin-2.9 +miktex-etex-base-2.9 +miktex-findtexmf-bin-2.9 +miktex-fontconfig-base +miktex-fontconfig-bin-2.9 +miktex-fontname-base +miktex-fonts-bin-2.9 +miktex-freetype2-bin-2.9 +miktex-graphics-bin-2.9 +miktex-graphite2-bin-2.9 +miktex-gsf2pk-bin-2.9 +miktex-hunspell-bin-2.9 +miktex-icu-bin-2.9 +miktex-int-bin-2.9 +miktex-kpathsea-bin-2.9 +miktex-log4cxx-bin-2.9 miktex-lua52-bin-2.9 miktex-luatex-base -miktex-luatex-bin-2.9 \ No newline at end of file +miktex-luatex-bin-2.9 +miktex-makeindex-bin-2.9 +miktex-metafont-bin-2.9 +miktex-metapost-bin-2.9 +miktex-mfware-bin-2.9 +miktex-misc +miktex-mkfntmap-bin-2.9 +miktex-mktex-bin-2.9 +miktex-mo-bin-2.9 +miktex-mpm-bin-2.9 + +miktex-mthelp-bin-2.9 +miktex-mtprint-bin-2.9 +miktex-omega-bin-2.9 +miktex-pdftex-bin-2.9 +miktex-poppler-bin-2.9 +miktex-ps2pk-bin-2.9 +miktex-psutils-bin-2.9 +miktex-qt5-bin +miktex-runtime-bin-2.9 +miktex-teckit-bin-2.9 +miktex-tex-bin-2.9 +miktex-tex2xindy-bin-2.9 +miktex-tex4ht-bin-2.9 +miktex-texify-bin-2.9 +miktex-texinfo-base +miktex-texware-bin-2.9 +miktex-texworks-bin-2.9 +miktex-web-bin-2.9 +miktex-xdvipdfmx-bin-2.9 +miktex-xetex-base +miktex-xetex-bin-2.9 +miktex-yap-bin-2.9 +pstricks +tools \ No newline at end of file From e67e7933d4f4cace0c454d0bb20202b0a5f22825 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 12:00:55 +0100 Subject: [PATCH 54/59] Manually update some packages --- .appveyor.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 126e9eaf..b911b700 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -34,8 +34,9 @@ install: # Update fonts - cmd: luaotfload-tool.exe --update - # Update some packages - - cmd: mpm.exe --update-some texlive_packages_updates.txt + # Update some packages to prevent ltluatex bug + - cmd: mpm.exe --update=miktex-bin-2.9 + - cmd: mpm.exe --update=ltxbase --update=luatexbase # Manually install required texlive packages - cmd: mpm.exe --install-some texlive_packages.txt @@ -50,4 +51,4 @@ test_script: # Cache Miktex Portable file cache: -- C:\projects\patacrep\miktex-portable.exe \ No newline at end of file +- C:\projects\patacrep\miktex-portable.exe From d02366a9c0ac2e5dc1de10b0039dbde4b17e88d8 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 12:01:25 +0100 Subject: [PATCH 55/59] Update texlive_packages.txt --- texlive_packages.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/texlive_packages.txt b/texlive_packages.txt index 9e354c83..7a3e79cb 100644 --- a/texlive_packages.txt +++ b/texlive_packages.txt @@ -6,6 +6,7 @@ babel-italian babel-latin babel-portuges babel-spanish +ctablestack etoolbox fancybox framed @@ -16,6 +17,7 @@ mptopdf ms pgf tipa +unicode-data url xcolor xstring From 6fbcf13d22fdff0c9bf091d45e1d745d56317ccc Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 12:01:41 +0100 Subject: [PATCH 56/59] Delete texlive_packages_updates.txt --- texlive_packages_updates.txt | 75 ------------------------------------ 1 file changed, 75 deletions(-) delete mode 100644 texlive_packages_updates.txt diff --git a/texlive_packages_updates.txt b/texlive_packages_updates.txt deleted file mode 100644 index 7936f7c4..00000000 --- a/texlive_packages_updates.txt +++ /dev/null @@ -1,75 +0,0 @@ -bidi -fontspec -hyph-utf8 -latex2e-help-texinfo -ltxbase -lualibs -luamplib -luaotfload -luatexbase -miktex-arctrl-bin-2.9 -miktex-biber-bin -miktex-bibtex-bin-2.9 -miktex-bibtex8bit-bin-2.9 -miktex-bin-2.9 -miktex-cairo-bin-2.9 -miktex-cjkutils-bin-2.9 -miktex-cweb-bin-2.9 -miktex-devnag-bin-2.9 -miktex-doc-2.9 -miktex-dvicopy-bin-2.9 -miktex-dvipdfmx-bin-2.9 -miktex-dvipng-bin-2.9 -miktex-dvips-bin-2.9 -miktex-etex-base-2.9 -miktex-findtexmf-bin-2.9 -miktex-fontconfig-base -miktex-fontconfig-bin-2.9 -miktex-fontname-base -miktex-fonts-bin-2.9 -miktex-freetype2-bin-2.9 -miktex-graphics-bin-2.9 -miktex-graphite2-bin-2.9 -miktex-gsf2pk-bin-2.9 -miktex-hunspell-bin-2.9 -miktex-icu-bin-2.9 -miktex-int-bin-2.9 -miktex-kpathsea-bin-2.9 -miktex-log4cxx-bin-2.9 -miktex-lua52-bin-2.9 -miktex-luatex-base -miktex-luatex-bin-2.9 -miktex-makeindex-bin-2.9 -miktex-metafont-bin-2.9 -miktex-metapost-bin-2.9 -miktex-mfware-bin-2.9 -miktex-misc -miktex-mkfntmap-bin-2.9 -miktex-mktex-bin-2.9 -miktex-mo-bin-2.9 -miktex-mpm-bin-2.9 - -miktex-mthelp-bin-2.9 -miktex-mtprint-bin-2.9 -miktex-omega-bin-2.9 -miktex-pdftex-bin-2.9 -miktex-poppler-bin-2.9 -miktex-ps2pk-bin-2.9 -miktex-psutils-bin-2.9 -miktex-qt5-bin -miktex-runtime-bin-2.9 -miktex-teckit-bin-2.9 -miktex-tex-bin-2.9 -miktex-tex2xindy-bin-2.9 -miktex-tex4ht-bin-2.9 -miktex-texify-bin-2.9 -miktex-texinfo-base -miktex-texware-bin-2.9 -miktex-texworks-bin-2.9 -miktex-web-bin-2.9 -miktex-xdvipdfmx-bin-2.9 -miktex-xetex-base -miktex-xetex-bin-2.9 -miktex-yap-bin-2.9 -pstricks -tools \ No newline at end of file From 3be952c6716b9f222c39b8d560b57a28ba822e0f Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 12:51:55 +0100 Subject: [PATCH 57/59] Change miktex install order --- .appveyor.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b911b700..3f7ca701 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -31,16 +31,16 @@ install: # Let the binaries be directly callable - cmd: set PATH=%PATH%;C:\projects\patacrep\miktex\miktex\bin - # Update fonts - - cmd: luaotfload-tool.exe --update - # Update some packages to prevent ltluatex bug - cmd: mpm.exe --update=miktex-bin-2.9 - - cmd: mpm.exe --update=ltxbase --update=luatexbase + - cmd: mpm.exe --update=ltxbase --update=luatexbase --update=luaotfload # Manually install required texlive packages - cmd: mpm.exe --install-some texlive_packages.txt + # Update fonts + - cmd: luaotfload-tool.exe --update + build: false # Not a C# project, build stuff at the test step instead. before_test: From e6595330eefc5a6e9678291d0d3e08ccac0c46c3 Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 14:55:18 +0100 Subject: [PATCH 58/59] Update .appveyor.yml --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 3f7ca701..265394eb 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,7 +33,7 @@ install: # Update some packages to prevent ltluatex bug - cmd: mpm.exe --update=miktex-bin-2.9 - - cmd: mpm.exe --update=ltxbase --update=luatexbase --update=luaotfload + - cmd: mpm.exe --update=ltxbase --update=luatexbase --update=luaotfload --update=miktex-luatex-base # Manually install required texlive packages - cmd: mpm.exe --install-some texlive_packages.txt From aae9922b76d3269d4f451f186646a138aba3e2fc Mon Sep 17 00:00:00 2001 From: oliverpool Date: Mon, 4 Jan 2016 15:05:05 +0100 Subject: [PATCH 59/59] Update .appveyor.yml --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 265394eb..ed692231 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -33,7 +33,7 @@ install: # 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 + - 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