From 7f26070f1b2b0dba98169789c911748c0a20f7ec Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 11:40:27 +0100
Subject: [PATCH 01/48] Use Rx to check songbook schema

---
 patacrep/Rx.py                              | 567 ++++++++++++++++++++
 patacrep/build.py                           |  20 +-
 patacrep/data/templates/songbook_schema.yml |   1 +
 3 files changed, 586 insertions(+), 2 deletions(-)
 create mode 100644 patacrep/Rx.py
 create mode 100644 patacrep/data/templates/songbook_schema.yml

diff --git a/patacrep/Rx.py b/patacrep/Rx.py
new file mode 100644
index 00000000..47219f33
--- /dev/null
+++ b/patacrep/Rx.py
@@ -0,0 +1,567 @@
+# Downloaded from https://github.com/rjbs/rx
+# The contents of the Rx repository are copyright (C) 2008, Ricardo SIGNES.
+# They may be distributed under the terms of the 
+# GNU  Public License (GPL) Version 2, June 1991
+
+#pylint: skip-file
+
+import re
+from six import string_types # for 2-3 compatibility
+import types
+from numbers import Number
+
+
+core_types = [ ]
+
+class SchemaError(Exception):
+  pass
+
+class SchemaMismatch(Exception):
+  pass
+
+class SchemaTypeMismatch(SchemaMismatch):
+  def __init__(self, name, desired_type):
+    SchemaMismatch.__init__(self, '{0} must be {1}'.format(name, desired_type))
+
+class SchemaValueMismatch(SchemaMismatch):
+  def __init__(self, name, value):
+    SchemaMismatch.__init__(self, '{0} must equal {1}'.format(name, value))
+
+class SchemaRangeMismatch(SchemaMismatch):
+  pass
+
+def indent(text, level=1, whitespace='  '):
+    return '\n'.join(whitespace*level+line for line in text.split('\n'))
+
+class Util(object):
+  @staticmethod
+  def make_range_check(opt):
+
+    if not {'min', 'max', 'min-ex', 'max-ex'}.issuperset(opt):
+      raise ValueError("illegal argument to make_range_check")
+    if {'min', 'min-ex'}.issubset(opt):
+      raise ValueError("Cannot define both exclusive and inclusive min")
+    if {'max', 'max-ex'}.issubset(opt):
+      raise ValueError("Cannot define both exclusive and inclusive max")      
+
+    r = opt.copy()
+    inf = float('inf')
+
+    def check_range(value):
+      return(
+        r.get('min',    -inf) <= value and \
+        r.get('max',     inf) >= value and \
+        r.get('min-ex', -inf) <  value and \
+        r.get('max-ex',  inf) >  value
+        )
+
+    return check_range
+
+  @staticmethod
+  def make_range_validator(opt):
+    check_range = Util.make_range_check(opt)
+
+    r = opt.copy()
+    nan = float('nan')
+
+    def validate_range(value, name='value'):
+      if not check_range(value):
+        if r.get('min', nan) == r.get('max', nan):
+          msg = '{0} must equal {1}'.format(name, r['min'])
+          raise SchemaRangeMismatch(msg)
+        
+        range_str = ''
+        if 'min' in r:
+          range_str = '[{0}, '.format(r['min'])
+        elif 'min-ex' in r:
+          range_str = '({0}, '.format(r['min-ex'])
+        else:
+          range_str = '(-inf, '
+
+        if 'max' in r:
+          range_str += '{0}]'.format(r['max'])
+        elif 'max-ex' in r:
+          range_str += '{0})'.format(r['max-ex'])
+        else:
+          range_str += 'inf)'
+
+        raise SchemaRangeMismatch(name+' must be in range '+range_str)
+
+    return validate_range
+
+
+class Factory(object):
+  def __init__(self, register_core_types=True):
+    self.prefix_registry = {
+      '':      'tag:codesimply.com,2008:rx/core/',
+      '.meta': 'tag:codesimply.com,2008:rx/meta/',
+    }
+
+    self.type_registry = {}
+    if register_core_types:
+      for t in core_types: self.register_type(t)
+
+  @staticmethod
+  def _default_prefixes(): pass
+
+  def expand_uri(self, type_name):
+    if re.match('^\w+:', type_name): return type_name
+
+    m = re.match('^/([-._a-z0-9]*)/([-._a-z0-9]+)$', type_name)
+
+    if not m:
+      raise ValueError("couldn't understand type name '{0}'".format(type_name))
+
+    prefix, suffix = m.groups()
+
+    if prefix not in self.prefix_registry:
+      raise KeyError(
+        "unknown prefix '{0}' in type name '{1}'".format(prefix, type_name)
+      )
+
+    return self.prefix_registry[ prefix ] + suffix
+
+  def add_prefix(self, name, base):
+    if self.prefix_registry.get(name):
+      raise SchemaError("the prefix '{0}' is already registered".format(name))
+
+    self.prefix_registry[name] = base;
+
+  def register_type(self, t):
+    t_uri = t.uri()
+
+    if t_uri in self.type_registry:
+      raise ValueError("type already registered for {0}".format(t_uri))
+
+    self.type_registry[t_uri] = t
+
+  def learn_type(self, uri, schema):
+    if self.type_registry.get(uri):
+      raise SchemaError("tried to learn type for already-registered uri {0}".format(uri))
+
+    # make sure schema is valid
+    # should this be in a try/except?
+    self.make_schema(schema)
+
+    self.type_registry[uri] = { 'schema': schema }
+
+  def make_schema(self, schema):
+    if isinstance(schema, string_types):
+      schema = { 'type': schema }
+
+    if not isinstance(schema, dict):
+      raise SchemaError('invalid schema argument to make_schema')
+
+    uri = self.expand_uri(schema['type'])
+
+    if not self.type_registry.get(uri): raise SchemaError("unknown type {0}".format(uri))
+
+    type_class = self.type_registry[uri]
+
+    if isinstance(type_class, dict):
+      if not {'type'}.issuperset(schema):
+        raise SchemaError('composed type does not take check arguments');
+      return self.make_schema(type_class['schema'])
+    else:
+      return type_class(schema, self)
+
+class _CoreType(object):
+  @classmethod
+  def uri(self):
+    return 'tag:codesimply.com,2008:rx/core/' + self.subname()
+
+  def __init__(self, schema, rx):
+    if not {'type'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //{0}'.format(self.subname()))
+
+  def check(self, value):
+    try:
+      self.validate(value)
+    except SchemaMismatch:
+      return False
+    return True
+
+  def validate(self, value, name='value'):
+    raise SchemaMismatch('Tried to validate abstract base schema class')
+
+class AllType(_CoreType):
+  @staticmethod
+  def subname(): return 'all'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'of'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //all')
+    
+    if not(schema.get('of') and len(schema.get('of'))):
+      raise SchemaError('no alternatives given in //all of')
+
+    self.alts = [rx.make_schema(s) for s in schema['of']]
+
+  def validate(self, value, name='value'):
+    error_messages = []
+    for schema in self.alts:
+      try:
+        schema.validate(value, name)
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if len(error_messages) > 1:
+      messages = indent('\n'.join(error_messages))
+      message = '{0} failed to meet all schema requirements:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+    elif len(error_messages) == 1:
+      raise SchemaMismatch(error_messages[0])
+
+class AnyType(_CoreType):
+  @staticmethod
+  def subname(): return 'any'
+
+  def __init__(self, schema, rx):
+    self.alts = None
+
+    if not {'type', 'of'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //any')
+    
+    if 'of' in schema:
+      if not schema['of']: raise SchemaError('no alternatives given in //any of')
+      self.alts = [ rx.make_schema(alt) for alt in schema['of'] ]
+
+  def validate(self, value, name='value'):
+    if self.alts is None:
+      return
+    error_messages = []
+    for schema in self.alts:
+      try:
+        schema.validate(value, name)
+        break
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if len(error_messages) == len(self.alts):
+      messages = indent('\n'.join(error_messages))
+      message = '{0} failed to meet any schema requirements:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+
+class ArrType(_CoreType):
+  @staticmethod
+  def subname(): return 'arr'
+
+  def __init__(self, schema, rx):
+    self.length = None
+
+    if not {'type', 'contents', 'length'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //arr')
+
+    if not schema.get('contents'):
+      raise SchemaError('no contents provided for //arr')
+
+    self.content_schema = rx.make_schema(schema['contents'])
+
+    if schema.get('length'):
+      self.length = Util.make_range_validator(schema['length'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, (list, tuple)):
+      raise SchemaTypeMismatch(name, 'array')
+
+    if self.length:
+      self.length(len(value), name+' length')
+
+    error_messages = []
+
+    for i, item in enumerate(value):
+      try:
+        self.content_schema.validate(item, 'item '+str(i))
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if len(error_messages) > 1:
+      messages = indent('\n'.join(error_messages))
+      message = '{0} sequence contains invalid elements:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+    elif len(error_messages) == 1:
+      raise SchemaMismatch(name+': '+error_messages[0])
+
+class BoolType(_CoreType):
+  @staticmethod
+  def subname(): return 'bool'
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, bool):
+      raise SchemaTypeMismatch(name, 'boolean')
+
+class DefType(_CoreType):
+  @staticmethod
+  def subname(): return 'def'
+
+
+  def validate(self, value, name='value'):
+    if value is None:
+      raise SchemaMismatch(name+' must be non-null')
+
+class FailType(_CoreType):
+  @staticmethod
+  def subname(): return 'fail'
+
+  def check(self, value): return False
+
+  def validate(self, value, name='value'):
+    raise SchemaMismatch(name+' is of fail type, automatically invalid.')
+
+class IntType(_CoreType):
+  @staticmethod
+  def subname(): return 'int'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'range', 'value'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //int')
+
+    self.value = None
+    if 'value' in schema:
+      if not isinstance(schema['value'], Number) or schema['value'] % 1 != 0:
+        raise SchemaError('invalid value parameter for //int')
+      self.value = schema['value']
+
+    self.range = None
+    if 'range' in schema:
+      self.range = Util.make_range_validator(schema['range'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, Number) or isinstance(value, bool) or value%1:
+      raise SchemaTypeMismatch(name,'integer')
+
+    if self.range:
+      self.range(value, name)
+
+    if self.value is not None and value != self.value:
+      raise SchemaValueMismatch(name, self.value)
+
+class MapType(_CoreType):
+  @staticmethod
+  def subname(): return 'map'
+
+  def __init__(self, schema, rx):
+    self.allowed = set()
+
+    if not {'type', 'values'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //map')
+
+    if not schema.get('values'):
+      raise SchemaError('no values given for //map')
+
+    self.value_schema = rx.make_schema(schema['values'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, dict):
+      raise SchemaTypeMismatch(name, 'map')
+
+    error_messages = []
+
+    for key, val in value.items():
+      try:
+        self.value_schema.validate(val, key)
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if len(error_messages) > 1:
+      messages = indent('\n'.join(error_messages))
+      message = '{0} map contains invalid entries:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+    elif len(error_messages) == 1:
+      raise SchemaMismatch(name+': '+error_messages[0])
+
+class NilType(_CoreType):
+  @staticmethod
+  def subname(): return 'nil'
+
+  def check(self, value): return value is None
+
+  def validate(self, value, name='value'):
+    if value is not None:
+      raise SchemaTypeMismatch(name, 'null')
+
+class NumType(_CoreType):
+  @staticmethod
+  def subname(): return 'num'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'range', 'value'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //num')
+
+    self.value = None
+    if 'value' in schema:
+      if not isinstance(schema['value'], Number):
+        raise SchemaError('invalid value parameter for //num')
+      self.value = schema['value']
+
+    self.range = None
+
+    if schema.get('range'):
+      self.range = Util.make_range_validator(schema['range'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, Number) or isinstance(value, bool):
+      raise SchemaTypeMismatch(name, 'number')
+
+    if self.range:
+      self.range(value, name)
+
+    if self.value is not None and value != self.value:
+      raise SchemaValueMismatch(name, self.value)
+
+class OneType(_CoreType):
+  @staticmethod
+  def subname(): return 'one'
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, (Number, string_types)):
+      raise SchemaTypeMismatch(name, 'number or string')
+
+class RecType(_CoreType):
+  @staticmethod
+  def subname(): return 'rec'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'rest', 'required', 'optional'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //rec')
+
+    self.known = set()
+    self.rest_schema = None
+    if schema.get('rest'): self.rest_schema = rx.make_schema(schema['rest'])
+
+    for which in ('required', 'optional'):
+      setattr(self, which, {})
+      for field in schema.get(which, {}).keys():
+        if field in self.known:
+          raise SchemaError('%s appears in both required and optional' % field)
+
+        self.known.add(field)
+
+        self.__getattribute__(which)[field] = rx.make_schema(
+          schema[which][field]
+        )
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, dict):
+      raise SchemaTypeMismatch(name, 'record')
+
+    unknown = [k for k in value.keys() if k not in self.known]
+
+    if unknown and not self.rest_schema:
+      fields = indent('\n'.join(unknown))
+      raise SchemaMismatch(name+' contains unknown fields:\n'+fields)
+
+    error_messages = []
+
+    for field in self.required:
+      try:
+        if field not in value:
+          raise SchemaMismatch('missing required field: '+field)
+        self.required[field].validate(value[field], field)
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    for field in self.optional:
+      if field not in value: continue
+      try:
+        self.optional[field].validate(value[field], field) 
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if unknown:
+      rest = {key: value[key] for key in unknown}
+      try:
+        self.rest_schema.validate(rest, name)
+      except SchemaMismatch as e:
+        error_messages.append(str(e))
+
+    if len(error_messages) > 1:
+      messages = indent('\n'.join(error_messages))
+      message = '{0} record is invalid:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+    elif len(error_messages) == 1:
+      raise SchemaMismatch(name+': '+error_messages[0])
+
+
+class SeqType(_CoreType):
+  @staticmethod
+  def subname(): return 'seq'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'contents', 'tail'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //seq')
+
+    if not schema.get('contents'):
+      raise SchemaError('no contents provided for //seq')
+
+    self.content_schema = [ rx.make_schema(s) for s in schema['contents'] ]
+
+    self.tail_schema = None
+    if (schema.get('tail')):
+      self.tail_schema = rx.make_schema(schema['tail'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, (list, tuple)):
+      raise SchemaTypeMismatch(name, 'sequence')
+
+    if len(value) < len(self.content_schema):
+      raise SchemaMismatch(name+' is less than expected length')
+
+    if len(value) > len(self.content_schema) and not self.tail_schema:
+      raise SchemaMismatch(name+' exceeds expected length')
+
+    error_messages = []
+
+    for i, (schema, item) in enumerate(zip(self.content_schema, value)):
+      try:
+        schema.validate(item, 'item '+str(i))
+      except SchemaMismatch as e:
+        error_messages.append(str(e))   
+
+    if len(error_messages) > 1:
+      messages = indent('\n'.join(error_messages))
+      message = '{0} sequence is invalid:\n{1}'
+      message = message.format(name, messages)
+      raise SchemaMismatch(message)
+    elif len(error_messages) == 1:
+      raise SchemaMismatch(name+': '+error_messages[0])
+
+    if len(value) > len(self.content_schema):
+      self.tail_schema.validate(value[len(self.content_schema):], name)
+
+class StrType(_CoreType):
+  @staticmethod
+  def subname(): return 'str'
+
+  def __init__(self, schema, rx):
+    if not {'type', 'value', 'length'}.issuperset(schema):
+      raise SchemaError('unknown parameter for //str')
+
+    self.value = None
+    if 'value' in schema:
+      if not isinstance(schema['value'], string_types):
+        raise SchemaError('invalid value parameter for //str')
+      self.value = schema['value']
+
+    self.length = None
+    if 'length' in schema:
+      self.length = Util.make_range_validator(schema['length'])
+
+  def validate(self, value, name='value'):
+    if not isinstance(value, string_types):
+      raise SchemaTypeMismatch(name, 'string')
+    if self.value is not None and value != self.value:
+      raise SchemaValueMismatch(name, '"{0}"'.format(self.value))
+    if self.length:
+      self.length(len(value), name+' length')
+
+core_types = [
+  AllType,  AnyType, ArrType, BoolType, DefType,
+  FailType, IntType, MapType, NilType,  NumType,
+  OneType,  RecType, SeqType, StrType
+]
diff --git a/patacrep/build.py b/patacrep/build.py
index e277067c..ead25cbd 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -8,7 +8,9 @@ import threading
 import os.path
 from subprocess import Popen, PIPE, call, check_call
 
-from patacrep import authors, content, errors, files
+import yaml
+
+from patacrep import authors, content, errors, encoding, files, pkg_datapath, Rx
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
@@ -41,7 +43,7 @@ class Songbook(object):
 
     def __init__(self, raw_songbook, basename):
         super().__init__()
-        self.config = raw_songbook
+        self.config = check_config_schema(raw_songbook)
         self.basename = basename
         # Some special keys have their value processed.
         self._set_datadir()
@@ -326,3 +328,17 @@ class SongbookBuilder(object):
                     os.unlink(self.basename + ext)
                 except Exception as exception:
                     raise errors.CleaningError(self.basename + ext, exception)
+
+
+def check_config_schema(data):
+    """
+    Check that the data respect the excepted songbook schema
+
+    """
+    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 = rx_checker.make_schema(yaml.load(schema_file))
+    if schema.check(data):
+        return data
+    raise errors.SBFileError('The songbook file does not respect the schema')
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
new file mode 100644
index 00000000..e335e82d
--- /dev/null
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -0,0 +1 @@
+type: //any
\ No newline at end of file

From 20012b4c29076a25ed98301b72fc58656c17bc36 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 12:18:52 +0100
Subject: [PATCH 02/48] Remove _default and _description keys from the schema
 before using it

---
 patacrep/build.py                           |  7 +++++--
 patacrep/data/templates/songbook_schema.yml |  3 ++-
 patacrep/utils.py                           | 16 ++++++++++++++++
 3 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index ead25cbd..60480ff1 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -10,7 +10,7 @@ from subprocess import Popen, PIPE, call, check_call
 
 import yaml
 
-from patacrep import authors, content, errors, encoding, files, pkg_datapath, Rx
+from patacrep import authors, content, errors, encoding, files, pkg_datapath, Rx, utils
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
@@ -338,7 +338,10 @@ def check_config_schema(data):
     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 = rx_checker.make_schema(yaml.load(schema_file))
+        schema_struct = yaml.load(schema_file)
+    schema_struct = utils.remove_keys(schema_struct, ['_default', '_description'])
+    schema = rx_checker.make_schema(schema_struct)
+
     if schema.check(data):
         return data
     raise errors.SBFileError('The songbook file does not respect the schema')
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index e335e82d..c41d2caa 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -1 +1,2 @@
-type: //any
\ No newline at end of file
+type: //any
+_default: "test"
\ No newline at end of file
diff --git a/patacrep/utils.py b/patacrep/utils.py
index 998fe738..d7a4142d 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -75,3 +75,19 @@ def yesno(string):
         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

From 8e872dba419b50a5e5f9305a8f47b325c74821cd Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 12:21:42 +0100
Subject: [PATCH 03/48] Remove python 2 compatibility

---
 patacrep/Rx.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/patacrep/Rx.py b/patacrep/Rx.py
index 47219f33..677a3461 100644
--- a/patacrep/Rx.py
+++ b/patacrep/Rx.py
@@ -6,7 +6,6 @@
 #pylint: skip-file
 
 import re
-from six import string_types # for 2-3 compatibility
 import types
 from numbers import Number
 
@@ -146,7 +145,7 @@ class Factory(object):
     self.type_registry[uri] = { 'schema': schema }
 
   def make_schema(self, schema):
-    if isinstance(schema, string_types):
+    if isinstance(schema, str):
       schema = { 'type': schema }
 
     if not isinstance(schema, dict):
@@ -418,7 +417,7 @@ class OneType(_CoreType):
   def subname(): return 'one'
 
   def validate(self, value, name='value'):
-    if not isinstance(value, (Number, string_types)):
+    if not isinstance(value, (Number, str)):
       raise SchemaTypeMismatch(name, 'number or string')
 
 class RecType(_CoreType):
@@ -544,7 +543,7 @@ class StrType(_CoreType):
 
     self.value = None
     if 'value' in schema:
-      if not isinstance(schema['value'], string_types):
+      if not isinstance(schema['value'], str):
         raise SchemaError('invalid value parameter for //str')
       self.value = schema['value']
 
@@ -553,7 +552,7 @@ class StrType(_CoreType):
       self.length = Util.make_range_validator(schema['length'])
 
   def validate(self, value, name='value'):
-    if not isinstance(value, string_types):
+    if not isinstance(value, str):
       raise SchemaTypeMismatch(name, 'string')
     if self.value is not None and value != self.value:
       raise SchemaValueMismatch(name, '"{0}"'.format(self.value))

From 02551f16d085f9643ece0d2817e2e98f601a5ba1 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 15:29:18 +0100
Subject: [PATCH 04/48] Complete schema

---
 examples/example-all.yaml.sb                  | 36 +++++---
 patacrep/build.py                             | 22 +----
 .../data/templates/default_songbook.sb.yml    | 46 ++++++++++
 patacrep/data/templates/songbook_schema.yml   | 91 ++++++++++++++++++-
 patacrep/utils.py                             | 26 ++++++
 test/test_songbook/datadir.sb                 | 12 +--
 6 files changed, 191 insertions(+), 42 deletions(-)
 create mode 100644 patacrep/data/templates/default_songbook.sb.yml

diff --git a/examples/example-all.yaml.sb b/examples/example-all.yaml.sb
index 3a167565..4f287c6d 100644
--- a/examples/example-all.yaml.sb
+++ b/examples/example-all.yaml.sb
@@ -1,17 +1,23 @@
-bookoptions: 
-  - "diagram"
-  - "repeatchords"
-  - "lilypond"
-  - "pictures"
-booktype: "chorded"
-datadir: "."
-template: "patacrep.tex"
-lang: "fr"
-encoding: "utf8"
-authwords: 
-  sep: 
+# bookoptions: 
+#   - "diagram"
+#   - "repeatchords"
+#   - "pictures"
+# booktype: "chorded"
+# datadir: "."
+# template: "patacrep.tex"
+# lang: "fr"
+# encoding: "utf8"
+book:
+  lang: fr
+  datadir: "."
+  pictures: yes
+  #type: chorded
+chords:
+  show: true
+  diagramreminder: none
+
+authors:
+  separators:
     - "and"
     - "et"
-content: 
-  - 
-    - "sorted"
\ No newline at end of file
+content: '[["sorted"]]'
\ No newline at end of file
diff --git a/patacrep/build.py b/patacrep/build.py
index 60480ff1..065c8604 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -10,7 +10,7 @@ from subprocess import Popen, PIPE, call, check_call
 
 import yaml
 
-from patacrep import authors, content, errors, encoding, files, pkg_datapath, Rx, utils
+from patacrep import authors, content, errors, encoding, files, utils
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
@@ -43,7 +43,8 @@ class Songbook(object):
 
     def __init__(self, raw_songbook, basename):
         super().__init__()
-        self.config = check_config_schema(raw_songbook)
+        self.config = raw_songbook
+        utils.validate_config_schema(raw_songbook)
         self.basename = basename
         # Some special keys have their value processed.
         self._set_datadir()
@@ -328,20 +329,3 @@ class SongbookBuilder(object):
                     os.unlink(self.basename + ext)
                 except Exception as exception:
                     raise errors.CleaningError(self.basename + ext, exception)
-
-
-def check_config_schema(data):
-    """
-    Check that the data respect the excepted songbook schema
-
-    """
-    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 = utils.remove_keys(schema_struct, ['_default', '_description'])
-    schema = rx_checker.make_schema(schema_struct)
-
-    if schema.check(data):
-        return data
-    raise errors.SBFileError('The songbook file does not respect the schema')
diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
new file mode 100644
index 00000000..54a8291d
--- /dev/null
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -0,0 +1,46 @@
+content:
+
+book: 
+  lang: en
+  datadir: 
+  encoding: utf-8
+  pictures: yes
+
+chords: # Options relatives aux accords
+  diagramreminder: ENUM[important, none, all] (anciennement importantdiagramonly)
+  diagrampage: BOOLEAN Montrer la page d'accords
+  repeatchords: BOOLEAN
+  lilypond: BOOLEAN
+  instruments: ENUM[guitar, ukulele]
+  show: BOOLEAN (anciennement booktype=chorded ou booktype=lyrics)
+  notation: ENUM[alphascale, solfedge]
+
+authors: # Comment sont analysés les auteurs
+  separators: LISTE
+  ignore: LISTE
+  by: LISTE
+
+titles: # Comment sont analysés les titres
+  prefix: LISTE
+
+template: # Des choses spécifiques au template
+  file: STRING
+  latex: # Des choses spécifiques au LaTeX
+    classoptions: STRING
+
+  # Peut dépendre fortement du template
+  color: # Des couleurs
+    songnumber: STRING
+    notebg: STRING
+    indexbg: STRING
+
+  titlepage: #Configuration de la page de garde
+    title: STRING
+    author: STRING
+    subtitle: STRING
+    version: STRING
+    url: STRING
+    email: STRING
+    picture: STRING
+    picturecopyright: STRING
+    footer: STRING
\ No newline at end of file
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index c41d2caa..6bbb7ba3 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -1,2 +1,89 @@
-type: //any
-_default: "test"
\ No newline at end of file
+type: //rec
+optional:
+  content: //str
+  book:
+    type: //rec
+    optional:
+      encoding: //str
+      lang: //str
+      pictures: //bool
+      datadir:
+        type: //arr
+        contents: //str
+  chords:
+    type: //rec
+    optional:
+      show: //bool
+      diagrampage: //bool
+      repeatchords: //bool
+      lilypond: //bool
+      diagramreminder:
+        type: //any
+        of:
+          - type: //str
+            value: "none"
+          - type: //str
+            value: "important"
+          - type: //str
+            value: "all"
+      instruments:
+        type: //any
+        of:
+          - type: //str
+            value: "guitar"
+          - type: //str
+            value: "ukulele"
+      notation:
+        type: //any
+        of:
+          - type: //str
+            value: "alphascale"
+          - type: //str
+            value: "solfedge"
+  authors:
+    _description: "Comment sont analysés les auteurs"
+    type: //rec
+    optional:
+      separators:
+        type: //arr
+        contents: //str
+      ignore:
+        type: //arr
+        contents: //str
+      by:
+        type: //arr
+        contents: //str
+  titles:
+    _description: "Comment sont analysés les titres"
+    type: //rec
+    optional:
+      prefix:
+        type: //arr
+        contents: //str
+  template:
+    _description: "Des choses spécifiques au template"
+    type: //rec
+    optional:
+      file: //str
+      latex:
+        type: //rec
+        optional:
+          classoptions: //str
+      color:
+        type: //rec
+        optional:
+          songnumber: //str
+          notebg: //str
+          indexbg: //str
+      titlepage:
+        type: //rec
+        optional:
+          title: //str
+          author: //str
+          subtitle: //str
+          version: //str
+          url: //str
+          email: //str
+          picture: //str
+          picturecopyright: //str
+          footer: //str
diff --git a/patacrep/utils.py b/patacrep/utils.py
index d7a4142d..54855b9f 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -2,6 +2,10 @@
 
 from collections import UserDict
 
+import yaml
+
+from patacrep import encoding, errors, pkg_datapath, Rx
+
 class DictOfDict(UserDict):
     """Dictionary, with a recursive :meth:`update` method.
 
@@ -91,3 +95,25 @@ def remove_keys(data, keys=None, recursive=True):
     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
diff --git a/test/test_songbook/datadir.sb b/test/test_songbook/datadir.sb
index f44c3dba..0763bc7a 100644
--- a/test/test_songbook/datadir.sb
+++ b/test/test_songbook/datadir.sb
@@ -1,6 +1,6 @@
-bookoptions:
-- pictures
-datadir:
-- datadir_datadir
-- datadir_datadir2
-lang: en
+book:
+  pictures: yes
+  datadir:
+    - datadir_datadir
+    - datadir_datadir2
+  lang: en

From 348345f220c9886dff3d71943842bb711f172a22 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 15:53:00 +0100
Subject: [PATCH 05/48] Add a default songbook configuration

---
 .../data/templates/default_songbook.sb.yml    | 67 ++++++++++---------
 patacrep/data/templates/songbook_schema.yml   | 46 ++++++++-----
 patacrep/songbook/__main__.py                 | 32 ++++++---
 3 files changed, 87 insertions(+), 58 deletions(-)

diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index 54a8291d..70b44063 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -1,46 +1,51 @@
-content:
-
 book: 
   lang: en
-  datadir: 
   encoding: utf-8
   pictures: yes
 
 chords: # Options relatives aux accords
-  diagramreminder: ENUM[important, none, all] (anciennement importantdiagramonly)
-  diagrampage: BOOLEAN Montrer la page d'accords
-  repeatchords: BOOLEAN
-  lilypond: BOOLEAN
-  instruments: ENUM[guitar, ukulele]
-  show: BOOLEAN (anciennement booktype=chorded ou booktype=lyrics)
-  notation: ENUM[alphascale, solfedge]
+  show: yes
+  diagramreminder: all
+  diagrampage: yes
+  repeatchords: yes
+  lilypond: yes
+  instruments: guitar
+  notation: alphascale
 
 authors: # Comment sont analysés les auteurs
-  separators: LISTE
-  ignore: LISTE
-  by: LISTE
+  separators:
+  - To
+  - Do
+  ignore:
+  - To
+  - Do
+  by:
+  - To
+  - Do
 
 titles: # Comment sont analysés les titres
-  prefix: LISTE
+  prefix:
+  - To
+  - Do
 
 template: # Des choses spécifiques au template
   file: STRING
-  latex: # Des choses spécifiques au LaTeX
-    classoptions: STRING
+  # latex: # Des choses spécifiques au LaTeX
+  #   classoptions: STRING
 
-  # Peut dépendre fortement du template
-  color: # Des couleurs
-    songnumber: STRING
-    notebg: STRING
-    indexbg: STRING
+  # # Peut dépendre fortement du template
+  # color: # Des couleurs
+  #   songnumber: STRING
+  #   notebg: STRING
+  #   indexbg: STRING
 
-  titlepage: #Configuration de la page de garde
-    title: STRING
-    author: STRING
-    subtitle: STRING
-    version: STRING
-    url: STRING
-    email: STRING
-    picture: STRING
-    picturecopyright: STRING
-    footer: STRING
\ No newline at end of file
+  # titlepage: #Configuration de la page de garde
+  #   title: STRING
+  #   author: STRING
+  #   subtitle: STRING
+  #   version: STRING
+  #   url: STRING
+  #   email: STRING
+  #   picture: STRING
+  #   picturecopyright: STRING
+  #   footer: STRING
\ No newline at end of file
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index 6bbb7ba3..4f47b185 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -1,9 +1,10 @@
 type: //rec
 optional:
   content: //str
+required:
   book:
     type: //rec
-    optional:
+    required:
       encoding: //str
       lang: //str
       pictures: //bool
@@ -12,7 +13,7 @@ optional:
         contents: //str
   chords:
     type: //rec
-    optional:
+    required:
       show: //bool
       diagrampage: //bool
       repeatchords: //bool
@@ -43,41 +44,54 @@ optional:
   authors:
     _description: "Comment sont analysés les auteurs"
     type: //rec
-    optional:
+    required:
       separators:
-        type: //arr
-        contents: //str
+        type: //any
+        of:
+          - type: //arr
+            contents: //str
+          - type: //nil
       ignore:
-        type: //arr
-        contents: //str
+        type: //any
+        of:
+          - type: //arr
+            contents: //str
+          - type: //nil
       by:
-        type: //arr
-        contents: //str
+        type: //any
+        of:
+          - type: //arr
+            contents: //str
+          - type: //nil
   titles:
     _description: "Comment sont analysés les titres"
     type: //rec
-    optional:
+    required:
       prefix:
-        type: //arr
-        contents: //str
+        type: //any
+        of:
+          - type: //arr
+            contents: //str
+          - type: //nil
   template:
     _description: "Des choses spécifiques au template"
     type: //rec
-    optional:
+    required:
       file: //str
+    optional:
       latex:
         type: //rec
-        optional:
+        required:
           classoptions: //str
       color:
         type: //rec
-        optional:
+        required:
           songnumber: //str
           notebg: //str
           indexbg: //str
       titlepage:
         type: //rec
-        optional:
+        required:
           title: //str
           author: //str
           subtitle: //str
diff --git a/patacrep/songbook/__main__.py b/patacrep/songbook/__main__.py
index b62dcf73..22e3ce66 100644
--- a/patacrep/songbook/__main__.py
+++ b/patacrep/songbook/__main__.py
@@ -9,9 +9,9 @@ import sys
 import yaml
 
 from patacrep.build import SongbookBuilder, DEFAULT_STEPS
-from patacrep.utils import yesno
+from patacrep.utils import yesno, DictOfDict
 from patacrep import __version__
-from patacrep import errors
+from patacrep import errors, pkg_datapath
 import patacrep.encoding
 
 # Logging configuration
@@ -133,39 +133,49 @@ def main():
 
     basename = os.path.basename(songbook_path)[:-3]
 
+    # Load the default songbook config
+    default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
+    with patacrep.encoding.open_read(default_songbook_path) as default_songbook_file:
+        default_songbook = DictOfDict(yaml.load(default_songbook_file))
+
+    # Load the user songbook config
     try:
         with patacrep.encoding.open_read(songbook_path) as songbook_file:
-            songbook = yaml.load(songbook_file)
-        if 'encoding' in songbook:
+            user_songbook = yaml.load(songbook_file)
+        if 'encoding' in user_songbook:
             with patacrep.encoding.open_read(
                 songbook_path,
-                encoding=songbook['encoding']
+                encoding=user_songbook['encoding']
                 ) as songbook_file:
-                songbook = yaml.load(songbook_file)
+                user_songbook = yaml.load(songbook_file)
     except Exception as error: # pylint: disable=broad-except
         LOGGER.error(error)
         LOGGER.error("Error while loading file '{}'.".format(songbook_path))
         sys.exit(1)
 
+    # Merge the default and user configs
+    default_songbook.update(user_songbook)
+    songbook = dict(default_songbook)
+
     # Gathering datadirs
     datadirs = []
     if options.datadir:
         # Command line options
         datadirs += [item[0] for item in options.datadir]
-    if 'datadir' in songbook:
-        if isinstance(songbook['datadir'], str):
-            songbook['datadir'] = [songbook['datadir']]
+    if 'book' in songbook and 'datadir' in songbook['book']:
+        if isinstance(songbook['book']['datadir'], str):
+            songbook['book']['datadir'] = [songbook['book']['datadir']]
         datadirs += [
             os.path.join(
                 os.path.dirname(os.path.abspath(songbook_path)),
                 path
                 )
-            for path in songbook['datadir']
+            for path in songbook['book']['datadir']
             ]
     # Default value
     datadirs.append(os.path.dirname(os.path.abspath(songbook_path)))
 
-    songbook['datadir'] = datadirs
+    songbook['book']['datadir'] = datadirs
     songbook['_cache'] = options.cache[0]
 
     try:

From fab3d4f12808066512dd3462545b83963bc81754 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 16:20:45 +0100
Subject: [PATCH 06/48] Rename datadir key to _datadir

---
 patacrep/build.py                             | 26 +++-----
 patacrep/content/include.py                   |  2 +-
 patacrep/content/tex.py                       |  4 +-
 .../data/templates/default_songbook.sb.yml    |  4 +-
 patacrep/data/templates/songbook_schema.yml   | 64 ++++++++++---------
 patacrep/songbook/__main__.py                 |  3 +-
 patacrep/songs/__init__.py                    | 19 ++----
 patacrep/songs/convert/__main__.py            |  1 +
 patacrep/utils.py                             |  1 -
 9 files changed, 57 insertions(+), 67 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 065c8604..114f121a 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -51,14 +51,8 @@ class Songbook(object):
 
     def _set_datadir(self):
         """Set the default values for datadir"""
-        try:
-            if isinstance(self.config['datadir'], str):
-                self.config['datadir'] = [self.config['datadir']]
-        except KeyError:  # No datadir in the raw_songbook
-            self.config['datadir'] = [os.path.abspath('.')]
-
         abs_datadir = []
-        for path in self.config['datadir']:
+        for path in self.config['_datadir']:
             if os.path.exists(path) and os.path.isdir(path):
                 abs_datadir.append(os.path.abspath(path))
             else:
@@ -66,10 +60,10 @@ class Songbook(object):
                     "Ignoring non-existent datadir '{}'.".format(path)
                     )
 
-        self.config['datadir'] = abs_datadir
+        self.config['_datadir'] = abs_datadir
         self.config['_songdir'] = [
             DataSubpath(path, 'songs')
-            for path in self.config['datadir']
+            for path in self.config['_datadir']
             ]
 
     def write_tex(self, output):
@@ -82,26 +76,26 @@ class Songbook(object):
         config = DEFAULT_CONFIG.copy()
         config.update(self.config)
         renderer = TexBookRenderer(
-            config['template'],
-            config['datadir'],
-            config['lang'],
-            config['encoding'],
+            config['book']['template'],
+            config['_datadir'],
+            config['book']['lang'],
+            config['book']['encoding'],
             )
         config.update(renderer.get_variables())
         config.update(self.config)
 
         config['_compiled_authwords'] = authors.compile_authwords(
-            copy.deepcopy(config['authwords'])
+            copy.deepcopy(config['authors'])
             )
 
         # Loading custom plugins
         config['_content_plugins'] = files.load_plugins(
-            datadirs=config.get('datadir', []),
+            datadirs=config['_datadir'],
             root_modules=['content'],
             keyword='CONTENT_PLUGINS',
             )
         config['_song_plugins'] = files.load_plugins(
-            datadirs=config.get('datadir', []),
+            datadirs=config['_datadir'],
             root_modules=['songs'],
             keyword='SONG_RENDERERS',
             )['tsg']
diff --git a/patacrep/content/include.py b/patacrep/content/include.py
index 30c99238..2948eb47 100644
--- a/patacrep/content/include.py
+++ b/patacrep/content/include.py
@@ -41,7 +41,7 @@ def parse(keyword, config, argument, contentlist):
     new_contentlist = []
 
     for path in contentlist:
-        filepath = load_from_datadirs(path, config.get('datadir', []))
+        filepath = load_from_datadirs(path, config['_datadir'])
         content_file = None
         try:
             with encoding.open_read(
diff --git a/patacrep/content/tex.py b/patacrep/content/tex.py
index 0f520c19..651eeba2 100755
--- a/patacrep/content/tex.py
+++ b/patacrep/content/tex.py
@@ -38,8 +38,8 @@ def parse(keyword, argument, contentlist, config):
     filelist = []
     basefolders = itertools.chain(
         (path.fullpath for path in config['_songdir']),
-        files.iter_datadirs(config['datadir']),
-        files.iter_datadirs(config['datadir'], 'latex'),
+        files.iter_datadirs(config['_datadir']),
+        files.iter_datadirs(config['_datadir'], 'latex'),
         )
     for filename in contentlist:
         checked_file = None
diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index 70b44063..491eaa79 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -2,6 +2,7 @@ book:
   lang: en
   encoding: utf-8
   pictures: yes
+  template: default.tex
 
 chords: # Options relatives aux accords
   show: yes
@@ -28,8 +29,7 @@ titles: # Comment sont analysés les titres
   - To
   - Do
 
-template: # Des choses spécifiques au template
-  file: STRING
+#template: # Des choses spécifiques au template
   # latex: # Des choses spécifiques au LaTeX
   #   classoptions: STRING
 
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index 4f47b185..8a5a20cd 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -2,15 +2,17 @@ type: //rec
 optional:
   content: //str
 required:
+  _cache: //bool
+  _datadir:
+    type: //arr
+    contents: //str
   book:
     type: //rec
     required:
       encoding: //str
       lang: //str
       pictures: //bool
-      datadir:
-        type: //arr
-        contents: //str
+      template: //str
   chords:
     type: //rec
     required:
@@ -73,31 +75,31 @@ required:
           - type: //arr
             contents: //str
           - type: //nil
-  template:
-    _description: "Des choses spécifiques au template"
-    type: //rec
-    required:
-      file: //str
-    optional:
-      latex:
-        type: //rec
-        required:
-          classoptions: //str
-      color:
-        type: //rec
-        required:
-          songnumber: //str
-          notebg: //str
-          indexbg: //str
-      titlepage:
-        type: //rec
-        required:
-          title: //str
-          author: //str
-          subtitle: //str
-          version: //str
-          url: //str
-          email: //str
-          picture: //str
-          picturecopyright: //str
-          footer: //str
+  # template:
+  #   _description: "Des choses spécifiques au template"
+  #   type: //rec
+  #   required:
+  #     file: //str
+  #   optional:
+  #     latex:
+  #       type: //rec
+  #       required:
+  #         classoptions: //str
+  #     color:
+  #       type: //rec
+  #       required:
+  #         songnumber: //str
+  #         notebg: //str
+  #         indexbg: //str
+  #     titlepage:
+  #       type: //rec
+  #       required:
+  #         title: //str
+  #         author: //str
+  #         subtitle: //str
+  #         version: //str
+  #         url: //str
+  #         email: //str
+  #         picture: //str
+  #         picturecopyright: //str
+  #         footer: //str
diff --git a/patacrep/songbook/__main__.py b/patacrep/songbook/__main__.py
index 22e3ce66..9daff597 100644
--- a/patacrep/songbook/__main__.py
+++ b/patacrep/songbook/__main__.py
@@ -175,7 +175,8 @@ def main():
     # Default value
     datadirs.append(os.path.dirname(os.path.abspath(songbook_path)))
 
-    songbook['book']['datadir'] = datadirs
+    del songbook['book']['datadir']
+    songbook['_datadir'] = datadirs
     songbook['_cache'] = options.cache[0]
 
     try:
diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py
index d7619096..23c7900f 100644
--- a/patacrep/songs/__init__.py
+++ b/patacrep/songs/__init__.py
@@ -12,14 +12,7 @@ from patacrep.authors import process_listauthors
 
 LOGGER = logging.getLogger(__name__)
 
-DEFAULT_CONFIG = {
-    'template': "default.tex",
-    'lang': 'en',
-    'content': [],
-    'titleprefixwords': [],
-    'encoding': None,
-    'datadir': [],
-    }
+DEFAULT_CONFIG = {}
 
 def cached_name(datadir, filename):
     """Return the filename of the cache version of the file."""
@@ -117,8 +110,8 @@ class Song:
         self.fullpath = os.path.join(self.datadir, subpath)
         self.subpath = subpath
         self._filehash = None
-        self.encoding = config["encoding"]
-        self.default_lang = config["lang"]
+        self.encoding = config['book']["encoding"]
+        self.default_lang = config['book']["lang"]
         self.config = config
 
         if self._cache_retrieved():
@@ -135,7 +128,7 @@ class Song:
         self.unprefixed_titles = [
             unprefixed_title(
                 title,
-                config['titleprefixwords']
+                config['titles']['prefix']
                 )
             for title
             in self.titles
@@ -221,7 +214,7 @@ class Song:
     def iter_datadirs(self, *subpath):
         """Return an iterator of existing datadirs (with an optionnal subpath)
         """
-        yield from files.iter_datadirs(self.config['datadir'], *subpath)
+        yield from files.iter_datadirs(self.config['_datadir'], *subpath)
 
     def search_datadir_file(self, filename, extensions=None, directories=None):
         """Search for a file name.
@@ -249,7 +242,7 @@ class Song:
         if extensions is None:
             extensions = ['']
         if directories is None:
-            directories = self.config['datadir']
+            directories = self.config['_datadir']
 
         songdir = os.path.dirname(self.fullpath)
         for extension in extensions:
diff --git a/patacrep/songs/convert/__main__.py b/patacrep/songs/convert/__main__.py
index 19d7041e..1c2e8cfb 100644
--- a/patacrep/songs/convert/__main__.py
+++ b/patacrep/songs/convert/__main__.py
@@ -33,6 +33,7 @@ if __name__ == "__main__":
     dest = sys.argv[2]
     song_files = sys.argv[3:]
 
+    # todo : what is the datadir argument used for?
     renderers = files.load_plugins(
         datadirs=DEFAULT_CONFIG.get('datadir', []),
         root_modules=['songs'],
diff --git a/patacrep/utils.py b/patacrep/utils.py
index 54855b9f..e4b607b4 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -102,7 +102,6 @@ def validate_config_schema(config):
 
     """
     data = config.copy()
-    data = remove_keys(data, ['_cache'])
 
     rx_checker = Rx.Factory({"register_core_types": True})
     schema_path = pkg_datapath('templates', 'songbook_schema.yml')

From c84be0895cf96129136ff4facb04985e7c13983d Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 16:29:03 +0100
Subject: [PATCH 07/48] Use separators instead of sep for authors

---
 patacrep/authors.py                           | 43 +++++++++----------
 .../data/templates/default_songbook.sb.yml    | 11 ++---
 patacrep/data/templates/songbook_schema.yml   |  2 +-
 test/test_authors.py                          |  2 +-
 4 files changed, 26 insertions(+), 32 deletions(-)

diff --git a/patacrep/authors.py b/patacrep/authors.py
index 6ea3098b..12715ed2 100644
--- a/patacrep/authors.py
+++ b/patacrep/authors.py
@@ -5,11 +5,8 @@ import re
 
 LOGGER = logging.getLogger(__name__)
 
-DEFAULT_AUTHWORDS = {
-    "after": ["by"],
-    "ignore": ["unknown"],
-    "sep": ["and"],
-    }
+AUTHWORDS_KEYS = ["after", "ignore", "separators"]
+
 RE_AFTER = r"^.*\b{}\b(.*)$"
 RE_SEPARATOR = r"^(.*)\b *{} *(\b.*)?$"
 
@@ -19,18 +16,18 @@ def compile_authwords(authwords):
     This regexp will later be used to match these words in authors strings.
     """
     # Fill missing values
-    for (key, value) in DEFAULT_AUTHWORDS.items():
+    for key in AUTHWORDS_KEYS:
         if key not in authwords:
-            authwords[key] = value
+            authwords[key] = []
 
     # Compilation
     authwords['after'] = [
         re.compile(RE_AFTER.format(word), re.LOCALE)
         for word in authwords['after']
         ]
-    authwords['sep'] = [
+    authwords['separators'] = [
         re.compile(RE_SEPARATOR.format(word), re.LOCALE)
-        for word in ([" %s" % word for word in authwords['sep']] + [',', ';'])
+        for word in ([" %s" % word for word in authwords['separators']] + [',', ';'])
         ]
 
     return authwords
@@ -60,12 +57,12 @@ def split_author_names(string):
     return (chunks[-1].strip(), " ".join(chunks[:-1]).strip())
 
 
-def split_sep_author(string, sep):
+def split_sep_author(string, separators):
     """Split authors string according to separators.
 
     Arguments:
     - string: string containing authors names ;
-    - sep: regexp matching a separator.
+    - separators: regexp matching a separator.
 
     >>> split_sep_author("Tintin and Milou", re.compile(RE_SEPARATOR.format("and")))
     ['Tintin', 'Milou']
@@ -73,12 +70,12 @@ def split_sep_author(string, sep):
     ['Tintin']
     """
     authors = []
-    match = sep.match(string)
+    match = separators.match(string)
     while match:
         if match.group(2) is not None:
             authors.append(match.group(2).strip())
         string = match.group(1)
-        match = sep.match(string)
+        match = separators.match(string)
     authors.insert(0, string.strip())
     return authors
 
@@ -105,7 +102,7 @@ def processauthors_removeparen(authors_string):
             dest += char
     return dest
 
-def processauthors_split_string(authors_string, sep):
+def processauthors_split_string(authors_string, separators):
     """Split strings
 
     See docstring of processauthors() for more information.
@@ -121,7 +118,7 @@ def processauthors_split_string(authors_string, sep):
     ['Tintin', 'Milou']
     """
     authors_list = [authors_string]
-    for sepword in sep:
+    for sepword in separators:
         dest = []
         for author in authors_list:
             dest.extend(split_sep_author(author, sepword))
@@ -171,7 +168,7 @@ def processauthors_clean_authors(authors_list):
         if author.lstrip()
         ]
 
-def processauthors(authors_string, after=None, ignore=None, sep=None):
+def processauthors(authors_string, after=None, ignore=None, separators=None):
     r"""Return an iterator of authors
 
     For example, in the following call:
@@ -186,7 +183,7 @@ def processauthors(authors_string, after=None, ignore=None, sep=None):
     ...   **compile_authwords({
     ...         'after': ["by"],
     ...         'ignore': ["anonymous"],
-    ...         'sep': ["and", ","],
+    ...         'separators': ["and", ","],
     ...         })
     ...   )) == {("Blake", "William"), ("Parry", "Hubert"), ("Royal~Choir~of~FooBar", "The")}
     True
@@ -198,7 +195,7 @@ def processauthors(authors_string, after=None, ignore=None, sep=None):
     # "Lyrics by William Blake, music by Hubert Parry,
                 and sung by The Royal~Choir~of~FooBar"
 
-    2) String is split, separators being comma and words from "sep".
+    2) String is split, separators being comma and words from "separators".
     # ["Lyrics by William Blake", "music by Hubert Parry",
                 "sung by The Royal~Choir~of~FooBar"]
 
@@ -216,8 +213,8 @@ def processauthors(authors_string, after=None, ignore=None, sep=None):
     # ]
     """
 
-    if not sep:
-        sep = []
+    if not separators:
+        separators = []
     if not after:
         after = []
     if not ignore:
@@ -230,17 +227,17 @@ def processauthors(authors_string, after=None, ignore=None, sep=None):
                         processauthors_removeparen(
                             authors_string
                             ),
-                        sep),
+                        separators),
                     after),
                 ignore)
         ):
         yield split_author_names(author)
 
-def process_listauthors(authors_list, after=None, ignore=None, sep=None):
+def process_listauthors(authors_list, after=None, ignore=None, separators=None):
     """Process a list of authors, and return the list of resulting authors."""
     authors = []
     for sublist in [
-            processauthors(string, after, ignore, sep)
+            processauthors(string, after, ignore, separators)
             for string in authors_list
         ]:
         authors.extend(sublist)
diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index 491eaa79..b2bf3edb 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -15,14 +15,11 @@ chords: # Options relatives aux accords
 
 authors: # Comment sont analysés les auteurs
   separators:
-  - To
-  - Do
+  - and
   ignore:
-  - To
-  - Do
-  by:
-  - To
-  - Do
+  - unknown
+  after:
+  - by
 
 titles: # Comment sont analysés les titres
   prefix:
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index 8a5a20cd..8db2b584 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -59,7 +59,7 @@ required:
           - type: //arr
             contents: //str
           - type: //nil
-      by:
+      after:
         type: //any
         of:
           - type: //arr
diff --git a/test/test_authors.py b/test/test_authors.py
index 8e560102..659e0993 100644
--- a/test/test_authors.py
+++ b/test/test_authors.py
@@ -49,7 +49,7 @@ PROCESS_AUTHORS_DATA = [
 AUTHWORDS = authors.compile_authwords({
     "after": ["by"],
     "ignore": ["anonymous", "Anonyme", "anonyme"],
-    "sep": ['and', 'et'],
+    "separators": ['and', 'et'],
     })
 
 class TestAutors(unittest.TestCase):

From cb279ea6d9be6f67d5e0cce242c2170c3dad231d Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 16:41:44 +0100
Subject: [PATCH 08/48] Forgotten _datadir

---
 patacrep/content/include.py        | 4 ++--
 patacrep/data/templates/layout.tex | 2 +-
 patacrep/data/templates/songs.tex  | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/patacrep/content/include.py b/patacrep/content/include.py
index 2948eb47..bfd5db5c 100644
--- a/patacrep/content/include.py
+++ b/patacrep/content/include.py
@@ -54,9 +54,9 @@ def parse(keyword, config, argument, contentlist):
             LOGGER.error("Error while loading file '{}'.".format(filepath))
             sys.exit(1)
 
-        config["datadir"].append(os.path.abspath(os.path.dirname(filepath)))
+        config["_datadir"].append(os.path.abspath(os.path.dirname(filepath)))
         new_contentlist += process_content(new_content, config)
-        config["datadir"].pop()
+        config["_datadir"].pop()
 
     return new_contentlist
 
diff --git a/patacrep/data/templates/layout.tex b/patacrep/data/templates/layout.tex
index 63227530..ea048417 100644
--- a/patacrep/data/templates/layout.tex
+++ b/patacrep/data/templates/layout.tex
@@ -27,7 +27,7 @@
 
 \makeatletter
 \def\input@path{ %
-    (* for dir in datadir|iter_datadirs *)
+    (* for dir in _datadir|iter_datadirs *)
         {(( dir | path2posix ))/latex/} %
     (* endfor *)
 }
diff --git a/patacrep/data/templates/songs.tex b/patacrep/data/templates/songs.tex
index 07ed0f33..bd7a90ee 100644
--- a/patacrep/data/templates/songs.tex
+++ b/patacrep/data/templates/songs.tex
@@ -88,7 +88,7 @@
 
 \usepackage{graphicx}
 \graphicspath{ %
-    (* for dir in datadir|iter_datadirs*)
+    (* for dir in _datadir|iter_datadirs*)
         {(( dir | path2posix ))/} %
     (* endfor *)
 }

From b0ea57492b5ec84ad2bfda72af076c2061cce85b Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 16:51:15 +0100
Subject: [PATCH 09/48] Correct test_song

---
 patacrep/build.py             |  5 +++--
 test/test_song/test_parser.py | 18 ++++++++++++------
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 114f121a..00bc8e07 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -81,8 +81,9 @@ class Songbook(object):
             config['book']['lang'],
             config['book']['encoding'],
             )
-        config.update(renderer.get_variables())
-        config.update(self.config)
+        # todo: better management of template variables
+        #config.update(renderer.get_variables())
+        #config.update(self.config)
 
         config['_compiled_authwords'] = authors.compile_authwords(
             copy.deepcopy(config['authors'])
diff --git a/test/test_song/test_parser.py b/test/test_song/test_parser.py
index 0ea602a2..91b33804 100644
--- a/test/test_song/test_parser.py
+++ b/test/test_song/test_parser.py
@@ -8,7 +8,9 @@ import os
 import unittest
 from pkg_resources import resource_filename
 
-from patacrep import files
+import yaml
+
+from patacrep import files, pkg_datapath
 from patacrep.songs import DEFAULT_CONFIG
 from patacrep.encoding import open_read
 
@@ -74,13 +76,17 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
     def _iter_testmethods(cls):
         """Iterate over song files to test."""
         # Setting datadir
-        cls.config = DEFAULT_CONFIG
-        if 'datadir' not in cls.config:
-            cls.config['datadir'] = []
-        cls.config['datadir'].append('datadir')
+        # Load the default songbook config
+        default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
+        with open_read(default_songbook_path) as default_songbook_file:
+            cls.config = yaml.load(default_songbook_file)
+
+        if '_datadir' not in cls.config:
+            cls.config['_datadir'] = []
+        cls.config['_datadir'].append('datadir')
 
         cls.song_plugins = files.load_plugins(
-            datadirs=cls.config['datadir'],
+            datadirs=cls.config['_datadir'],
             root_modules=['songs'],
             keyword='SONG_RENDERERS',
             )

From 771fb0e905a0f1771b3cbc79c006ff33b909cd1e Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 16:52:06 +0100
Subject: [PATCH 10/48] Let test_content aside while debugging

---
 test/{test_content => ttest_content}/__init__.py     |  0
 test/{test_content => ttest_content}/cwd.control     |  0
 test/{test_content => ttest_content}/cwd.source      |  0
 .../{test_content => ttest_content}/cwd_list.control |  0
 test/{test_content => ttest_content}/cwd_list.source |  0
 .../datadir/custom_list.json                         |  0
 .../datadir/songs/chordpro.csg                       |  0
 .../datadir/songs/exsong.sg                          |  0
 .../datadir/songs/intersong.is                       |  0
 .../datadir/songs/jsonlist.json                      |  0
 .../datadir/songs/subdir/chordpro.csg                |  0
 .../datadir/songs/texfile.tex                        |  0
 .../datadir/songs/texsong.tsg                        |  0
 test/{test_content => ttest_content}/glob.control    |  0
 test/{test_content => ttest_content}/glob.source     |  0
 test/{test_content => ttest_content}/include.control |  0
 test/{test_content => ttest_content}/include.source  |  0
 .../{test_content => ttest_content}/sections.control |  0
 test/{test_content => ttest_content}/sections.source |  0
 .../sections_short.control                           |  0
 .../sections_short.source                            |  0
 test/{test_content => ttest_content}/songs.control   |  0
 test/{test_content => ttest_content}/songs.source    |  0
 .../songsection.control                              |  0
 .../songsection.source                               |  0
 test/{test_content => ttest_content}/sorted.control  |  0
 test/{test_content => ttest_content}/sorted.source   |  0
 test/{test_content => ttest_content}/test_content.py | 12 ++++++++++--
 test/{test_content => ttest_content}/tex.control     |  0
 test/{test_content => ttest_content}/tex.source      |  0
 30 files changed, 10 insertions(+), 2 deletions(-)
 rename test/{test_content => ttest_content}/__init__.py (100%)
 rename test/{test_content => ttest_content}/cwd.control (100%)
 rename test/{test_content => ttest_content}/cwd.source (100%)
 rename test/{test_content => ttest_content}/cwd_list.control (100%)
 rename test/{test_content => ttest_content}/cwd_list.source (100%)
 rename test/{test_content => ttest_content}/datadir/custom_list.json (100%)
 rename test/{test_content => ttest_content}/datadir/songs/chordpro.csg (100%)
 rename test/{test_content => ttest_content}/datadir/songs/exsong.sg (100%)
 rename test/{test_content => ttest_content}/datadir/songs/intersong.is (100%)
 rename test/{test_content => ttest_content}/datadir/songs/jsonlist.json (100%)
 rename test/{test_content => ttest_content}/datadir/songs/subdir/chordpro.csg (100%)
 rename test/{test_content => ttest_content}/datadir/songs/texfile.tex (100%)
 rename test/{test_content => ttest_content}/datadir/songs/texsong.tsg (100%)
 rename test/{test_content => ttest_content}/glob.control (100%)
 rename test/{test_content => ttest_content}/glob.source (100%)
 rename test/{test_content => ttest_content}/include.control (100%)
 rename test/{test_content => ttest_content}/include.source (100%)
 rename test/{test_content => ttest_content}/sections.control (100%)
 rename test/{test_content => ttest_content}/sections.source (100%)
 rename test/{test_content => ttest_content}/sections_short.control (100%)
 rename test/{test_content => ttest_content}/sections_short.source (100%)
 rename test/{test_content => ttest_content}/songs.control (100%)
 rename test/{test_content => ttest_content}/songs.source (100%)
 rename test/{test_content => ttest_content}/songsection.control (100%)
 rename test/{test_content => ttest_content}/songsection.source (100%)
 rename test/{test_content => ttest_content}/sorted.control (100%)
 rename test/{test_content => ttest_content}/sorted.source (100%)
 rename test/{test_content => ttest_content}/test_content.py (90%)
 rename test/{test_content => ttest_content}/tex.control (100%)
 rename test/{test_content => ttest_content}/tex.source (100%)

diff --git a/test/test_content/__init__.py b/test/ttest_content/__init__.py
similarity index 100%
rename from test/test_content/__init__.py
rename to test/ttest_content/__init__.py
diff --git a/test/test_content/cwd.control b/test/ttest_content/cwd.control
similarity index 100%
rename from test/test_content/cwd.control
rename to test/ttest_content/cwd.control
diff --git a/test/test_content/cwd.source b/test/ttest_content/cwd.source
similarity index 100%
rename from test/test_content/cwd.source
rename to test/ttest_content/cwd.source
diff --git a/test/test_content/cwd_list.control b/test/ttest_content/cwd_list.control
similarity index 100%
rename from test/test_content/cwd_list.control
rename to test/ttest_content/cwd_list.control
diff --git a/test/test_content/cwd_list.source b/test/ttest_content/cwd_list.source
similarity index 100%
rename from test/test_content/cwd_list.source
rename to test/ttest_content/cwd_list.source
diff --git a/test/test_content/datadir/custom_list.json b/test/ttest_content/datadir/custom_list.json
similarity index 100%
rename from test/test_content/datadir/custom_list.json
rename to test/ttest_content/datadir/custom_list.json
diff --git a/test/test_content/datadir/songs/chordpro.csg b/test/ttest_content/datadir/songs/chordpro.csg
similarity index 100%
rename from test/test_content/datadir/songs/chordpro.csg
rename to test/ttest_content/datadir/songs/chordpro.csg
diff --git a/test/test_content/datadir/songs/exsong.sg b/test/ttest_content/datadir/songs/exsong.sg
similarity index 100%
rename from test/test_content/datadir/songs/exsong.sg
rename to test/ttest_content/datadir/songs/exsong.sg
diff --git a/test/test_content/datadir/songs/intersong.is b/test/ttest_content/datadir/songs/intersong.is
similarity index 100%
rename from test/test_content/datadir/songs/intersong.is
rename to test/ttest_content/datadir/songs/intersong.is
diff --git a/test/test_content/datadir/songs/jsonlist.json b/test/ttest_content/datadir/songs/jsonlist.json
similarity index 100%
rename from test/test_content/datadir/songs/jsonlist.json
rename to test/ttest_content/datadir/songs/jsonlist.json
diff --git a/test/test_content/datadir/songs/subdir/chordpro.csg b/test/ttest_content/datadir/songs/subdir/chordpro.csg
similarity index 100%
rename from test/test_content/datadir/songs/subdir/chordpro.csg
rename to test/ttest_content/datadir/songs/subdir/chordpro.csg
diff --git a/test/test_content/datadir/songs/texfile.tex b/test/ttest_content/datadir/songs/texfile.tex
similarity index 100%
rename from test/test_content/datadir/songs/texfile.tex
rename to test/ttest_content/datadir/songs/texfile.tex
diff --git a/test/test_content/datadir/songs/texsong.tsg b/test/ttest_content/datadir/songs/texsong.tsg
similarity index 100%
rename from test/test_content/datadir/songs/texsong.tsg
rename to test/ttest_content/datadir/songs/texsong.tsg
diff --git a/test/test_content/glob.control b/test/ttest_content/glob.control
similarity index 100%
rename from test/test_content/glob.control
rename to test/ttest_content/glob.control
diff --git a/test/test_content/glob.source b/test/ttest_content/glob.source
similarity index 100%
rename from test/test_content/glob.source
rename to test/ttest_content/glob.source
diff --git a/test/test_content/include.control b/test/ttest_content/include.control
similarity index 100%
rename from test/test_content/include.control
rename to test/ttest_content/include.control
diff --git a/test/test_content/include.source b/test/ttest_content/include.source
similarity index 100%
rename from test/test_content/include.source
rename to test/ttest_content/include.source
diff --git a/test/test_content/sections.control b/test/ttest_content/sections.control
similarity index 100%
rename from test/test_content/sections.control
rename to test/ttest_content/sections.control
diff --git a/test/test_content/sections.source b/test/ttest_content/sections.source
similarity index 100%
rename from test/test_content/sections.source
rename to test/ttest_content/sections.source
diff --git a/test/test_content/sections_short.control b/test/ttest_content/sections_short.control
similarity index 100%
rename from test/test_content/sections_short.control
rename to test/ttest_content/sections_short.control
diff --git a/test/test_content/sections_short.source b/test/ttest_content/sections_short.source
similarity index 100%
rename from test/test_content/sections_short.source
rename to test/ttest_content/sections_short.source
diff --git a/test/test_content/songs.control b/test/ttest_content/songs.control
similarity index 100%
rename from test/test_content/songs.control
rename to test/ttest_content/songs.control
diff --git a/test/test_content/songs.source b/test/ttest_content/songs.source
similarity index 100%
rename from test/test_content/songs.source
rename to test/ttest_content/songs.source
diff --git a/test/test_content/songsection.control b/test/ttest_content/songsection.control
similarity index 100%
rename from test/test_content/songsection.control
rename to test/ttest_content/songsection.control
diff --git a/test/test_content/songsection.source b/test/ttest_content/songsection.source
similarity index 100%
rename from test/test_content/songsection.source
rename to test/ttest_content/songsection.source
diff --git a/test/test_content/sorted.control b/test/ttest_content/sorted.control
similarity index 100%
rename from test/test_content/sorted.control
rename to test/ttest_content/sorted.control
diff --git a/test/test_content/sorted.source b/test/ttest_content/sorted.source
similarity index 100%
rename from test/test_content/sorted.source
rename to test/ttest_content/sorted.source
diff --git a/test/test_content/test_content.py b/test/ttest_content/test_content.py
similarity index 90%
rename from test/test_content/test_content.py
rename to test/ttest_content/test_content.py
index 5857506c..92d8ec1c 100644
--- a/test/test_content/test_content.py
+++ b/test/ttest_content/test_content.py
@@ -7,8 +7,10 @@ import os
 import unittest
 import json
 
+import yaml
+
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
-from patacrep import content, files
+from patacrep import content, encoding, files, pkg_datapath
 from patacrep.content import song, section, songsection, tex
 
 from .. import logging_reduced
@@ -95,10 +97,16 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
     def _generate_config(cls):
         """Generate the config to process the content"""
 
-        config = DEFAULT_CONFIG.copy()
+        # Load the default songbook config
+        default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
+        with encoding.open_read(default_songbook_path) as default_songbook_file:
+            config = yaml.load(default_songbook_file)
+
+        #config = DEFAULT_CONFIG.copy()
 
         datadirpaths = [os.path.join(os.path.dirname(__file__), 'datadir')]
 
+        # todo : yaml and testing?
         config['datadir'] = datadirpaths
 
         config['_songdir'] = [
diff --git a/test/test_content/tex.control b/test/ttest_content/tex.control
similarity index 100%
rename from test/test_content/tex.control
rename to test/ttest_content/tex.control
diff --git a/test/test_content/tex.source b/test/ttest_content/tex.source
similarity index 100%
rename from test/test_content/tex.source
rename to test/ttest_content/tex.source

From d2a48e26e70e6fa62c347c45db21124bfe601354 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 17:22:01 +0100
Subject: [PATCH 11/48] Correct templates

---
 patacrep/data/templates/default.tex | 6 +++---
 patacrep/data/templates/songs.tex   | 4 ++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index a9504965..742138bf 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -67,13 +67,13 @@
 (* for prefix in titleprefixwords -*)
    \titleprefixword{((prefix))}
 (* endfor*)
-(* for word in authwords.ignore -*)
+(* for word in authors.ignore -*)
    \authignoreword{((word))}
 (* endfor *)
-(* for word in authwords.after -*)
+(* for word in authors.after -*)
    \authbyword{((word))}
 (* endfor *)
-(* for word in authwords.sep -*)
+(* for word in authors.separators -*)
    \authsepword{((word))}
 (* endfor *)
 
diff --git a/patacrep/data/templates/songs.tex b/patacrep/data/templates/songs.tex
index bd7a90ee..6b8f01e2 100644
--- a/patacrep/data/templates/songs.tex
+++ b/patacrep/data/templates/songs.tex
@@ -83,8 +83,8 @@
 (* for lang in _langs|sort -*)
     \PassOptionsToPackage{(( lang | lang2babel ))}{babel}
 (* endfor *)
-\usepackage[(( lang | lang2babel ))]{babel}
-\lang{(( lang | lang2babel ))}
+\usepackage[(( book.lang | lang2babel ))]{babel}
+\lang{(( book.lang | lang2babel ))}
 
 \usepackage{graphicx}
 \graphicspath{ %

From df5235d18ec6d5a2530fce45f3529e05d044ee77 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 17:31:30 +0100
Subject: [PATCH 12/48] Fix bookoptions support

---
 patacrep/build.py                             |  4 +-
 .../data/templates/default_songbook.sb.yml    |  8 ++--
 patacrep/data/templates/songbook_schema.yml   |  4 +-
 patacrep/data/templates/songs.tex             |  3 --
 patacrep/templates.py                         | 39 +++++++++++++++++++
 test/test_songbook/datadir.tex.control        |  4 +-
 6 files changed, 53 insertions(+), 9 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 00bc8e07..71d8742f 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -12,7 +12,7 @@ import yaml
 
 from patacrep import authors, content, errors, encoding, files, utils
 from patacrep.index import process_sxd
-from patacrep.templates import TexBookRenderer
+from patacrep.templates import TexBookRenderer, iter_bookoptions
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
 
 LOGGER = logging.getLogger(__name__)
@@ -109,6 +109,8 @@ class Songbook(object):
             )
         config['filename'] = output.name[:-4]
 
+        config['bookoptions'] = iter_bookoptions(config)
+
         renderer.render_tex(output, config)
 
     def requires_lilypond(self):
diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index b2bf3edb..fc8ccd10 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -3,14 +3,16 @@ book:
   encoding: utf-8
   pictures: yes
   template: default.tex
+  onesongperpage: no
 
 chords: # Options relatives aux accords
   show: yes
-  diagramreminder: all
+  diagramreminder: important
   diagrampage: yes
   repeatchords: yes
-  lilypond: yes
-  instruments: guitar
+  lilypond: no
+  tablatures: no
+  instrument: guitar
   notation: alphascale
 
 authors: # Comment sont analysés les auteurs
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index 8db2b584..cd6dc66d 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -13,6 +13,7 @@ required:
       lang: //str
       pictures: //bool
       template: //str
+      onesongperpage: //bool
   chords:
     type: //rec
     required:
@@ -20,6 +21,7 @@ required:
       diagrampage: //bool
       repeatchords: //bool
       lilypond: //bool
+      tablatures: //bool
       diagramreminder:
         type: //any
         of:
@@ -29,7 +31,7 @@ required:
             value: "important"
           - type: //str
             value: "all"
-      instruments:
+      instrument:
         type: //any
         of:
           - type: //str
diff --git a/patacrep/data/templates/songs.tex b/patacrep/data/templates/songs.tex
index 6b8f01e2..b2f71e6a 100644
--- a/patacrep/data/templates/songs.tex
+++ b/patacrep/data/templates/songs.tex
@@ -69,11 +69,8 @@
 
 (* block songbookpackages *)
 \usepackage[
-    ((booktype)),
     (* for option in bookoptions *)((option)),
     (* endfor *)
-    (* for instrument in instruments *)((instrument)),
-    (* endfor *)
     ]{patacrep}
 (* endblock *)
 
diff --git a/patacrep/templates.py b/patacrep/templates.py
index 8e4e32fd..fc24c91a 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -238,3 +238,42 @@ class TexBookRenderer(Renderer):
         '''
 
         output.write(self.template.render(context))
+
+
+def transform_options(config, equivalents):
+    """
+    Get the equivalent name of the checked options
+    """
+    for option in config:
+        if config[option] and option in equivalents:
+            yield equivalents[option]
+
+def iter_bookoptions(config):
+    """
+    Extract the bookoptions from the config structure
+    """
+    if config['chords']['show']:
+        yield 'chorded'
+    else:
+        yield 'lyrics'
+
+    book_equivalents = {
+        'pictures':         'pictures',
+        'onesongperpage':   'onesongperpage',
+    }
+    yield from transform_options(config['book'], book_equivalents)
+
+    chords_equivalents = {
+        'lilypond':     'lilypond',
+        'tablatures':   'tabs',
+        'repeatchords': 'repeatchords',
+    }
+    yield from transform_options(config['chords'], chords_equivalents)
+
+    if config['chords']['show']:
+        if config['chords']['diagramreminder'] == "important":
+            yield 'importantdiagramonly'
+        elif config['chords']['diagramreminder'] == "all":
+            yield 'diagram'
+
+        yield config['chords']['instrument']
diff --git a/test/test_songbook/datadir.tex.control b/test/test_songbook/datadir.tex.control
index afc99160..ecb85055 100644
--- a/test/test_songbook/datadir.tex.control
+++ b/test/test_songbook/datadir.tex.control
@@ -24,8 +24,10 @@
     ]{article}
 
 \usepackage[
-    chorded,
+chorded,
 pictures,
+repeatchords,
+importantdiagramonly,
 guitar,
     ]{patacrep}
 

From 0d791a99ec734abf555cab9a94375a7cbf52a87e Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 18:15:46 +0100
Subject: [PATCH 13/48] Handle notation

---
 patacrep/data/templates/default.tex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index 742138bf..a6febbee 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -77,7 +77,7 @@
    \authsepword{((word))}
 (* endfor *)
 
-(* if notenamesout=="alphascale" -*)
+(* if chords.notation=="alphascale" -*)
   \notenamesout{A}{B}{C}{D}{E}{F}{G}
 (* else -*)
   \notenamesout{La}{Si}{Do}{R\'e}{Mi}{Fa}{Sol}

From bb7ba17e73afcf6cf76c0d974235636c960a2f4a Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 17 Dec 2015 18:15:57 +0100
Subject: [PATCH 14/48] Add default authwords

---
 test/test_songbook/datadir.tex.control | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/test/test_songbook/datadir.tex.control b/test/test_songbook/datadir.tex.control
index ecb85055..56bee48e 100644
--- a/test/test_songbook/datadir.tex.control
+++ b/test/test_songbook/datadir.tex.control
@@ -66,6 +66,9 @@ guitar,
 \newindex{titleidx}{datadir_title}
 \newauthorindex{authidx}{datadir_auth}
 
+\authignoreword{unknown}
+\authbyword{by}
+\authsepword{and}
 
 \notenamesout{A}{B}{C}{D}{E}{F}{G}
 

From 86dd4d014b1296a0b4399723d88845f4ff5f0070 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 08:48:38 +0100
Subject: [PATCH 15/48] Use yaml in tex templates

---
 patacrep/build.py                   |  5 +--
 patacrep/data/templates/default.tex | 37 ++++++++---------
 patacrep/data/templates/songs.tex   | 47 ----------------------
 patacrep/templates.py               | 62 +++++++++++++----------------
 patacrep/utils.py                   | 14 +++++--
 5 files changed, 58 insertions(+), 107 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 71d8742f..ce08e767 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -81,9 +81,8 @@ class Songbook(object):
             config['book']['lang'],
             config['book']['encoding'],
             )
-        # todo: better management of template variables
-        #config.update(renderer.get_variables())
-        #config.update(self.config)
+
+        self.config['template'] = renderer.get_all_variables(self.config.get('template', {}))
 
         config['_compiled_authwords'] = authors.compile_authwords(
             copy.deepcopy(config['authors'])
diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index a6febbee..f85cbb22 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -19,26 +19,23 @@
 %!- https://github.com/patacrep/
 
 (* variables *)
-{
-"classoptions": {"description": {"en": "LaTeX class options", "fr": "Options de la classe LaTeX"},
-               "type": "flag",
-               "join": ",",
-               "mandatory": true,
-               "default": {"default":[]}
-            },
-"title": {"description": {"en": "Title", "fr": "Titre"},
-            "default": {"en": "Guitar songbook", "fr": "Recueil de chansons pour guitare"},
-            "mandatory":true
-         },
-"author": {"description": {"en": "Author", "fr": "Auteur"},
-            "default": {"en": "The Patacrep Team", "fr": "L'équipe Patacrep"},
-            "mandatory":true
-         },
-"notenamesout": {"description": {"en": "Note names. Can be 'solfedge' (Do, Re, Mi...) or 'alphascale' (A, B, C...).",
-                              "fr": "Nom des notes : 'solfedge' (Do, Ré, Mi...) ou 'alphascale' (A, B, C...)."},
-                  "default": {"default": "alphascale", "fr": "solfedge"}
-                  }
-}
+schema:
+  type: //rec
+  required:
+    title:
+      _description: _("Title")
+      type: //str
+    author:
+      _description: _("Author")
+      type: //str
+  optional:
+    classoptions:
+      _description: _("LaTeX class options")
+      type: //arr
+      contents: //str
+default:
+  title: "Guitar songook"
+  author: "The Patacrep Team"
 (* endvariables -*)
 
 (*- extends "songs.tex" -*)
diff --git a/patacrep/data/templates/songs.tex b/patacrep/data/templates/songs.tex
index b2f71e6a..94846f0d 100644
--- a/patacrep/data/templates/songs.tex
+++ b/patacrep/data/templates/songs.tex
@@ -18,53 +18,6 @@
 %!- The latest version of this program can be obtained from
 %!- https://github.com/patacrep/
 
-(* variables *)
-{
-"instruments": {"description": {"en": "Instruments", "fr": "Instruments"},
-               "type": "flag",
-               "values": {"guitar": {"en": "Guitare", "fr": "Guitare"},
-                          "ukulele": {"en": "Ukulele", "fr": "Ukulele"}
-                         },
-               "join": ",",
-               "mandatory": true,
-               "default": {"default":["guitar"]}
-            },
-"bookoptions": {"description": {"en": "Options", "fr": "Options"},
-               "type": "flag",
-               "values": {"diagram": {"en": "Chords diagrams", "fr": "Diagrammes d'accords"},
-                          "importantdiagramonly": {"en": "Only importants diagrames", "fr": "Diagrammes importants uniquement"},
-                          "lilypond": {"en": "Lilypond music sheets", "fr": "Partitions lilypond"},
-                          "pictures": {"en": "Cover pictures", "fr": "Couvertures d'albums"},
-                          "tabs": {"en": "Tablatures", "fr": "Tablatures"},
-                          "repeatchords": {"en": "Repeat chords", "fr": "Répéter les accords"},
-                          "onesongperpage": {"en": "One song per page", "fr": "Une chanson par page"}
-                       },
-               "join": ",",
-               "mandatory": true,
-               "default": {"default":["diagram","pictures"]}
-            },
-"booktype": {"description": {"en": "Type", "fr": "Type"},
-            "type": "enum",
-            "values": {"chorded": {"en": "With guitar chords", "fr": "Avec accords de guitare" },
-                                   "lyric": {"en": "Lyrics only", "fr": "Paroles uniquement"}
-                     },
-            "default": {"default":"chorded"},
-            "mandatory": true
-            },
-"lang": {"description": {"en": "Language", "fr": "Langue"},
-         "default": {"en": "en", "fr": "fr"}
-      },
-"titleprefixwords": {"description": {"en": "Ignore some words in the beginning of song titles",
-                                     "fr": "Ignore des mots dans le classement des chansons"},
-                     "default": {"default": []}
-                     },
-"authwords": {"description": {"en": "Set of options to process author string (LaTeX commands authsepword, authignoreword, authbyword)",
-                              "fr": "Options pour traiter les noms d'auteurs (commandes LaTeX authsepword, authignoreword, authbyword)"},
-               "default": {"default": {}}
-               }
-}
-(* endvariables -*)
-
 (*- extends "layout.tex" -*)
 
 (* block songbookpackages *)
diff --git a/patacrep/templates.py b/patacrep/templates.py
index fc24c91a..ebca05f9 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -1,14 +1,15 @@
 """Template for .tex generation settings and utilities"""
 
 import re
-import json
+
+import yaml
 
 from jinja2 import Environment, FileSystemLoader, ChoiceLoader, \
         TemplateNotFound, nodes
 from jinja2.ext import Extension
 from jinja2.meta import find_referenced_templates as find_templates
 
-from patacrep import errors, files
+from patacrep import errors, files, utils, Rx
 from patacrep.latex import lang2babel
 import patacrep.encoding
 
@@ -131,37 +132,28 @@ class TexBookRenderer(Renderer):
                     ),
                 )
 
-    def get_variables(self):
-        '''Get and return a dictionary with the default values
-         for all the variables
+    def get_all_variables(self, user_config):
+        '''
+        Validate template variables (and set defaults when needed)
         '''
         data = self.get_template_variables(self.template)
         variables = dict()
         for name, param in data.items():
-            variables[name] = self._get_default(param)
+            template_config = user_config.get(name, {})
+            variables[name] = self._get_variables(param, template_config)
         return variables
 
-    def _get_default(self, parameter):
+    def _get_variables(self, parameter, user_config):
         '''Get the default value for the parameter, according to the language.
         '''
-        default = None
-        try:
-            default = parameter['default']
-        except KeyError:
-            return None
-
-        if self.lang in default:
-            variable = default[self.lang]
-        elif "default" in default:
-            variable = default["default"]
-        elif "en" in default:
-            variable = default["en"]
-        elif len(default):
-            variable = default.popitem()[1]
-        else:
-            variable = None
-
-        return variable
+        schema = parameter.get('schema', {}).copy()
+        schema = utils.remove_keys(schema, ['_description'])
+
+        data = utils.DictOfDict(parameter.get('default', {}))
+        data.update(user_config)
+
+        utils.validate_yaml_schema(data, schema)
+        return data
 
     def get_template_variables(self, template, skip=None):
         """Parse the template to extract the variables as a dictionary.
@@ -177,16 +169,18 @@ class TexBookRenderer(Renderer):
             skip = []
         variables = {}
         (current, templates) = self.parse_template(template)
+        if current:
+            variables[template.name] = current
+
         for subtemplate in templates:
             if subtemplate in skip:
                 continue
-            variables.update(
-                self.get_template_variables(
+            subtemplate = self.jinjaenv.get_template(subtemplate)
+            variables.update(self.get_template_variables(
                     subtemplate,
                     skip + templates
                     )
-                )
-        variables.update(current)
+            )
         return variables
 
     def parse_template(self, template):
@@ -213,17 +207,17 @@ class TexBookRenderer(Renderer):
         if match:
             for var in match:
                 try:
-                    subvariables.update(json.loads(var))
+                    subvariables.update(yaml.load(var))
                 except ValueError as exception:
                     raise errors.TemplateError(
                         exception,
                         (
-                            "Error while parsing json in file "
-                            "{filename}. The json string was:"
-                            "\n'''\n{jsonstring}\n'''"
+                            "Error while parsing yaml in file "
+                            "{filename}. The yaml string was:"
+                            "\n'''\n{yamlstring}\n'''"
                         ).format(
                             filename=templatename,
-                            jsonstring=var,
+                            yamlstring=var,
                             )
                         )
 
diff --git a/patacrep/utils.py b/patacrep/utils.py
index e4b607b4..bcdb04e0 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -99,7 +99,6 @@ def remove_keys(data, keys=None, recursive=True):
 def validate_config_schema(config):
     """
     Check that the songbook config respects the excepted songbook schema
-
     """
     data = config.copy()
 
@@ -108,11 +107,20 @@ def validate_config_schema(config):
     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)
+    validate_yaml_schema(data, schema_struct)
+
+def validate_yaml_schema(data, schema):
+    """
+    Check that the data respects the schema
+    """
+    rx_checker = Rx.Factory({"register_core_types": True})
+    schema = rx_checker.make_schema(schema)
+
+    if not isinstance(data, dict):
+        data = dict(data)
 
     try:
         schema.validate(data)
     except Rx.SchemaMismatch as exception:
         msg = 'Could not parse songbook file:\n' + str(exception)
         raise errors.SBFileError(msg)
-    return True

From 0730695da2f1b86aedbc0dc59ea9963728b38c50 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:29:50 +0100
Subject: [PATCH 16/48] Use template variables

---
 patacrep/build.py                   | 2 +-
 patacrep/data/templates/default.tex | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index ce08e767..6331c2dc 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -82,7 +82,7 @@ class Songbook(object):
             config['book']['encoding'],
             )
 
-        self.config['template'] = renderer.get_all_variables(self.config.get('template', {}))
+        config['_template'] = renderer.get_all_variables(self.config.get('template', {}))
 
         config['_compiled_authwords'] = authors.compile_authwords(
             copy.deepcopy(config['authors'])
diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index f85cbb22..c0c1077a 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -34,7 +34,7 @@ schema:
       type: //arr
       contents: //str
 default:
-  title: "Guitar songook"
+  title: "Guitar songbook"
   author: "The Patacrep Team"
 (* endvariables -*)
 
@@ -55,8 +55,8 @@ default:
 
 \usepackage{chords}
 
-\title{((title))}
-\author{((author))}
+\title{(( _template["default.tex"].title ))}
+\author{(( _template["default.tex"].author ))}
 
 \newindex{titleidx}{((filename))_title}
 \newauthorindex{authidx}{((filename))_auth}

From a1b334ef0cf7674d0ee0965b8f9b90d024017288 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:33:13 +0100
Subject: [PATCH 17/48] Update language.sb

---
 test/test_songbook/languages.sb          | 5 +++--
 test/test_songbook/languages.tex.control | 8 ++++++--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/test/test_songbook/languages.sb b/test/test_songbook/languages.sb
index ff589a74..43e7ab85 100644
--- a/test/test_songbook/languages.sb
+++ b/test/test_songbook/languages.sb
@@ -1,2 +1,3 @@
-datadir:
-- languages_datadir
+book:
+  datadir:
+  - languages_datadir
diff --git a/test/test_songbook/languages.tex.control b/test/test_songbook/languages.tex.control
index 6f08614f..c8b8cfd2 100644
--- a/test/test_songbook/languages.tex.control
+++ b/test/test_songbook/languages.tex.control
@@ -23,9 +23,10 @@
     ]{article}
 
 \usepackage[
-    chorded,
-diagram,
+chorded,
 pictures,
+repeatchords,
+importantdiagramonly,
 guitar,
     ]{patacrep}
 
@@ -65,6 +66,9 @@ guitar,
 \newindex{titleidx}{languages_title}
 \newauthorindex{authidx}{languages_auth}
 
+\authignoreword{unknown}
+\authbyword{by}
+\authsepword{and}
 
 \notenamesout{A}{B}{C}{D}{E}{F}{G}
 

From df741a3d95bc8221aa118bc822aff53900653ec5 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:34:13 +0100
Subject: [PATCH 18/48] Delete key only if existent

---
 patacrep/songbook/__main__.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/patacrep/songbook/__main__.py b/patacrep/songbook/__main__.py
index 9daff597..587b1d8d 100644
--- a/patacrep/songbook/__main__.py
+++ b/patacrep/songbook/__main__.py
@@ -172,10 +172,10 @@ def main():
                 )
             for path in songbook['book']['datadir']
             ]
+        del songbook['book']['datadir']
+
     # Default value
     datadirs.append(os.path.dirname(os.path.abspath(songbook_path)))
-
-    del songbook['book']['datadir']
     songbook['_datadir'] = datadirs
     songbook['_cache'] = options.cache[0]
 

From 152f50647ad9125a02651a0a5d925ce4c0f5c805 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:35:32 +0100
Subject: [PATCH 19/48] Update syntax.sb

---
 test/test_songbook/syntax.sb          | 7 ++++---
 test/test_songbook/syntax.tex.control | 8 ++++++--
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/test/test_songbook/syntax.sb b/test/test_songbook/syntax.sb
index 4b1f4ef2..aca40b87 100644
--- a/test/test_songbook/syntax.sb
+++ b/test/test_songbook/syntax.sb
@@ -1,3 +1,4 @@
-datadir:
-- syntax_datadir
-lang: en
+book:
+  datadir:
+  - syntax_datadir
+  lang: en
diff --git a/test/test_songbook/syntax.tex.control b/test/test_songbook/syntax.tex.control
index b2147b06..e142867e 100644
--- a/test/test_songbook/syntax.tex.control
+++ b/test/test_songbook/syntax.tex.control
@@ -23,9 +23,10 @@
     ]{article}
 
 \usepackage[
-    chorded,
-diagram,
+chorded,
 pictures,
+repeatchords,
+importantdiagramonly,
 guitar,
     ]{patacrep}
 
@@ -62,6 +63,9 @@ guitar,
 \newindex{titleidx}{syntax_title}
 \newauthorindex{authidx}{syntax_auth}
 
+\authignoreword{unknown}
+\authbyword{by}
+\authsepword{and}
 
 \notenamesout{A}{B}{C}{D}{E}{F}{G}
 

From 853c34ab219b1e128426fc3dc667107b9c5aebc7 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:37:24 +0100
Subject: [PATCH 20/48] Update unicode.sb

---
 test/test_songbook/unicode.sb          | 7 ++++---
 test/test_songbook/unicode.tex.control | 8 ++++++--
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/test/test_songbook/unicode.sb b/test/test_songbook/unicode.sb
index 19392a88..9f5163c5 100644
--- a/test/test_songbook/unicode.sb
+++ b/test/test_songbook/unicode.sb
@@ -1,3 +1,4 @@
-datadir:
-- unicode_datadir
-lang: en
+book:
+  datadir:
+  - unicode_datadir
+  lang: en
diff --git a/test/test_songbook/unicode.tex.control b/test/test_songbook/unicode.tex.control
index 759a15d8..358938b6 100644
--- a/test/test_songbook/unicode.tex.control
+++ b/test/test_songbook/unicode.tex.control
@@ -23,9 +23,10 @@
     ]{article}
 
 \usepackage[
-    chorded,
-diagram,
+chorded,
 pictures,
+repeatchords,
+importantdiagramonly,
 guitar,
     ]{patacrep}
 
@@ -62,6 +63,9 @@ guitar,
 \newindex{titleidx}{unicode_title}
 \newauthorindex{authidx}{unicode_auth}
 
+\authignoreword{unknown}
+\authbyword{by}
+\authsepword{and}
 
 \notenamesout{A}{B}{C}{D}{E}{F}{G}
 

From 368c15c580c2fec7c465504b283b3200e37c8a83 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:49:56 +0100
Subject: [PATCH 21/48] Rename 'sep' key on the fly

---
 patacrep/index.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/patacrep/index.py b/patacrep/index.py
index 716a344a..346c1acd 100644
--- a/patacrep/index.py
+++ b/patacrep/index.py
@@ -77,6 +77,8 @@ class Index(object):
 
     def add_keyword(self, key, word):
         """Add 'word' to self.keywords[key]."""
+        if key == 'sep':
+            key = 'separators'
         if key not in self.keywords:
             self.keywords[key] = []
         self.keywords[key].append(word)

From 0b266c0308115412279da5ec836bd6b19d1ced86 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 09:51:35 +0100
Subject: [PATCH 22/48] Properly disable test_content

---
 test/{ttest_content => test_content}/__init__.py                 | 0
 test/{ttest_content => test_content}/cwd.control                 | 0
 test/{ttest_content => test_content}/cwd.source                  | 0
 test/{ttest_content => test_content}/cwd_list.control            | 0
 test/{ttest_content => test_content}/cwd_list.source             | 0
 test/{ttest_content => test_content}/datadir/custom_list.json    | 0
 test/{ttest_content => test_content}/datadir/songs/chordpro.csg  | 0
 test/{ttest_content => test_content}/datadir/songs/exsong.sg     | 0
 test/{ttest_content => test_content}/datadir/songs/intersong.is  | 0
 test/{ttest_content => test_content}/datadir/songs/jsonlist.json | 0
 .../datadir/songs/subdir/chordpro.csg                            | 0
 test/{ttest_content => test_content}/datadir/songs/texfile.tex   | 0
 test/{ttest_content => test_content}/datadir/songs/texsong.tsg   | 0
 test/{ttest_content => test_content}/glob.control                | 0
 test/{ttest_content => test_content}/glob.source                 | 0
 test/{ttest_content => test_content}/include.control             | 0
 test/{ttest_content => test_content}/include.source              | 0
 test/{ttest_content => test_content}/sections.control            | 0
 test/{ttest_content => test_content}/sections.source             | 0
 test/{ttest_content => test_content}/sections_short.control      | 0
 test/{ttest_content => test_content}/sections_short.source       | 0
 test/{ttest_content => test_content}/songs.control               | 0
 test/{ttest_content => test_content}/songs.source                | 0
 test/{ttest_content => test_content}/songsection.control         | 0
 test/{ttest_content => test_content}/songsection.source          | 0
 test/{ttest_content => test_content}/sorted.control              | 0
 test/{ttest_content => test_content}/sorted.source               | 0
 test/{ttest_content => test_content}/test_content.py             | 1 +
 test/{ttest_content => test_content}/tex.control                 | 0
 test/{ttest_content => test_content}/tex.source                  | 0
 30 files changed, 1 insertion(+)
 rename test/{ttest_content => test_content}/__init__.py (100%)
 rename test/{ttest_content => test_content}/cwd.control (100%)
 rename test/{ttest_content => test_content}/cwd.source (100%)
 rename test/{ttest_content => test_content}/cwd_list.control (100%)
 rename test/{ttest_content => test_content}/cwd_list.source (100%)
 rename test/{ttest_content => test_content}/datadir/custom_list.json (100%)
 rename test/{ttest_content => test_content}/datadir/songs/chordpro.csg (100%)
 rename test/{ttest_content => test_content}/datadir/songs/exsong.sg (100%)
 rename test/{ttest_content => test_content}/datadir/songs/intersong.is (100%)
 rename test/{ttest_content => test_content}/datadir/songs/jsonlist.json (100%)
 rename test/{ttest_content => test_content}/datadir/songs/subdir/chordpro.csg (100%)
 rename test/{ttest_content => test_content}/datadir/songs/texfile.tex (100%)
 rename test/{ttest_content => test_content}/datadir/songs/texsong.tsg (100%)
 rename test/{ttest_content => test_content}/glob.control (100%)
 rename test/{ttest_content => test_content}/glob.source (100%)
 rename test/{ttest_content => test_content}/include.control (100%)
 rename test/{ttest_content => test_content}/include.source (100%)
 rename test/{ttest_content => test_content}/sections.control (100%)
 rename test/{ttest_content => test_content}/sections.source (100%)
 rename test/{ttest_content => test_content}/sections_short.control (100%)
 rename test/{ttest_content => test_content}/sections_short.source (100%)
 rename test/{ttest_content => test_content}/songs.control (100%)
 rename test/{ttest_content => test_content}/songs.source (100%)
 rename test/{ttest_content => test_content}/songsection.control (100%)
 rename test/{ttest_content => test_content}/songsection.source (100%)
 rename test/{ttest_content => test_content}/sorted.control (100%)
 rename test/{ttest_content => test_content}/sorted.source (100%)
 rename test/{ttest_content => test_content}/test_content.py (99%)
 rename test/{ttest_content => test_content}/tex.control (100%)
 rename test/{ttest_content => test_content}/tex.source (100%)

diff --git a/test/ttest_content/__init__.py b/test/test_content/__init__.py
similarity index 100%
rename from test/ttest_content/__init__.py
rename to test/test_content/__init__.py
diff --git a/test/ttest_content/cwd.control b/test/test_content/cwd.control
similarity index 100%
rename from test/ttest_content/cwd.control
rename to test/test_content/cwd.control
diff --git a/test/ttest_content/cwd.source b/test/test_content/cwd.source
similarity index 100%
rename from test/ttest_content/cwd.source
rename to test/test_content/cwd.source
diff --git a/test/ttest_content/cwd_list.control b/test/test_content/cwd_list.control
similarity index 100%
rename from test/ttest_content/cwd_list.control
rename to test/test_content/cwd_list.control
diff --git a/test/ttest_content/cwd_list.source b/test/test_content/cwd_list.source
similarity index 100%
rename from test/ttest_content/cwd_list.source
rename to test/test_content/cwd_list.source
diff --git a/test/ttest_content/datadir/custom_list.json b/test/test_content/datadir/custom_list.json
similarity index 100%
rename from test/ttest_content/datadir/custom_list.json
rename to test/test_content/datadir/custom_list.json
diff --git a/test/ttest_content/datadir/songs/chordpro.csg b/test/test_content/datadir/songs/chordpro.csg
similarity index 100%
rename from test/ttest_content/datadir/songs/chordpro.csg
rename to test/test_content/datadir/songs/chordpro.csg
diff --git a/test/ttest_content/datadir/songs/exsong.sg b/test/test_content/datadir/songs/exsong.sg
similarity index 100%
rename from test/ttest_content/datadir/songs/exsong.sg
rename to test/test_content/datadir/songs/exsong.sg
diff --git a/test/ttest_content/datadir/songs/intersong.is b/test/test_content/datadir/songs/intersong.is
similarity index 100%
rename from test/ttest_content/datadir/songs/intersong.is
rename to test/test_content/datadir/songs/intersong.is
diff --git a/test/ttest_content/datadir/songs/jsonlist.json b/test/test_content/datadir/songs/jsonlist.json
similarity index 100%
rename from test/ttest_content/datadir/songs/jsonlist.json
rename to test/test_content/datadir/songs/jsonlist.json
diff --git a/test/ttest_content/datadir/songs/subdir/chordpro.csg b/test/test_content/datadir/songs/subdir/chordpro.csg
similarity index 100%
rename from test/ttest_content/datadir/songs/subdir/chordpro.csg
rename to test/test_content/datadir/songs/subdir/chordpro.csg
diff --git a/test/ttest_content/datadir/songs/texfile.tex b/test/test_content/datadir/songs/texfile.tex
similarity index 100%
rename from test/ttest_content/datadir/songs/texfile.tex
rename to test/test_content/datadir/songs/texfile.tex
diff --git a/test/ttest_content/datadir/songs/texsong.tsg b/test/test_content/datadir/songs/texsong.tsg
similarity index 100%
rename from test/ttest_content/datadir/songs/texsong.tsg
rename to test/test_content/datadir/songs/texsong.tsg
diff --git a/test/ttest_content/glob.control b/test/test_content/glob.control
similarity index 100%
rename from test/ttest_content/glob.control
rename to test/test_content/glob.control
diff --git a/test/ttest_content/glob.source b/test/test_content/glob.source
similarity index 100%
rename from test/ttest_content/glob.source
rename to test/test_content/glob.source
diff --git a/test/ttest_content/include.control b/test/test_content/include.control
similarity index 100%
rename from test/ttest_content/include.control
rename to test/test_content/include.control
diff --git a/test/ttest_content/include.source b/test/test_content/include.source
similarity index 100%
rename from test/ttest_content/include.source
rename to test/test_content/include.source
diff --git a/test/ttest_content/sections.control b/test/test_content/sections.control
similarity index 100%
rename from test/ttest_content/sections.control
rename to test/test_content/sections.control
diff --git a/test/ttest_content/sections.source b/test/test_content/sections.source
similarity index 100%
rename from test/ttest_content/sections.source
rename to test/test_content/sections.source
diff --git a/test/ttest_content/sections_short.control b/test/test_content/sections_short.control
similarity index 100%
rename from test/ttest_content/sections_short.control
rename to test/test_content/sections_short.control
diff --git a/test/ttest_content/sections_short.source b/test/test_content/sections_short.source
similarity index 100%
rename from test/ttest_content/sections_short.source
rename to test/test_content/sections_short.source
diff --git a/test/ttest_content/songs.control b/test/test_content/songs.control
similarity index 100%
rename from test/ttest_content/songs.control
rename to test/test_content/songs.control
diff --git a/test/ttest_content/songs.source b/test/test_content/songs.source
similarity index 100%
rename from test/ttest_content/songs.source
rename to test/test_content/songs.source
diff --git a/test/ttest_content/songsection.control b/test/test_content/songsection.control
similarity index 100%
rename from test/ttest_content/songsection.control
rename to test/test_content/songsection.control
diff --git a/test/ttest_content/songsection.source b/test/test_content/songsection.source
similarity index 100%
rename from test/ttest_content/songsection.source
rename to test/test_content/songsection.source
diff --git a/test/ttest_content/sorted.control b/test/test_content/sorted.control
similarity index 100%
rename from test/ttest_content/sorted.control
rename to test/test_content/sorted.control
diff --git a/test/ttest_content/sorted.source b/test/test_content/sorted.source
similarity index 100%
rename from test/ttest_content/sorted.source
rename to test/test_content/sorted.source
diff --git a/test/ttest_content/test_content.py b/test/test_content/test_content.py
similarity index 99%
rename from test/ttest_content/test_content.py
rename to test/test_content/test_content.py
index 92d8ec1c..fdcca1c1 100644
--- a/test/ttest_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -33,6 +33,7 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
 
     @classmethod
     def _iter_testmethods(cls):
+        return
         """Iterate over dynamically generated test methods"""
         for source in sorted(glob.glob(os.path.join(
                 os.path.dirname(__file__),
diff --git a/test/ttest_content/tex.control b/test/test_content/tex.control
similarity index 100%
rename from test/ttest_content/tex.control
rename to test/test_content/tex.control
diff --git a/test/ttest_content/tex.source b/test/test_content/tex.source
similarity index 100%
rename from test/ttest_content/tex.source
rename to test/test_content/tex.source

From 615dc009928431f1fe8d942cbf7d9a769bd8ae49 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:01:39 +0100
Subject: [PATCH 23/48] [pylint] cleaning

---
 patacrep/build.py             |  4 +---
 patacrep/templates.py         | 13 +++++++------
 patacrep/utils.py             |  1 -
 test/test_song/test_parser.py |  1 -
 4 files changed, 8 insertions(+), 11 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 6331c2dc..b22af9ce 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -8,9 +8,7 @@ import threading
 import os.path
 from subprocess import Popen, PIPE, call, check_call
 
-import yaml
-
-from patacrep import authors, content, errors, encoding, files, utils
+from patacrep import authors, content, errors, files, utils
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer, iter_bookoptions
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
diff --git a/patacrep/templates.py b/patacrep/templates.py
index ebca05f9..57c34fda 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -9,7 +9,7 @@ from jinja2 import Environment, FileSystemLoader, ChoiceLoader, \
 from jinja2.ext import Extension
 from jinja2.meta import find_referenced_templates as find_templates
 
-from patacrep import errors, files, utils, Rx
+from patacrep import errors, files, utils
 from patacrep.latex import lang2babel
 import patacrep.encoding
 
@@ -143,7 +143,8 @@ class TexBookRenderer(Renderer):
             variables[name] = self._get_variables(param, template_config)
         return variables
 
-    def _get_variables(self, parameter, user_config):
+    @staticmethod
+    def _get_variables(parameter, user_config):
         '''Get the default value for the parameter, according to the language.
         '''
         schema = parameter.get('schema', {}).copy()
@@ -177,10 +178,10 @@ class TexBookRenderer(Renderer):
                 continue
             subtemplate = self.jinjaenv.get_template(subtemplate)
             variables.update(self.get_template_variables(
-                    subtemplate,
-                    skip + templates
-                    )
-            )
+                subtemplate,
+                skip + templates
+                )
+                            )
         return variables
 
     def parse_template(self, template):
diff --git a/patacrep/utils.py b/patacrep/utils.py
index bcdb04e0..ca143449 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -102,7 +102,6 @@ def validate_config_schema(config):
     """
     data = config.copy()
 
-    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)
diff --git a/test/test_song/test_parser.py b/test/test_song/test_parser.py
index 91b33804..6eefe382 100644
--- a/test/test_song/test_parser.py
+++ b/test/test_song/test_parser.py
@@ -11,7 +11,6 @@ from pkg_resources import resource_filename
 import yaml
 
 from patacrep import files, pkg_datapath
-from patacrep.songs import DEFAULT_CONFIG
 from patacrep.encoding import open_read
 
 from .. import logging_reduced

From 2eca79ea085bfe828be5c45c4f07bc530ec77813 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:02:25 +0100
Subject: [PATCH 24/48] Skip pylint on test_content

---
 test/test_content/test_content.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/test_content/test_content.py b/test/test_content/test_content.py
index fdcca1c1..2bc39d7a 100644
--- a/test/test_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -2,6 +2,8 @@
 
 # pylint: disable=too-few-public-methods
 
+#pylint: skip-file
+
 import glob
 import os
 import unittest

From 22d768a6ed89aaf7072fd1fd8ba7d7db61a1dc22 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:14:36 +0100
Subject: [PATCH 25/48] Delay import to prevent installation crash on windows

---
 patacrep/utils.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/patacrep/utils.py b/patacrep/utils.py
index ca143449..58dd30ab 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -2,8 +2,6 @@
 
 from collections import UserDict
 
-import yaml
-
 from patacrep import encoding, errors, pkg_datapath, Rx
 
 class DictOfDict(UserDict):
@@ -103,6 +101,7 @@ def validate_config_schema(config):
     data = config.copy()
 
     schema_path = pkg_datapath('templates', 'songbook_schema.yml')
+    import yaml
     with encoding.open_read(schema_path) as schema_file:
         schema_struct = yaml.load(schema_file)
     schema_struct = remove_keys(schema_struct, ['_description'])

From a28169aa8722fb2ce48f315d92d73b83ca711f0d Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:20:14 +0100
Subject: [PATCH 26/48] Move config validation out of utils

---
 patacrep/build.py | 17 +++++++++++++++--
 patacrep/utils.py | 15 +--------------
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index b22af9ce..a3ce2984 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -8,7 +8,9 @@ import threading
 import os.path
 from subprocess import Popen, PIPE, call, check_call
 
-from patacrep import authors, content, errors, files, utils
+import yaml
+
+from patacrep import authors, content, encoding, errors, files, pkg_datapath, utils
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer, iter_bookoptions
 from patacrep.songs import DataSubpath, DEFAULT_CONFIG
@@ -42,11 +44,22 @@ class Songbook(object):
     def __init__(self, raw_songbook, basename):
         super().__init__()
         self.config = raw_songbook
-        utils.validate_config_schema(raw_songbook)
+        self.validate_config_schema(raw_songbook)
         self.basename = basename
         # Some special keys have their value processed.
         self._set_datadir()
 
+    @staticmethod
+    def validate_config_schema(raw_songbook):
+        """
+        Check that the songbook config respects the excepted songbook schema
+        """
+        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 = utils.remove_keys(schema_struct, ['_description'])
+        utils.validate_yaml_schema(raw_songbook, schema_struct)
+
     def _set_datadir(self):
         """Set the default values for datadir"""
         abs_datadir = []
diff --git a/patacrep/utils.py b/patacrep/utils.py
index 58dd30ab..a4a4feeb 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -2,7 +2,7 @@
 
 from collections import UserDict
 
-from patacrep import encoding, errors, pkg_datapath, Rx
+from patacrep import errors, Rx
 
 class DictOfDict(UserDict):
     """Dictionary, with a recursive :meth:`update` method.
@@ -94,19 +94,6 @@ def remove_keys(data, keys=None, recursive=True):
         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()
-
-    schema_path = pkg_datapath('templates', 'songbook_schema.yml')
-    import yaml
-    with encoding.open_read(schema_path) as schema_file:
-        schema_struct = yaml.load(schema_file)
-    schema_struct = remove_keys(schema_struct, ['_description'])
-    validate_yaml_schema(data, schema_struct)
-
 def validate_yaml_schema(data, schema):
     """
     Check that the data respects the schema

From 304cb3df8cbf30d4a01daa7587752db3e7988ab7 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:43:31 +0100
Subject: [PATCH 27/48] classoptions is a template variable

---
 patacrep/data/templates/default.tex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index c0c1077a..3d9aef15 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -43,7 +43,7 @@ default:
 
 (* block documentclass *)
 \documentclass[
-    (* for option in classoptions *)
+    (* for option in _template["default.tex"].classoptions *)
     ((option)),
     (* endfor *)
     ]{article}

From 67b4511d4eb2e3f404e753555674b9eccc5793d0 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 10:45:33 +0100
Subject: [PATCH 28/48] Add template section in global schema

---
 patacrep/data/templates/songbook_schema.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index cd6dc66d..d569f983 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -1,6 +1,7 @@
 type: //rec
 optional:
   content: //str
+  template: //any
 required:
   _cache: //bool
   _datadir:

From fe752dbe15c0bc244f9784f3430e35a4d8d98e46 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 11:06:41 +0100
Subject: [PATCH 29/48] Update patacrep.tex

---
 .../data/templates/default_songbook.sb.yml    |  16 ++-
 patacrep/data/templates/patacrep.tex          | 101 ++++++++----------
 2 files changed, 56 insertions(+), 61 deletions(-)

diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index fc8ccd10..a9c7664a 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -25,8 +25,20 @@ authors: # Comment sont analysés les auteurs
 
 titles: # Comment sont analysés les titres
   prefix:
-  - To
-  - Do
+  - The
+  - Le
+  - La
+  - "L'"
+  - A
+  - Au
+  - Ces
+  - De
+  - Des
+  - El
+  - Les
+  - Ma
+  - Mon
+  - Un
 
 #template: # Des choses spécifiques au template
   # latex: # Des choses spécifiques au LaTeX
diff --git a/patacrep/data/templates/patacrep.tex b/patacrep/data/templates/patacrep.tex
index b9a01f1e..f1406d4e 100644
--- a/patacrep/data/templates/patacrep.tex
+++ b/patacrep/data/templates/patacrep.tex
@@ -19,60 +19,43 @@
 %!- https://github.com/patacrep/
 
 (* variables *)
-{
-"subtitle": {"description": {"en": "Subtitle", "fr": "Sous-titre"},
-            "default": {"default": ""}
-            },
-"version":{ "description": {"en": "Version", "fr": "Version"},
-            "default": {"default": "undefined"}
-         },
-"web": {"description": {"en": "Website", "fr": "Site web"},
-         "default": {"default": "http://www.patacrep.com"}
-      },
-"mail": {"description": {"en": "Email", "fr": "Adresse électronique"},
-         "default": {"default": "crep@team-on-fire.com"}
-      },
-"picture": {"description": {"en": "Cover picture", "fr": "Image de couverture"},
-            "type": "file",
-            "default": {"default": "img/treble_a"}
-         },
-"picturecopyright": {"description": {"en": "Copyright for the cover picture", "fr": "Copyright pour l'image de couverture"},
-                     "default": {"default": "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"}
-                  },
-"footer": {"description": {"en": "Footer", "fr": "Pied de page"},
-           "default": {"default": "Generated using Songbook (\\url{http://www.patacrep.com})"}
-        },
-"songnumberbgcolor": {"description": {"en": "Number Shade", "fr": "Couleur des numéros"},
-                     "type": "color",
-                     "default": {"default": "D1E4AE"}
-                  },
-"notebgcolor": {"description": {"en": "Note Shade", "fr": "Couleur des notes"},
-               "type": "color",
-               "default": {"default": "D1E4AE"}
-            },
-"indexbgcolor": {"description": {"en": "Index Shade", "fr": "Couleur d'index"},
-               "type": "color",
-               "default": {"default": "D1E4AE"}
-            },
-"titleprefixwords": {"description": {"en": "Ignore some words in the beginning of song titles",
-                                     "fr": "Ignore des mots dans le classement des chansons"},
-                     "default": {"default": ["The", "Le", "La", "L'", "A", "Au", "Ces", "De",
-                     						 "Des", "El", "Les", "Ma", "Mon", "Un"]}
-                     }
-}
+schema:
+  type: //rec
+  optional:
+    version: //str
+  required:
+    subtitle: //str
+    url: //str
+    email: //str
+    picture: //str
+    picturecopyright: //str
+    footer: //str
+    bgcolor:
+      type: //rec
+      required:
+        songnumber: //str
+        note: //str
+        index: //str
+
+default:
+  subtitle: ""
+  version: ""
+  url: http://www.patacrep.com"
+  email: crep@team-on-fire.com
+  picture: img/treble_a
+  picturecopyright: "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"
+  footer: "Generated using Songbook (\\url{http://www.patacrep.com})"
+  bgcolor:
+    songnumber: D1E4AE
+    note: D1E4AE
+    index: D1E4AE
 (* endvariables -*)
 
 (*- extends "default.tex" -*)
 
 (* block songbookpackages *)
-%! booktype, bookoptions and instruments are defined in "songs.tex"
 \usepackage[
-    ((booktype)),
-    (* for option in bookoptions *)
-    ((option)),
-    (* endfor *)
-    (* for instrument in instruments *)
-    ((instrument)),
+    (* for option in bookoptions *)((option)),
     (* endfor *)
     ]{crepbook}
 (* endblock *)
@@ -91,9 +74,9 @@
 
 \pagestyle{empty}
 
-\definecolor{SongNumberBgColor}{HTML}{((songnumberbgcolor))}
-\definecolor{NoteBgColor}{HTML}{((notebgcolor))}
-\definecolor{IndexBgColor}{HTML}{((indexbgcolor))}
+\definecolor{SongNumberBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.songnumber ))}
+\definecolor{NoteBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.note ))}
+\definecolor{IndexBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.index ))}
 
 \renewcommand{\snumbgcolor}{SongNumberBgColor}
 \renewcommand{\notebgcolor}{NoteBgColor}
