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):