Project

Profile

Help

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

github / src / config.py @ 9083fa1b

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
from world import World
41

    
42
class Configuration(object):
43

    
44
    """Class describing CocoMUD's configuration.
45

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

52
    Example:
53

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

63
    """
64

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

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

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

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

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

    
94
                value = value[sub_key]
95

    
96
        else:
97
            value = self.values[key]
98

    
99
        return value
100

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

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

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

    
118
                dictionary = dictionary[sub_key]
119

    
120
            dictionary[last] = value
121
        else:
122
            self.values[key] = value
123

    
124
    def load(self):
125
        """Load the configuration."""
126
        raise NotImplementedError
127

    
128
    def load_config_file(self, filename, spec, root_dir=None):
129
        """Load the specified file using ConfigObj."""
130
        root_dir = root_dir or self.root_dir
131
        fullpath = os.path.join(root_dir, filename)
132

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

    
139
        for directory in directories[1:]:
140
            base += os.path.sep + directory
141
            if not os.path.exists(base):
142
                os.mkdir(base)
143

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

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

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

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

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

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

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

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

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

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

    
192

    
193
class Settings(Configuration):
194

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

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

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

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

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

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

    
220
        return lang
221

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

    
226
        # Load the world configuration
227
        for directory in os.listdir("worlds"):
228
            world = World(location=directory)
229
            settings = GameSettings(self.engine, world)
230
            settings.load()
231
            self.engine.worlds[world.name] = world
232

    
233
        self.load_YAML_file("macros")
234

    
235
    def load_options(self):
236
        """Load the file containing the options."""
237
        lang = locale.getdefaultlocale()[0].split("_")[0]
238
        spec = dedent("""
239
            [general]
240
                language = option('en', 'fr', default='{lang}')
241
                encoding = string(default="iso8859_15")
242

243
            [accessibility]
244
                tab_end = boolean(default=true)
245

246
            [TTS]
247
                on = boolean(default=True)
248
                outside = boolean(default=True)
249
        """.format(lang=lang).strip("\n"))
250
        self.load_config_file("options", spec)
251

    
252
    def write_macros(self):
253
        """Write the YAML data file."""
254
        macros = {}
255
        for macro in self.engine.macros.values():
256
            macros[macro.shortcut] = macro.action
257

    
258
        self.write_YAML_file("macros", macros)
259

    
260
class GameSettings(Configuration):
261

    
262
    """Game settings (specific to a game/world)."""
263

    
264
    def __init__(self, engine, world):
265
        Configuration.__init__(self, "settings", engine)
266
        self.world = world
267

    
268
    def load(self):
269
        """Load all the files."""
270
        self.load_options()
271

    
272
    def load_options(self):
273
        """Load the file containing the options."""
274
        world = self.world
275
        spec = dedent("""
276
            [connection]
277
                name = string
278
                hostname = string
279
                port = integer
280
        """).strip("\n")
281
        self.load_config_file("options", spec, world.path)
282
        world.name = self["options.connection.name"]
283
        world.hostname = self["options.connection.hostname"]
284
        world.port = self["options.connection.port"]
285
        world.settings = self["options"]
(5-5/8)