@@ -111,13 +94,13 @@
     ]{hyperref}
 
 
-\subtitle{((subtitle))}
-(* if version!="undefined" -*)
-    \version{((version))}
+\subtitle{(( _template["patacrep.tex"].subtitle ))}
+(* if _template["patacrep.tex"].version -*)
+    \version{(( _template["patacrep.tex"].version ))}
 (* endif *)
-\mail{((mail))}
-\web{((web))}
-\picture{((picture))}
-\picturecopyright{((picturecopyright))}
-\footer{((footer))}
+\mail{(( _template["patacrep.tex"].email ))}
+\web{(( _template["patacrep.tex"].url ))}
+\picture{(( _template["patacrep.tex"].picture ))}
+\picturecopyright{(( _template["patacrep.tex"].picturecopyright ))}
+\footer{(( _template["patacrep.tex"].footer ))}
 (* endblock *)

From a2688edd5723837106212544ed036d10cd7bf0aa Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 11:36:14 +0100
Subject: [PATCH 30/48] template default and schema are managed inside the
 templates

---
 .../data/templates/default_songbook.sb.yml    | 21 --------------
 patacrep/data/templates/songbook_schema.yml   | 28 -------------------
 2 files changed, 49 deletions(-)

diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
index a9c7664a..67a9a464 100644
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ b/patacrep/data/templates/default_songbook.sb.yml
@@ -39,24 +39,3 @@ titles: # Comment sont analysés les titres
   - Ma
   - Mon
   - Un
