#!/usr/bin/env python
# coding=utf-8
# aeneas is a Python/C library and a set of tools
# to automagically synchronize audio and text (aka forced alignment)
#
# Copyright (C) 2012-2013, Alberto Pettarin (www.albertopettarin.it)
# Copyright (C) 2013-2015, ReadBeyond Srl (www.readbeyond.it)
# Copyright (C) 2015-2017, Alberto Pettarin (www.albertopettarin.it)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module contains the following classes:
* :class:`~aeneas.configuration.Configuration`
which is a dictionary with a fixed set of keys,
possibly with default values and key aliases.
.. versionadded:: 1.4.1
"""
from __future__ import absolute_import
from __future__ import print_function
from copy import deepcopy
from aeneas.exacttiming import Decimal
from aeneas.exacttiming import TimeValue
import aeneas.globalconstants as gc
import aeneas.globalfunctions as gf
[docs]class Configuration(object):
"""
A generic configuration object, that is,
a dictionary with a fixed set of keys,
each with a type, a default value, and possibly aliases.
Keys are (unique) Unicode strings.
Values are stored as Unicode strings (or ``None``), and casted
to the type of the field (``int``, ``float``,
``bool``, :class:`~aeneas.exacttiming.TimeValue`, etc.)
when accessed.
For ``bool`` keys, values listed in
:data:`~aeneas.configuration.Configuration.TRUE_ALIASES`
are considered equivalent to a ``True`` value.
If ``config_string`` is not ``None``, the given string will be parsed
and ``key=value`` pairs will be stored in the object,
provided that ``key`` is listed in :data:`~aeneas.configuration.Configuration.FIELDS`.
:param string config_string: the configuration string to be parsed
:raises: TypeError: if ``config_string`` is not ``None`` and it is not a Unicode string
:raises: KeyError: if trying to access a key not listed above
"""
FIELDS = [
#
# in subclasses, create fields like this:
# (field_name, (default_value, conversion_function, [alias1, alias2, ...], human_description))
#
# examples:
# (gc.FOO, (None, None, ["foo"], u"path to foo"))
# (gc.BAR, (0.0, float, ["bar", "barrr"], u"bar threshold"))
# (gc.BAZ, (None, TimeValue, ["baz"], u"duration, in seconds, of baz"))
#
]
"""
The fields, that is, key names each with associated
default value, type, and possibly aliases,
of this object.
"""
TRUE_ALIASES = [True, u"TRUE", u"True", u"true", u"YES", u"Yes", u"yes", u"1", 1]
"""
Aliases for a ``True`` value for ``bool`` fields
"""
TAG = u"Configuration"
def __init__(self, config_string=None):
if (config_string is not None) and (not gf.is_unicode(config_string)):
raise TypeError(u"config_string is not a Unicode string")
# set dictionaries up to keep the config data
self.data = {}
self.types = {}
self.aliases = {}
self.desc = {}
for (field, info) in self.FIELDS:
(fdefault, ftype, faliases, fdesc) = info
self.data[field] = fdefault
self.types[field] = ftype
self.desc[field] = fdesc
for alias in faliases:
self.aliases[alias] = field
if config_string is not None:
# strip leading/trailing " or ' characters
if (
(len(config_string) > 0) and
(config_string[0] == config_string[-1]) and
(config_string[0] in [u"\"", u"'"])
):
config_string = config_string[1:-1]
# populate values from config_string,
# ignoring keys not present in FIELDS
properties = gf.config_string_to_dict(config_string)
for key in set(properties.keys()) & set(self.data.keys()):
self.data[key] = properties[key]
def __contains__(self, key):
return (key in self.data) or (key in self.aliases)
def __setitem__(self, key, value):
if key in self.aliases:
key = self.aliases[key]
if key in self.data:
self.data[key] = value
else:
raise KeyError(key)
def __getitem__(self, key):
if key in self.aliases:
key = self.aliases[key]
if key in self.data:
return self._cast(key, self.data[key])
else:
raise KeyError(key)
def __unicode__(self):
return u"\n".join([u"%s: '%s'" % (fn, self.data[fn]) for fn in sorted(self.data.keys())])
def __str__(self):
return gf.safe_str(self.__unicode__())
def _cast(self, key, value):
if (value is not None) and (self.types[key] is not None):
if self.types[key] is bool:
return value in self.TRUE_ALIASES
else:
return self.types[key](value)
return value
[docs] def clone(self):
"""
Return a deep copy of this configuration object.
.. versionadded:: 1.7.0
:rtype: :class:`~aeneas.configuration.Configuration`
"""
return deepcopy(self)
@property
def config_string(self):
"""
Build the storable string corresponding
to this configuration object.
:rtype: string
"""
return (gc.CONFIG_STRING_SEPARATOR_SYMBOL).join(
[u"%s%s%s" % (fn, gc.CONFIG_STRING_ASSIGNMENT_SYMBOL, self.data[fn]) for fn in sorted(self.data.keys()) if self.data[fn] is not None]
)
@classmethod
[docs] def parameters(cls, sort=True, as_strings=False):
"""
Return a list of tuples ``(field, description, type, default)``,
one for each field of the configuration.
:param bool sort: if ``True``, return the list sorted by field
:param bool as_strings: if ``True``, return formatted strings instead
:rtype: list
"""
def cft(ftype, fdefault):
""" Convert field type and default value to string """
if ftype is None:
return u""
if ftype in [TimeValue, Decimal, float]:
cftype = u"float"
cfdefault = u"%.3f" % ftype(fdefault) if fdefault is not None else u"None"
elif ftype == int:
cftype = u"int"
cfdefault = u"%d" % ftype(fdefault) if fdefault is not None else u"None"
elif ftype == bool:
cftype = u"bool"
cfdefault = u"%s" % fdefault if fdefault is not None else u"None"
else:
cftype = u"unknown"
cfdefault = u"%s" % fdefault if fdefault is not None else u"None"
return u" (%s, %s)" % (cftype, cfdefault)
parameters = [(field, fdesc, ftype, fdefault) for (field, (fdefault, ftype, faliases, fdesc)) in cls.FIELDS]
if sort:
parameters = sorted(parameters)
if as_strings:
l = max([len(t[0]) for t in parameters])
parameters = [u"%s : %s%s" % (f.ljust(l), d, cft(t, df)) for (f, d, t, df) in parameters]
return parameters