mirror of https://github.com/patacrep/patacrep.git
Engine for LaTeX songbooks
http://www.patacrep.com
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
3.5 KiB
119 lines
3.5 KiB
"""Some utility functions"""
|
|
|
|
from collections import UserDict
|
|
|
|
import yaml
|
|
|
|
from patacrep import encoding, errors, pkg_datapath, Rx
|
|
|
|
class DictOfDict(UserDict):
|
|
"""Dictionary, with a recursive :meth:`update` method.
|
|
|
|
By "recursive", we mean: if `self.update(other)` is called, and for some
|
|
key both `self[key]` and `other[key]` are dictionary, then `self[key]` is
|
|
not replaced by `other[key]`, but instead is updated. This is done
|
|
recursively (that is, `self[foo][bar][baz]` is updated with
|
|
`other[foo][bar][baz]`, if the corresponding objects are dictionaries).
|
|
|
|
>>> ordinal = DictOfDict({
|
|
... "francais": {
|
|
... 1: "premier",
|
|
... 2: "deuxieme",
|
|
... },
|
|
... "english": {
|
|
... 1: "first",
|
|
... },
|
|
... })
|
|
>>> ordinal.update({
|
|
... "francais": {
|
|
... 2: "second",
|
|
... 3: "troisieme",
|
|
... },
|
|
... "espanol": {
|
|
... 1: "primero",
|
|
... },
|
|
... })
|
|
>>> ordinal == {
|
|
... "francais": {
|
|
... 1: "premier",
|
|
... 2: "second",
|
|
... 3: "troisieme",
|
|
... },
|
|
... "english": {
|
|
... 1: "first",
|
|
... },
|
|
... "espanol": {
|
|
... 1: "primero",
|
|
... },
|
|
... }
|
|
True
|
|
"""
|
|
|
|
def update(self, other):
|
|
# pylint: disable=arguments-differ
|
|
self._update(self, other)
|
|
|
|
@staticmethod
|
|
def _update(left, right):
|
|
"""Equivalent to `left.update(right)`, with recursive update."""
|
|
for key in right:
|
|
if key not in left:
|
|
left[key] = right[key]
|
|
elif isinstance(left[key], dict) and isinstance(right[key], dict):
|
|
DictOfDict._update(left[key], right[key])
|
|
else:
|
|
left[key] = right[key]
|
|
|
|
def yesno(string):
|
|
"""Interpret string argument as a boolean.
|
|
|
|
May raise `ValueError` if argument cannot be interpreted.
|
|
"""
|
|
yes_strings = ["y", "yes", "true", "1"]
|
|
no_strings = ["n", "no", "false", "0"]
|
|
if string.lower() in yes_strings:
|
|
return True
|
|
if string.lower() in no_strings:
|
|
return False
|
|
raise ValueError("'{}' is supposed to be one of {}.".format(
|
|
string,
|
|
", ".join(["'{}'".format(string) for string in yes_strings + no_strings]),
|
|
))
|
|
|
|
def remove_keys(data, keys=None, recursive=True):
|
|
"""
|
|
Remove the keys of the dict
|
|
"""
|
|
if isinstance(data, dict):
|
|
for key in keys:
|
|
if key in data:
|
|
del data[key]
|
|
if recursive:
|
|
for key in data:
|
|
data[key] = remove_keys(data[key], keys, True)
|
|
return data
|
|
elif isinstance(data, list) and recursive:
|
|
return [remove_keys(elt, keys, True) for elt in data]
|
|
return data
|
|
|
|
def validate_config_schema(config):
|
|
"""
|
|
Check that the songbook config respects the excepted songbook schema
|
|
|
|
"""
|
|
data = config.copy()
|
|
data = remove_keys(data, ['_cache'])
|
|
|
|
rx_checker = Rx.Factory({"register_core_types": True})
|
|
schema_path = pkg_datapath('templates', 'songbook_schema.yml')
|
|
with encoding.open_read(schema_path) as schema_file:
|
|
schema_struct = yaml.load(schema_file)
|
|
schema_struct = remove_keys(schema_struct, ['_description'])
|
|
schema = rx_checker.make_schema(schema_struct)
|
|
|
|
try:
|
|
schema.validate(data)
|
|
except Rx.SchemaMismatch as exception:
|
|
msg = 'Could not parse songbook file:\n' + str(exception)
|
|
raise errors.SBFileError(msg)
|
|
return True
|
|
|