-
-#template: # Des choses spécifiques au template
-  # latex: # Des choses spécifiques au LaTeX
-  #   classoptions: STRING
-
-  # # Peut dépendre fortement du template
-  # color: # Des couleurs
-  #   songnumber: STRING
-  #   notebg: STRING
-  #   indexbg: STRING
-
-  # titlepage: #Configuration de la page de garde
-  #   title: STRING
-  #   author: STRING
-  #   subtitle: STRING
-  #   version: STRING
-  #   url: STRING
-  #   email: STRING
-  #   picture: STRING
-  #   picturecopyright: STRING
-  #   footer: STRING
\ No newline at end of file
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index d569f983..cb4463df 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -78,31 +78,3 @@ required:
           - type: //arr
             contents: //str
           - type: //nil
-  # template:
-  #   _description: "Des choses spécifiques au template"
-  #   type: //rec
-  #   required:
-  #     file: //str
-  #   optional:
-  #     latex:
-  #       type: //rec
-  #       required:
-  #         classoptions: //str
-  #     color:
-  #       type: //rec
-  #       required:
-  #         songnumber: //str
-  #         notebg: //str
-  #         indexbg: //str
-  #     titlepage:
-  #       type: //rec
-  #       required:
-  #         title: //str
-  #         author: //str
-  #         subtitle: //str
-  #         version: //str
-  #         url: //str
-  #         email: //str
-  #         picture: //str
-  #         picturecopyright: //str
-  #         footer: //str

