diff --git a/beets/util/confit.py b/beets/util/confit.py index 73ae97abc..7b5d39f29 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -413,6 +413,12 @@ class ConfigView(object): """ return self.get(StrSeq(split=split)) + def as_pairs(self, default_value=None): + """Get the value as a sequence of pairs of two strings. Equivalent to + `get(Pairs())`. + """ + return self.get(Pairs(default_value=default_value)) + def as_str(self): """Get the value as a (Unicode) string. Equivalent to `get(unicode)` on Python 2 and `get(str)` on Python 3. @@ -1242,30 +1248,75 @@ class StrSeq(Template): super(StrSeq, self).__init__() self.split = split + def _convert_value(self, x, view): + if isinstance(x, STRING): + return x + elif isinstance(x, bytes): + return x.decode('utf-8', 'ignore') + else: + self.fail(u'must be a list of strings', view, True) + def convert(self, value, view): if isinstance(value, bytes): value = value.decode('utf-8', 'ignore') if isinstance(value, STRING): if self.split: - return value.split() + value = value.split() else: - return [value] + value = [value] + else: + try: + value = list(value) + except TypeError: + self.fail(u'must be a whitespace-separated string or a list', + view, True) + return [self._convert_value(v, view) for v in value] + + +class Pairs(StrSeq): + """A template for ordered key-value pairs. + + This can either be given with the same syntax as for `StrSeq` (i.e. without + values), or as a list of strings and/or single-element mappings such as:: + + - key: value + - [key, value] + - key + + The result is a list of two-element tuples. If no value is provided, the + `default_value` will be returned as the second element. + """ + + def __init__(self, default_value=None): + """Create a new template. + + `default` is the dictionary value returned for items that are not + a mapping, but a single string. + """ + super(Pairs, self).__init__(split=True) + self.default_value = default_value + + def _convert_value(self, x, view): try: - value = list(value) - except TypeError: - self.fail(u'must be a whitespace-separated string or a list', - view, True) - - def convert(x): - if isinstance(x, STRING): - return x - elif isinstance(x, bytes): - return x.decode('utf-8', 'ignore') + return (super(Pairs, self)._convert_value(x, view), + self.default_value) + except ConfigTypeError: + if isinstance(x, collections.Mapping): + if len(x) != 1: + self.fail(u'must be a single-element mapping', view, True) + k, v = iter_first(x.items()) + elif isinstance(x, collections.Sequence): + if len(x) != 2: + self.fail(u'must be a two-element list', view, True) + k, v = x else: - self.fail(u'must be a list of strings', view, True) - return list(map(convert, value)) + # Is this even possible? -> Likely, if some !directive cause + # YAML to parse this to some custom type. + self.fail(u'must be a single string, mapping, or a list' + str(x), view, True) + return (super(Pairs, self)._convert_value(k, view), + super(Pairs, self)._convert_value(v, view)) class Filename(Template):