Project

Profile

Help

How to connect?
Download (8.04 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / config.py @ 8d408f2d

1
# Copyright (c) 2016, LE GOFF Vincent
2
# All rights reserved.
3

    
4
# Redistribution and use in source and binary forms, with or without
5
# modification, are permitted provided that the following conditions are met:
6

    
7
# * Redistributions of source code must retain the above copyright notice, this
8
#   list of conditions and the following disclaimer.
9

    
10
# * Redistributions in binary form must reproduce the above copyright notice,
11
#   this list of conditions and the following disclaimer in the documentation
12
#   and/or other materials provided with the distribution.
13

    
14
# * Neither the name of ytranslate nor the names of its
15
#   contributors may be used to endorse or promote products derived from
16
#   this software without specific prior written permission.
17

    
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

    
29
"""This module defines the default configuration."""
30

    
31
import locale
32
import os
33
import os.path
34
from textwrap import dedent
35

    
36
from yaml import safe_dump, safe_load
37
from configobj import ConfigObj
38
from validate import Validator
39

    
40
class Configuration(object):
41

    
42
    """Class describing CocoMUD's configuration.
43

44
    Each configuration file is loaded here.  A configuration file can
45
    be either YAML or respecting the ConfigObj syntax (that is,
46
    basically a .ini file).  If everything goes smoothly, the ConfigObj
47
    objects can be found in __getitem__-ing these values, specifying
48
    the directory structure.
49

50
    Example:
51

52
        >>> config = Configuration()
53
        >>> config.load()
54
        >>> # In the settings/global.conf file is the following line:
55
        >>> #     name = CocoMUD
56
        >>> # You can access this configuration through:
57
        >>> configuration["options"]["global"]["name"]
58
        >>> # Or, more readable
59
        >>> configuration["options.global.name"]
60

61
    """
62

    
63
    def __init__(self, root_dir, engine):
64
        self.root_dir = root_dir
65
        self.engine = engine
66
        self.values = {}
67

    
68
    def __getitem__(self, key):
69
        """Return the configured value at the specified key.
70

71
        The key is a string.  It can be an identifier without periods
72
        (.).  In which case, the top-level data at this key is returned
73
        or a KeyError exception is raised.
74
        The 'key' can also be a list of identifiers in a string separated
75
        by a period.  In this case, the data is looked for in the
76
        directory/file/ConfigObj hierarchy.  A KeyError exception
77
        is raised if the expected key cannot be found.
78

79
        Thus, the two following lines are identical:
80
            >>> configuration["settings"]["global"]["name"]
81
            >>> configuration["settings.global.name"]
82

83
        """
84
        if "." in key:
85
            keys = key.split(".")
86
            value = self.values
87
            for sub_key in keys:
88
                if sub_key not in value:
89
                    raise KeyError("the key {} cannot be found, cannot " \
90
                            "find {}".format(repr(key), repr(sub_key)))
91

    
92
                value = value[sub_key]
93

    
94
        else:
95
            value = self.values[key]
96

    
97
        return value
98

    
99
    def __setitem__(self, key, value):
100
        """Change the value of 'key'.
101

102
        'key' is a string, and can be specified in the two ways supported
103
        by '__getitem__'.
104

105
        """
106
        if "." in key:
107
            keys = key.split(".")
108
            last = keys[-1]
109
            del keys[-1]
110
            dictionary = self.values
111
            for sub_key in keys:
112
                if sub_key not in dictionary:
113
                    raise KeyError("the key {} cannot be found, cannot " \
114
                            "find {}".format(repr(key), repr(sub_key)))
115

    
116
                dictionary = dictionary[sub_key]
117

    
118
            dictionary[last] = value
119
        else:
120
            self.values[key] = value
121

    
122
    def load(self):
123
        """Load the configuration."""
124
        raise NotImplementedError
125

    
126
    def load_config_file(self, filename, spec):
127
        """Load the specified file using ConfigObj."""
128
        fullpath = self.root_dir + os.sep + filename
129

    
130
        # Create the directory structure if necessary
131
        directories = os.path.dirname(fullpath).split("/")
132
        base = directories[0]
133
        if not os.path.exists(base):
134
            os.mkdir(base)
135

    
136
        for directory in directories[1:]:
137
            base += os.path + directory
138
            if not os.path.exists(base):
139
                os.mkdir(base)
140

    
141
        filename = os.path.basename(fullpath) + ".conf"
142

    
143
        # Create the ConfigObj
144
        config = ConfigObj(fullpath + ".conf", configspec=spec.split("\n"))
145

    
146
        # Validates the configuration
147
        validator = Validator()
148
        result = config.validate(validator)
149

    
150
        # Saves the ConfigObj
151
        values = self.values
152
        for path in os.path.dirname(fullpath).split("/")[1:]:
153
            if path not in values:
154
                values[path] = {}
155
            values = values[path]
156

    
157
        values[os.path.basename(fullpath)] = config
158

    
159
    def load_YAML_file(self, filename):
160
        """Load the YAML file."""
161
        fullpath = self.root_dir + os.sep + filename
162
        if os.path.exists(fullpath + ".yml"):
163
            file = open(fullpath + ".yml", "r")
164
            datas = safe_load(file.read())
165
        else:
166
            datas = {}
167

    
168
        values = self.values
169
        for path in os.path.dirname(fullpath).split("/")[1:]:
170
            if path not in values:
171
                values[path] = {}
172
            values = values[path]
173

    
174
        values[os.path.basename(fullpath)] = datas
175

    
176
    def write_YAML_file(self, filename, data):
177
        """Write the YAML associated with the data.
178

179
        Arguments:
180
            filename: the filename relative to the rootdir without extension
181
            data: the data as a dictionary.
182

183
        """
184
        fullpath = self.root_dir + os.sep + filename + ".yml"
185
        file = open(fullpath, "w")
186
        try:
187
            safe_dump(data, file, default_flow_style=False)
188
        finally:
189
            file.close()
190

    
191

    
192
class Settings(Configuration):
193

    
194
    """Special configuration in the 'settings' directory."""
195

    
196
    LANGUAGES = (
197
        ("en", "English"),
198
        ("fr", "French"),
199
    )
200

    
201
    def __init__(self, engine):
202
        Configuration.__init__(self, "settings", engine)
203

    
204
    def get_language(self):
205
        """Return the configured language.
206

207
        If the configuration hasn't been loaded, or the configured
208
        language isn't valid, return "en" (English).
209

210
        """
211
        default = "en"
212
        codes = [c[0] for c in type(self).LANGUAGES]
213
        try:
214
            lang = self["options.general.language"]
215
            assert lang in codes
216
        except (KeyError, AssertionError):
217
            return default
218

    
219
        return lang
220

    
221
    def load(self):
222
        """Load all the files."""
223
        self.load_options()
224
        self.load_YAML_file("macros")
225

    
226
    def load_options(self):
227
        """Load the file containing the options."""
228
        lang = locale.getdefaultlocale()[0].split("_")[0]
229
        spec = dedent("""
230
            [general]
231
                language = option('en', 'fr', default='{lang}')
232

233
            [TTS]
234
                on = boolean(default=True)
235
                outside = boolean(default=True)
236
        """.format(lang=lang).strip("\n"))
237
        self.load_config_file("options", spec)
238

    
239
    def write_macros(self):
240
        """Write the YAML data file."""
241
        macros = {}
242
        for macro in self.engine.macros.values():
243
            macros[macro.shortcut] = macro.action
244

    
245
        self.write_YAML_file("macros", macros)
(5-5/7)