From 33eded681fa5d2a9bb2e675a109473b35b750733 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 11:40:07 +0100
Subject: [PATCH 31/48] Remove _description key

---
 patacrep/build.py                           | 1 -
 patacrep/data/templates/default.tex         | 7 ++++---
 patacrep/data/templates/songbook_schema.yml | 2 --
 patacrep/templates.py                       | 1 -
 4 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index a3ce2984..29b2502e 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -57,7 +57,6 @@ class Songbook(object):
         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 = utils.remove_keys(schema_struct, ['_description'])
         utils.validate_yaml_schema(raw_songbook, schema_struct)
 
     def _set_datadir(self):
diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index 3d9aef15..f02d0895 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -23,19 +23,20 @@ schema:
   type: //rec
   required:
     title:
-      _description: _("Title")
       type: //str
     author:
-      _description: _("Author")
       type: //str
   optional:
     classoptions:
-      _description: _("LaTeX class options")
       type: //arr
       contents: //str
 default:
   title: "Guitar songbook"
   author: "The Patacrep Team"
+description.en:
+  title: "Title"
+  author: "Author"
+  classoptions: "LaTeX class options"
 (* endvariables -*)
 
 (*- extends "songs.tex" -*)
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
index cb4463df..ee75be64 100644
--- a/patacrep/data/templates/songbook_schema.yml
+++ b/patacrep/data/templates/songbook_schema.yml
@@ -47,7 +47,6 @@ required:
           - type: //str
             value: "solfedge"
   authors:
