diff --git a/patacrep/content/__init__.py b/patacrep/content/__init__.py index ab18de6b..237bbb55 100755 --- a/patacrep/content/__init__.py +++ b/patacrep/content/__init__.py @@ -68,7 +68,7 @@ import sys import jinja2 import yaml -from patacrep import files, utils +from patacrep import files, Rx, utils from patacrep.errors import SBFileError, SharedError LOGGER = logging.getLogger(__name__) @@ -223,37 +223,31 @@ def render(context, content): return rendered -def build_plugin_schema(plugins): - """Builds the Rx schema for the ContentItem""" - plugin_schemas = {} - for keyword, parser in plugins.items(): - subschema = getattr(parser, 'rxschema', '//any') - plugin_schemas[keyword] = yaml.load(subschema) - plugin_schema = [{ - 'type': '//rec', - 'optional': plugin_schemas, - }] - song_schema = { - 'type': '//str', - } - plugin_schema.append(song_schema) - return { - 'type': '//any', - 'of': plugin_schema, - } +def validate_parser_argument(raw_schema): + """Check that the parser argument respects the schema + + Will raise `SBFileError` if the schema is not respected. + """ + rx_checker = Rx.Factory({"register_core_types": True}) + schema = rx_checker.make_schema(yaml.load(raw_schema)) + + def wrap(parse): + """Wrap the parse function""" + def wrapped(keyword, argument, config): + """Check the argument schema before calling the plugin parser""" + try: + schema.validate(argument) + except Rx.SchemaMismatch as exception: + msg = 'Invalid `{}` syntax:\n---\n{}---\n{}'.format( + keyword, + yaml.dump({keyword: argument}, default_flow_style=False), + str(exception) + ) + raise SBFileError(msg) + return parse(keyword, argument=argument, config=config) + return wrapped + return wrap -def validate_content(content, plugins): - """Validate the content against the Rx content schema""" - plugin_schema = build_plugin_schema(plugins) - content_schema = { - 'type': '//any', - 'of': [ - plugin_schema, - {'type': '//arr', 'contents':plugin_schema}, - #{'type': '//nil'}, - ] - } - utils.validate_yaml_schema(content, content_schema) def process_content(content, config=None): """Process content, and return a list of ContentItem() objects. @@ -269,19 +263,14 @@ def process_content(content, config=None): contentlist = ContentList() plugins = config.get('_content_plugins', {}) if not content: - content = [{'song': ""}] - try: - validate_content(content, plugins) - except SBFileError as error: - contentlist.append_error(ContentError("Invalid content", str(error))) - return contentlist + content = [{'song': None}] for elem in content: if isinstance(elem, str): elem = {'song': [elem]} if isinstance(elem, dict): for keyword, argument in elem.items(): if keyword not in plugins: - contentlist.append_error(ContentError(keyword, "Unknown content type.")) + contentlist.append_error(ContentError(keyword, "Unknown content keyword.")) continue contentlist.extend(plugins[keyword]( keyword, diff --git a/patacrep/content/cwd.py b/patacrep/content/cwd.py index 9a1f2352..69ca3a16 100755 --- a/patacrep/content/cwd.py +++ b/patacrep/content/cwd.py @@ -1,9 +1,15 @@ """Change base directory before importing songs.""" -from patacrep.content import process_content +from patacrep.content import process_content, validate_parser_argument from patacrep.songs import DataSubpath #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //rec +required: + path: //str + content: //any +""") def parse(keyword, config, argument): """Return a list songs, whith a different base path. @@ -33,11 +39,4 @@ def parse(keyword, config, argument): config['_songdir'] = old_songdir return processed_content -parse.rxschema = """ -type: //rec -required: - path: //str - content: //any -""" - CONTENT_PLUGINS = {'cwd': parse} diff --git a/patacrep/content/include.py b/patacrep/content/include.py index 03e9bd3b..b469d4d5 100644 --- a/patacrep/content/include.py +++ b/patacrep/content/include.py @@ -8,7 +8,7 @@ import json import os import logging -from patacrep.content import process_content, ContentError, ContentList +from patacrep.content import process_content, ContentError, ContentList, validate_parser_argument from patacrep import encoding, errors, files LOGGER = logging.getLogger(__name__) @@ -28,6 +28,13 @@ def load_from_datadirs(path, datadirs): ) #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //any +of: + - type: //str + - type: //arr + contents: //str +""") def parse(keyword, config, argument): """Include an external file content. @@ -69,12 +76,4 @@ def parse(keyword, config, argument): return new_contentlist -parse.rxschema = """ -type: //any -of: - - type: //str - - type: //arr - contents: //str -""" - CONTENT_PLUGINS = {'include': parse} diff --git a/patacrep/content/section.py b/patacrep/content/section.py index 5adf72ed..316b0fa3 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, ContentList +from patacrep.content import ContentItem, ContentList, validate_parser_argument KEYWORDS = [ "part", @@ -29,6 +29,16 @@ class Section(ContentItem): return r'\{}[{}]{{{}}}'.format(self.keyword, self.short, self.name) #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //any +of: + - type: //str + - type: //rec + required: + name: //str + optional: + short: //str +""") def parse(keyword, argument, config): """Parse the section. @@ -47,17 +57,6 @@ def parse(keyword, argument, config): argument = {'name': argument} return ContentList([Section(keyword, **argument)]) -parse.rxschema = """ -type: //any -of: - - type: //str - - type: //rec - required: - name: //str - optional: - short: //str -""" - CONTENT_PLUGINS = dict([ (word, parse) for word diff --git a/patacrep/content/song.py b/patacrep/content/song.py index 020690d1..18680442 100755 --- a/patacrep/content/song.py +++ b/patacrep/content/song.py @@ -7,7 +7,7 @@ import textwrap import jinja2 -from patacrep.content import process_content +from patacrep.content import process_content, validate_parser_argument from patacrep.content import ContentError, ContentItem, ContentList from patacrep import files, errors @@ -58,6 +58,14 @@ class SongRenderer(ContentItem): return self.song.fullpath < other.song.fullpath #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //any +of: + - type: //nil + - type: //str + - type: //arr + contents: //str +""") def parse(keyword, argument, config): """Parse data associated with keyword 'song'. @@ -121,10 +129,6 @@ def parse(keyword, argument, config): )) return sorted(songlist) -parse.rxschema = """ -type: //str -""" - CONTENT_PLUGINS = {'song': parse} diff --git a/patacrep/content/songsection.py b/patacrep/content/songsection.py index a6ccb013..abc9d609 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, ContentList +from patacrep.content import ContentItem, ContentList, validate_parser_argument KEYWORDS = [ "songchapter", @@ -20,6 +20,9 @@ class SongSection(ContentItem): return r'\{}{{{}}}'.format(self.keyword, self.name) #pylint: disable=unused-argument +@validate_parser_argument(""" +//str +""") def parse(keyword, argument, config): """Parse the songsection. @@ -30,10 +33,6 @@ def parse(keyword, argument, config): """ return ContentList([SongSection(keyword, argument)]) -parse.rxschema = """ -//str -""" - CONTENT_PLUGINS = dict([ (keyword, parse) for keyword diff --git a/patacrep/content/sorted.py b/patacrep/content/sorted.py index 32ef89af..277b722d 100755 --- a/patacrep/content/sorted.py +++ b/patacrep/content/sorted.py @@ -9,7 +9,7 @@ import unidecode from patacrep import files from patacrep.content import ContentError, EmptyContentList -from patacrep.content import process_content +from patacrep.content import process_content, validate_parser_argument from patacrep.content.song import OnlySongsError LOGGER = logging.getLogger(__name__) @@ -67,6 +67,20 @@ def key_generator(sort): return ordered_song_keys #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //any +of: + - type: //nil + - type: //rec + optional: + key: + type: //any + of: + - //str + - type: //arr + contents: //str + content: //any +""") def parse(keyword, config, argument): """Return a sorted list of songs. @@ -94,19 +108,4 @@ def parse(keyword, config, argument): ))]) return sorted(songlist, key=key_generator(sort)) -parse.rxschema = """ -type: //any -of: - - type: //nil - - type: //rec - optional: - key: - type: //any - of: - - //str - - type: //arr - contents: //str - content: //any -""" - CONTENT_PLUGINS = {'sorted': parse} diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py index 3e0bf2e9..406084a8 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, ContentError +from patacrep.content import ContentItem, ContentList, ContentError, validate_parser_argument LOGGER = logging.getLogger(__name__) @@ -22,6 +22,13 @@ class LaTeX(ContentItem): ))) #pylint: disable=unused-argument +@validate_parser_argument(""" +type: //any +of: + - type: //arr + contents: //str + - type: //str +""") def parse(keyword, argument, config): """Parse the tex files. @@ -62,12 +69,4 @@ def parse(keyword, argument, config): return filelist -parse.rxschema = """ -type: //any -of: - - type: //arr - contents: //str - - type: //str -""" - CONTENT_PLUGINS = {'tex': parse}