-    _description: "Comment sont analysés les auteurs"
     type: //rec
     required:
       separators:
@@ -69,7 +68,6 @@ required:
             contents: //str
           - type: //nil
   titles:
-    _description: "Comment sont analysés les titres"
     type: //rec
     required:
       prefix:
diff --git a/patacrep/templates.py b/patacrep/templates.py
index 57c34fda..3ba3867d 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -148,7 +148,6 @@ class TexBookRenderer(Renderer):
         '''Get the default value for the parameter, according to the language.
         '''
         schema = parameter.get('schema', {}).copy()
-        schema = utils.remove_keys(schema, ['_description'])
 
         data = utils.DictOfDict(parameter.get('default', {}))
         data.update(user_config)

From 727c6a20c6c3bb087019937715be9c2746ada028 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 12:06:52 +0100
Subject: [PATCH 32/48] Merge schema and default arguments

---
 patacrep/build.py                             |  28 ++--
 .../data/templates/default_songbook.sb.yml    |  41 ------
 patacrep/data/templates/songbook_model.yml    | 121 ++++++++++++++++++
 patacrep/data/templates/songbook_schema.yml   |  78 -----------
 patacrep/songbook/__main__.py                 |  10 +-
 test/test_song/test_parser.py                 |   5 +-
 6 files changed, 143 insertions(+), 140 deletions(-)
 delete mode 100644 patacrep/data/templates/default_songbook.sb.yml
 create mode 100644 patacrep/data/templates/songbook_model.yml
 delete mode 100644 patacrep/data/templates/songbook_schema.yml

diff --git a/patacrep/build.py b/patacrep/build.py
index 29b2502e..01553bc0 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -44,21 +44,15 @@ class Songbook(object):
     def __init__(self, raw_songbook, basename):
         super().__init__()
         self.config = raw_songbook
-        self.validate_config_schema(raw_songbook)
+
+        # Validate config
+        schema = config_model('schema')
+        utils.validate_yaml_schema(raw_songbook, schema)
+
         self.basename = basename
         # Some special keys have their value processed.
         self._set_datadir()
 
-    @staticmethod
-    def validate_config_schema(raw_songbook):
-        """
-        Check that the songbook config respects the excepted songbook schema
-        """
-        schema_path = pkg_datapath('templates', 'songbook_schema.yml')
-        with encoding.open_read(schema_path) as schema_file:
-            schema_struct = yaml.load(schema_file)
-        utils.validate_yaml_schema(raw_songbook, schema_struct)
-
     def _set_datadir(self):
         """Set the default values for datadir"""
         abs_datadir = []
@@ -335,3 +329,15 @@ class SongbookBuilder(object):
                     os.unlink(self.basename + ext)
                 except Exception as exception:
                     raise errors.CleaningError(self.basename + ext, exception)
+
+
+def config_model(*args):
+    """Get the model structure with schema and default options"""
+    model_path = pkg_datapath('templates', 'songbook_model.yml')
+    with encoding.open_read(model_path) as model_file:
+        data = yaml.load(model_file)
+
+    while data and args:
+        name, *args = args
+        data = data.get(name)
+    return data
diff --git a/patacrep/data/templates/default_songbook.sb.yml b/patacrep/data/templates/default_songbook.sb.yml
deleted file mode 100644
index 67a9a464..00000000
--- a/patacrep/data/templates/default_songbook.sb.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-book: 
-  lang: en
-  encoding: utf-8
-  pictures: yes
-  template: default.tex
-  onesongperpage: no
-
-chords: # Options relatives aux accords
-  show: yes
-  diagramreminder: important
-  diagrampage: yes
-  repeatchords: yes
-  lilypond: no
-  tablatures: no
-  instrument: guitar
-  notation: alphascale
-
-authors: # Comment sont analysés les auteurs
-  separators:
-  - and
-  ignore:
-  - unknown
-  after:
-  - by
-
-titles: # Comment sont analysés les titres
-  prefix:
-  - The
-  - Le
-  - La
-  - "L'"
-  - A
-  - Au
-  - Ces
-  - De
-  - Des
-  - El
-  - Les
-  - Ma
-  - Mon
-  - Un
diff --git a/patacrep/data/templates/songbook_model.yml b/patacrep/data/templates/songbook_model.yml
new file mode 100644
index 00000000..1f588996
--- /dev/null
+++ b/patacrep/data/templates/songbook_model.yml
@@ -0,0 +1,121 @@
+schema:
+  type: //rec
+  optional:
+    content: //str
+    template: //any
+  required:
+    _cache: //bool
+    _datadir:
+      type: //arr
+      contents: //str
+    book:
+      type: //rec
+      required:
+        encoding: //str
+        lang: //str
+        pictures: //bool
+        template: //str
+        onesongperpage: //bool
+    chords:
+      type: //rec
+      required:
+        show: //bool
+        diagrampage: //bool
+        repeatchords: //bool
+        lilypond: //bool
+        tablatures: //bool
+        diagramreminder:
+          type: //any
+          of:
+            - type: //str
+              value: "none"
+            - type: //str
+              value: "important"
+            - type: //str
+              value: "all"
+        instrument:
+          type: //any
+          of:
+            - type: //str
+              value: "guitar"
+            - type: //str
+              value: "ukulele"
+        notation:
+          type: //any
+          of:
+            - type: //str
+              value: "alphascale"
+            - type: //str
+              value: "solfedge"
+    authors:
+      type: //rec
+      required:
+        separators:
+          type: //any
+          of:
+            - type: //arr
+              contents: //str
+            - type: //nil
+        ignore:
+          type: //any
+          of:
+            - type: //arr
+              contents: //str
+            - type: //nil
+        after:
+          type: //any
+          of:
+            - type: //arr
+              contents: //str
+            - type: //nil
+    titles:
+      type: //rec
+      required:
+        prefix:
+          type: //any
+          of:
+            - type: //arr
+              contents: //str
+            - type: //nil
+default:
+  book:
+    lang: en
+    encoding: utf-8
+    pictures: yes
+    template: default.tex
+    onesongperpage: no
+
+  chords: # Options relatives aux accords
+    show: yes
+    diagramreminder: important
+    diagrampage: yes
+    repeatchords: yes
+    lilypond: no
+    tablatures: no
+    instrument: guitar
+    notation: alphascale
+
+  authors: # Comment sont analysés les auteurs
+    separators:
+    - and
+    ignore:
+    - unknown
+    after:
+    - by
+
+  titles: # Comment sont analysés les titres
+    prefix:
+    - The
+    - Le
+    - La
+    - "L'"
+    - A
+    - Au
+    - Ces
+    - De
+    - Des
+    - El
+    - Les
+    - Ma
+    - Mon
+    - Un
diff --git a/patacrep/data/templates/songbook_schema.yml b/patacrep/data/templates/songbook_schema.yml
deleted file mode 100644
index ee75be64..00000000
--- a/patacrep/data/templates/songbook_schema.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-type: //rec
-optional:
-  content: //str
-  template: //any
-required:
-  _cache: //bool
-  _datadir:
-    type: //arr
-    contents: //str
-  book:
-    type: //rec
-    required:
-      encoding: //str
-      lang: //str
-      pictures: //bool
-      template: //str
-      onesongperpage: //bool
-  chords:
-    type: //rec
-    required:
-      show: //bool
-      diagrampage: //bool
-      repeatchords: //bool
-      lilypond: //bool
-      tablatures: //bool
-      diagramreminder:
-        type: //any
-        of:
-          - type: //str
-            value: "none"
-          - type: //str
-            value: "important"
-          - type: //str
-            value: "all"
-      instrument:
-        type: //any
-        of:
-          - type: //str
-            value: "guitar"
-          - type: //str
-            value: "ukulele"
-      notation:
-        type: //any
-        of:
-          - type: //str
-            value: "alphascale"
-          - type: //str
-            value: "solfedge"
-  authors:
-    type: //rec
-    required:
-      separators:
-        type: //any
-        of:
-          - type: //arr
-            contents: //str
-          - type: //nil
-      ignore:
-        type: //any
-        of:
-          - type: //arr
-            contents: //str
-          - type: //nil
-      after:
-        type: //any
-        of:
-          - type: //arr
-            contents: //str
-          - type: //nil
-  titles:
-    type: //rec
-    required:
-      prefix:
-        type: //any
-        of:
-          - type: //arr
-            contents: //str
-          - type: //nil
diff --git a/patacrep/songbook/__main__.py b/patacrep/songbook/__main__.py
index 587b1d8d..adc581c5 100644
--- a/patacrep/songbook/__main__.py
+++ b/patacrep/songbook/__main__.py
@@ -8,10 +8,10 @@ import textwrap
 import sys
 import yaml
 
-from patacrep.build import SongbookBuilder, DEFAULT_STEPS
+from patacrep.build import SongbookBuilder, DEFAULT_STEPS, config_model
 from patacrep.utils import yesno, DictOfDict
 from patacrep import __version__
-from patacrep import errors, pkg_datapath
+from patacrep import errors
 import patacrep.encoding
 
 # Logging configuration
@@ -133,11 +133,6 @@ def main():
 
     basename = os.path.basename(songbook_path)[:-3]
 
-    # Load the default songbook config
-    default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
-    with patacrep.encoding.open_read(default_songbook_path) as default_songbook_file:
-        default_songbook = DictOfDict(yaml.load(default_songbook_file))
-
     # Load the user songbook config
     try:
         with patacrep.encoding.open_read(songbook_path) as songbook_file:
@@ -154,6 +149,7 @@ def main():
         sys.exit(1)
 
     # Merge the default and user configs
+    default_songbook = DictOfDict(config_model('default'))
     default_songbook.update(user_songbook)
     songbook = dict(default_songbook)
 
diff --git a/test/test_song/test_parser.py b/test/test_song/test_parser.py
index 6eefe382..0e7f4dcd 100644
--- a/test/test_song/test_parser.py
+++ b/test/test_song/test_parser.py
@@ -12,6 +12,7 @@ import yaml
 
 from patacrep import files, pkg_datapath
 from patacrep.encoding import open_read
+from patacrep.build import config_model
 
 from .. import logging_reduced
 from .. import dynamic # pylint: disable=unused-import
@@ -76,9 +77,7 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
         """Iterate over song files to test."""
         # Setting datadir
         # Load the default songbook config
-        default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
-        with open_read(default_songbook_path) as default_songbook_file:
-            cls.config = yaml.load(default_songbook_file)
+        cls.config = config_model('default')
 
         if '_datadir' not in cls.config:
             cls.config['_datadir'] = []

From 69abfcc9eea6e80fd3ceaa4e9650ecd0f415e631 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 12:18:48 +0100
Subject: [PATCH 33/48] remove_keys is now useless

---
 patacrep/utils.py | 16 ----------------
 1 file changed, 16 deletions(-)

diff --git a/patacrep/utils.py b/patacrep/utils.py
index a4a4feeb..b9f0de88 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -78,22 +78,6 @@ def yesno(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_yaml_schema(data, schema):
     """
     Check that the data respects the schema

From 80d69a1060a921c589d586f43c31459ee83310e5 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 12:22:55 +0100
Subject: [PATCH 34/48] Remove DEFAULT_CONFIG

---
 patacrep/build.py                 | 6 ++----
 patacrep/songs/__init__.py        | 4 +---
 test/test_content/test_content.py | 2 --
 3 files changed, 3 insertions(+), 9 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 01553bc0..29388952 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -13,7 +13,7 @@ import yaml
 from patacrep import authors, content, encoding, errors, files, pkg_datapath, utils
 from patacrep.index import process_sxd
 from patacrep.templates import TexBookRenderer, iter_bookoptions
-from patacrep.songs import DataSubpath, DEFAULT_CONFIG
+from patacrep.songs import DataSubpath
 
 LOGGER = logging.getLogger(__name__)
 EOL = "\n"
@@ -76,9 +76,7 @@ class Songbook(object):
         Arguments:
         - output: a file object, in which the file will be written.
         """
-        # Updating configuration
-        config = DEFAULT_CONFIG.copy()
-        config.update(self.config)
+        config = self.config.copy()
         renderer = TexBookRenderer(
             config['book']['template'],
             config['_datadir'],
diff --git a/patacrep/songs/__init__.py b/patacrep/songs/__init__.py
index 23c7900f..c3636bca 100644
--- a/patacrep/songs/__init__.py
+++ b/patacrep/songs/__init__.py
@@ -12,8 +12,6 @@ from patacrep.authors import process_listauthors
 
 LOGGER = logging.getLogger(__name__)
 
-DEFAULT_CONFIG = {}
-
 def cached_name(datadir, filename):
     """Return the filename of the cache version of the file."""
     fullpath = os.path.abspath(os.path.join(datadir, '.cache', filename))
@@ -97,7 +95,7 @@ class Song:
 
     def __init__(self, subpath, config=None, *, datadir=None):
         if config is None:
-            config = DEFAULT_CONFIG.copy()
+            config = {}
 
         if datadir is None:
             self.datadir = ""
diff --git a/test/test_content/test_content.py b/test/test_content/test_content.py
index 2bc39d7a..868b8fc5 100644
--- a/test/test_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -105,8 +105,6 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
         with encoding.open_read(default_songbook_path) as default_songbook_file:
             config = yaml.load(default_songbook_file)
 
-        #config = DEFAULT_CONFIG.copy()
-
         datadirpaths = [os.path.join(os.path.dirname(__file__), 'datadir')]
 
         # todo : yaml and testing?

From 0204919280067da93c7512a4fe6f32410192e3e2 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 12:27:10 +0100
Subject: [PATCH 35/48] Restore test_content tests

---
 patacrep/content/include.py       |  2 +-
 test/test_content/test_content.py | 12 ++++--------
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/patacrep/content/include.py b/patacrep/content/include.py
index bfd5db5c..0dbba32c 100644
--- a/patacrep/content/include.py
+++ b/patacrep/content/include.py
@@ -46,7 +46,7 @@ def parse(keyword, config, argument, contentlist):
         try:
             with encoding.open_read(
                 filepath,
-                encoding=config['encoding']
+                encoding=config['book']['encoding']
                 ) as content_file:
                 new_content = json.load(content_file)
         except Exception as error: # pylint: disable=broad-except
diff --git a/test/test_content/test_content.py b/test/test_content/test_content.py
index 868b8fc5..4d824346 100644
--- a/test/test_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -2,8 +2,6 @@
 
 # pylint: disable=too-few-public-methods
 
-#pylint: skip-file
-
 import glob
 import os
 import unittest
@@ -11,9 +9,10 @@ import json
 
 import yaml
 
-from patacrep.songs import DataSubpath, DEFAULT_CONFIG
+from patacrep.songs import DataSubpath
 from patacrep import content, encoding, files, pkg_datapath
 from patacrep.content import song, section, songsection, tex
+from patacrep.build import config_model
 
 from .. import logging_reduced
 from .. import dynamic # pylint: disable=unused-import
@@ -35,7 +34,6 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
 
     @classmethod
     def _iter_testmethods(cls):
-        return
         """Iterate over dynamically generated test methods"""
         for source in sorted(glob.glob(os.path.join(
                 os.path.dirname(__file__),
@@ -101,14 +99,12 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
         """Generate the config to process the content"""
 
         # Load the default songbook config
-        default_songbook_path = pkg_datapath('templates', 'default_songbook.sb.yml')
-        with encoding.open_read(default_songbook_path) as default_songbook_file:
-            config = yaml.load(default_songbook_file)
+        config = config_model('default')
 
         datadirpaths = [os.path.join(os.path.dirname(__file__), 'datadir')]
 
         # todo : yaml and testing?
-        config['datadir'] = datadirpaths
+        config['_datadir'] = datadirpaths
 
         config['_songdir'] = [
             DataSubpath(path, 'songs')

From 666f4d38f4944d085199bf66440c1c3c60db2a39 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:26:19 +0100
Subject: [PATCH 36/48] [pylint] Remove unused imports

---
 test/test_content/test_content.py | 4 +---
 test/test_song/test_parser.py     | 4 +---
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/test/test_content/test_content.py b/test/test_content/test_content.py
index 4d824346..83e78797 100644
--- a/test/test_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -7,10 +7,8 @@ import os
 import unittest
 import json
 
-import yaml
-
 from patacrep.songs import DataSubpath
-from patacrep import content, encoding, files, pkg_datapath
+from patacrep import content, files
 from patacrep.content import song, section, songsection, tex
 from patacrep.build import config_model
 
diff --git a/test/test_song/test_parser.py b/test/test_song/test_parser.py
index 0e7f4dcd..1cdfae5b 100644
--- a/test/test_song/test_parser.py
+++ b/test/test_song/test_parser.py
@@ -8,9 +8,7 @@ import os
 import unittest
 from pkg_resources import resource_filename
 
-import yaml
-
-from patacrep import files, pkg_datapath
+from patacrep import files
 from patacrep.encoding import open_read
 from patacrep.build import config_model
 

From bc163b146dc6bfdc21b507c49d927483d0dc35a3 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:35:18 +0100
Subject: [PATCH 37/48] Correct content parameter

---
 examples/example-all.yaml.sb               | 19 ++++++-------------
 patacrep/data/templates/songbook_model.yml |  2 +-
 2 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/examples/example-all.yaml.sb b/examples/example-all.yaml.sb
index 4f287c6d..c121ca14 100644
--- a/examples/example-all.yaml.sb
+++ b/examples/example-all.yaml.sb
@@ -1,23 +1,16 @@
-# bookoptions: 
-#   - "diagram"
-#   - "repeatchords"
-#   - "pictures"
-# booktype: "chorded"
-# datadir: "."
-# template: "patacrep.tex"
-# lang: "fr"
-# encoding: "utf8"
 book:
   lang: fr
+  encoding: utf8
+  template: patacrep.tex
   datadir: "."
   pictures: yes
-  #type: chorded
 chords:
-  show: true
-  diagramreminder: none
+  show: yes
+  diagramreminder: all
+  repeatchords: yes
 
 authors:
   separators:
     - "and"
     - "et"
-content: '[["sorted"]]'
\ No newline at end of file
+content: [["sorted"]]
\ No newline at end of file
diff --git a/patacrep/data/templates/songbook_model.yml b/patacrep/data/templates/songbook_model.yml
index 1f588996..7567de47 100644
--- a/patacrep/data/templates/songbook_model.yml
+++ b/patacrep/data/templates/songbook_model.yml
@@ -1,7 +1,7 @@
 schema:
   type: //rec
   optional:
-    content: //str
+    content: //any
     template: //any
   required:
     _cache: //bool

From b93062921197b746d4f70d63f35020cf68a67c2c Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:43:10 +0100
Subject: [PATCH 38/48] Rename template_var

---
 patacrep/data/templates/patacrep.tex | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/patacrep/data/templates/patacrep.tex b/patacrep/data/templates/patacrep.tex
index f1406d4e..8023ed3b 100644
--- a/patacrep/data/templates/patacrep.tex
+++ b/patacrep/data/templates/patacrep.tex
@@ -74,9 +74,11 @@ default:
 
 \pagestyle{empty}
 
-\definecolor{SongNumberBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.songnumber ))}
-\definecolor{NoteBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.note ))}
-\definecolor{IndexBgColor}{HTML}{(( _template["patacrep.tex"].bgcolor.index ))}
+(*- set template_var = _template["patacrep.tex"] -*)
+
+\definecolor{SongNumberBgColor}{HTML}{(( template_var.bgcolor.songnumber ))}
+\definecolor{NoteBgColor}{HTML}{(( template_var.bgcolor.note ))}
+\definecolor{IndexBgColor}{HTML}{(( template_var.bgcolor.index ))}
 
 \renewcommand{\snumbgcolor}{SongNumberBgColor}
 \renewcommand{\notebgcolor}{NoteBgColor}
@@ -94,13 +96,13 @@ default:
     ]{hyperref}
 
 
-\subtitle{(( _template["patacrep.tex"].subtitle ))}
-(* if _template["patacrep.tex"].version -*)
-    \version{(( _template["patacrep.tex"].version ))}
+\subtitle{(( template_var.subtitle ))}
+(* if template_var.version -*)
+    \version{(( template_var.version ))}
 (* endif *)
-\mail{(( _template["patacrep.tex"].email ))}
-\web{(( _template["patacrep.tex"].url ))}
-\picture{(( _template["patacrep.tex"].picture ))}
-\picturecopyright{(( _template["patacrep.tex"].picturecopyright ))}
-\footer{(( _template["patacrep.tex"].footer ))}
+\mail{(( template_var.email ))}
+\web{(( template_var.url ))}
+\picture{(( template_var.picture ))}
+\picturecopyright{(( template_var.picturecopyright ))}
+\footer{(( template_var.footer ))}
 (* endblock *)

From a46e42bfce3be0da203724013199ebe79de1364b Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:46:49 +0100
Subject: [PATCH 39/48] Demonstrate color arguments

---
 examples/example-all.yaml.sb         | 12 +++++++++++-
 patacrep/data/templates/patacrep.tex | 12 ++++++++++--
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/examples/example-all.yaml.sb b/examples/example-all.yaml.sb
index c121ca14..01a95015 100644
--- a/examples/example-all.yaml.sb
+++ b/examples/example-all.yaml.sb
@@ -13,4 +13,14 @@ authors:
   separators:
     - "and"
     - "et"
-content: [["sorted"]]
\ No newline at end of file
+content: [["sorted"]]
+
+template:
+  patacrep.tex:
+    color:
+      songlink: FF0000
+      hyperlink: 0000FF
+    bgcolor:
+      note: D1E4AE
+      songnumber: AED1E4
+      index: E4AED1 #not enough songs to see it
\ No newline at end of file
diff --git a/patacrep/data/templates/patacrep.tex b/patacrep/data/templates/patacrep.tex
index 8023ed3b..48cb7bd4 100644
--- a/patacrep/data/templates/patacrep.tex
+++ b/patacrep/data/templates/patacrep.tex
@@ -30,6 +30,11 @@ schema:
     picture: //str
     picturecopyright: //str
     footer: //str
+    color:
+      type: //rec
+      required:
+        songlink: //str
+        hyperlink: //str
     bgcolor:
       type: //rec
       required:
@@ -45,6 +50,9 @@ default:
   picture: img/treble_a
   picturecopyright: "Dbolton \\url{http://commons.wikimedia.org/wiki/User:Dbolton}"
   footer: "Generated using Songbook (\\url{http://www.patacrep.com})"
+  color:
+    songlink: 4e9a06
+    hyperlink: 204a87
   bgcolor:
     songnumber: D1E4AE
     note: D1E4AE
@@ -84,8 +92,8 @@ default:
 \renewcommand{\notebgcolor}{NoteBgColor}
 \renewcommand{\idxbgcolor}{IndexBgColor}
 
-\definecolor{tango-green-3}{HTML}{4e9a06}
-\definecolor{tango-blue-3}{HTML}{204a87}
+\definecolor{tango-green-3}{HTML}{(( template_var.color.songlink ))}
+\definecolor{tango-blue-3}{HTML}{(( template_var.color.hyperlink ))}
 \usepackage[
     bookmarks,
     bookmarksopen,

From 55d67014035ee7a0c93a6cf1ac96ee3dcfc56420 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:49:01 +0100
Subject: [PATCH 40/48] Validate the config first

---
 patacrep/build.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 29388952..73ba64f3 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -43,12 +43,11 @@ class Songbook(object):
 
     def __init__(self, raw_songbook, basename):
         super().__init__()
-        self.config = raw_songbook
-
         # Validate config
         schema = config_model('schema')
         utils.validate_yaml_schema(raw_songbook, schema)
 
+        self.config = raw_songbook
         self.basename = basename
         # Some special keys have their value processed.
         self._set_datadir()

From c235cea7eef79aa2adec4a8a30d745ab429139fa Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:53:55 +0100
Subject: [PATCH 41/48] Use template_var for default.tex

---
 patacrep/data/templates/default.tex | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/patacrep/data/templates/default.tex b/patacrep/data/templates/default.tex
index f02d0895..5fbf5c10 100644
--- a/patacrep/data/templates/default.tex
+++ b/patacrep/data/templates/default.tex
@@ -41,10 +41,11 @@ description.en:
 
 (*- extends "songs.tex" -*)
 (*- set indexes = "titleidx,authidx" -*)
+(*- set template_var = _template["default.tex"] -*)
 
 (* block documentclass *)
 \documentclass[
-    (* for option in _template["default.tex"].classoptions *)
+    (* for option in template_var.classoptions *)
     ((option)),
     (* endfor *)
     ]{article}
@@ -56,8 +57,8 @@ description.en:
 
 \usepackage{chords}
 
-\title{(( _template["default.tex"].title ))}
-\author{(( _template["default.tex"].author ))}
+\title{(( template_var.title ))}
+\author{(( template_var.author ))}
 
 \newindex{titleidx}{((filename))_title}
 \newauthorindex{authidx}{((filename))_auth}

From 4ec1152feae36c47620348b1a7467959673eee21 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 16:56:35 +0100
Subject: [PATCH 42/48] Explain statement

---
 patacrep/index.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/patacrep/index.py b/patacrep/index.py
index 346c1acd..c29b88ab 100644
--- a/patacrep/index.py
+++ b/patacrep/index.py
@@ -77,6 +77,7 @@ class Index(object):
 
     def add_keyword(self, key, word):
         """Add 'word' to self.keywords[key]."""
+        # Because LaTeX uses 'sep'
         if key == 'sep':
             key = 'separators'
         if key not in self.keywords:

From 81eb55811c08835eb399cbec39703e47e43463c8 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 17:00:22 +0100
Subject: [PATCH 43/48] Correct indentation

---
 patacrep/templates.py | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/patacrep/templates.py b/patacrep/templates.py
index 3ba3867d..f86b1d99 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -176,11 +176,12 @@ class TexBookRenderer(Renderer):
             if subtemplate in skip:
                 continue
             subtemplate = self.jinjaenv.get_template(subtemplate)
-            variables.update(self.get_template_variables(
-                subtemplate,
-                skip + templates
+            variables.update(
+                self.get_template_variables(
+                    subtemplate,
+                    skip + templates
+                    )
                 )
-                            )
         return variables
 
     def parse_template(self, template):

From 2547a750a4e8fb402133731f8fef09fd69020e64 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Fri, 18 Dec 2015 17:35:38 +0100
Subject: [PATCH 44/48] Remove useless comment

---
 test/test_content/test_content.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/test_content/test_content.py b/test/test_content/test_content.py
index 83e78797..23d498bc 100644
--- a/test/test_content/test_content.py
+++ b/test/test_content/test_content.py
@@ -101,7 +101,6 @@ class FileTest(unittest.TestCase, metaclass=dynamic.DynamicTest):
 
         datadirpaths = [os.path.join(os.path.dirname(__file__), 'datadir')]
 
-        # todo : yaml and testing?
         config['_datadir'] = datadirpaths
 
         config['_songdir'] = [

From 342ffe8919ffc42ff1b9055b412b26d07fe47ce0 Mon Sep 17 00:00:00 2001
From: Louis <spalax@gresille.org>
Date: Tue, 22 Dec 2015 19:53:11 +0100
Subject: [PATCH 45/48] Function `compile_authwords()` no longer has side
 effects

Closes #185
---
 patacrep/authors.py | 30 +++++++++++-------------------
 patacrep/build.py   |  5 +----
 2 files changed, 12 insertions(+), 23 deletions(-)

diff --git a/patacrep/authors.py b/patacrep/authors.py
index 12715ed2..c8b65361 100644
--- a/patacrep/authors.py
+++ b/patacrep/authors.py
@@ -5,8 +5,6 @@ import re
 
 LOGGER = logging.getLogger(__name__)
 
-AUTHWORDS_KEYS = ["after", "ignore", "separators"]
-
 RE_AFTER = r"^.*\b{}\b(.*)$"
 RE_SEPARATOR = r"^(.*)\b *{} *(\b.*)?$"
 
@@ -15,23 +13,17 @@ def compile_authwords(authwords):
 
     This regexp will later be used to match these words in authors strings.
     """
-    # Fill missing values
-    for key in AUTHWORDS_KEYS:
-        if key not in authwords:
-            authwords[key] = []
-
-    # Compilation
-    authwords['after'] = [
-        re.compile(RE_AFTER.format(word), re.LOCALE)
-        for word in authwords['after']
-        ]
-    authwords['separators'] = [
-        re.compile(RE_SEPARATOR.format(word), re.LOCALE)
-        for word in ([" %s" % word for word in authwords['separators']] + [',', ';'])
-        ]
-
-    return authwords
-
+    return {
+        'ignore': authwords.get('ignore', []),
+        'after': [
+            re.compile(RE_AFTER.format(word), re.LOCALE)
+            for word in authwords['after']
+            ],
+        'separators': [
+            re.compile(RE_SEPARATOR.format(word), re.LOCALE)
+            for word in ([" %s" % word for word in authwords['separators']] + [',', ';'])
+            ],
+        }
 
 def split_author_names(string):
     r"""Split author between first and last name.
diff --git a/patacrep/build.py b/patacrep/build.py
index 73ba64f3..ed24664d 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -1,7 +1,6 @@
 """Build a songbook, according to parameters found in a .sb file."""
 
 import codecs
-import copy
 import glob
 import logging
 import threading
@@ -85,9 +84,7 @@ class Songbook(object):
 
         config['_template'] = renderer.get_all_variables(self.config.get('template', {}))
 
-        config['_compiled_authwords'] = authors.compile_authwords(
-            copy.deepcopy(config['authors'])
-            )
+        config['_compiled_authwords'] = authors.compile_authwords(config['authors'])
 
         # Loading custom plugins
         config['_content_plugins'] = files.load_plugins(

From 7c37a848118a302632935a97bd0ae1f7ef667513 Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 24 Dec 2015 08:32:01 +0100
Subject: [PATCH 46/48] Useless dict.copy

---
 patacrep/templates.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/patacrep/templates.py b/patacrep/templates.py
index f86b1d99..370b3d88 100644
--- a/patacrep/templates.py
+++ b/patacrep/templates.py
@@ -147,7 +147,7 @@ class TexBookRenderer(Renderer):
     def _get_variables(parameter, user_config):
         '''Get the default value for the parameter, according to the language.
         '''
-        schema = parameter.get('schema', {}).copy()
+        schema = parameter.get('schema', {})
 
         data = utils.DictOfDict(parameter.get('default', {}))
         data.update(user_config)

From b0ece3246704acc82a4421f218b538cf1dc528fa Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Thu, 24 Dec 2015 08:38:49 +0100
Subject: [PATCH 47/48] Correct docstring

---
 patacrep/utils.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/patacrep/utils.py b/patacrep/utils.py
index b9f0de88..7ddd0eca 100644
--- a/patacrep/utils.py
+++ b/patacrep/utils.py
@@ -79,8 +79,9 @@ def yesno(string):
         ))
 
 def validate_yaml_schema(data, schema):
-    """
-    Check that the data respects the schema
+    """Check that the data 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(schema)

From 1c4b48c9e754f325699a65dcfbf1202eb9f0d7ec Mon Sep 17 00:00:00 2001
From: Oliverpool <oliverpool@hotmail.fr>
Date: Mon, 4 Jan 2016 18:58:51 +0100
Subject: [PATCH 48/48] Simplify lilypond check

---
 patacrep/build.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/patacrep/build.py b/patacrep/build.py
index 94416383..96a09424 100644
--- a/patacrep/build.py
+++ b/patacrep/build.py
@@ -148,9 +148,7 @@ class Songbook:
 
     def requires_lilypond(self):
         """Tell if lilypond is part of the bookoptions"""
-        return ('chords' in self._config
-                and 'lilypond' in self._config['chords']
-                and self._config['chords']['lilypond'])
+        return 'lilypond' in iter_bookoptions(self._config)
 
 def _log_pipe(pipe):
     """Log content from `pipe`."""