Project

Profile

Help

Download (10.4 KB) Statistics View on GitHub Reload from mirrored respository
| Branch: | Tag: | Revision:

github / src / config.py @ 05b173a7

1 2e7e9634 Vincent Le Goff
# 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 e1d2d0dc Vincent Le Goff
31 01dfce9e Vincent Le Goff
import locale
32 e1d2d0dc Vincent Le Goff
import os
33
import os.path
34
from textwrap import dedent
35
36 7e3e3813 Vincent Le Goff
from yaml import safe_dump, safe_load
37 ebe2010d Vincent Le Goff
from configobj import ConfigObj, ParseError
38 e1d2d0dc Vincent Le Goff
from validate import Validator
39
40 ebe2010d Vincent Le Goff
from log import main as logger
41 1df43fe1 Vincent Le Goff
from world import World
42
43 a88028b6 Vincent Le Goff
class Configuration:
44 e1d2d0dc Vincent Le Goff
45
    """Class describing CocoMUD's configuration.
46

47 7e3e3813 Vincent Le Goff
    Each configuration file is loaded here.  A configuration file can
48
    be either YAML or respecting the ConfigObj syntax (that is,
49
    basically a .ini file).  If everything goes smoothly, the ConfigObj
50
    objects can be found in __getitem__-ing these values, specifying
51 a88028b6 Vincent Le Goff
    the directory structure.  Additionally, results from the argument parser
52
    can also be found there.
53 e1d2d0dc Vincent Le Goff

54
    Example:
55

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

65
    """
66
67 d465ec21 Vincent Le Goff
    def __init__(self, root_dir, engine):
68 e1d2d0dc Vincent Le Goff
        self.root_dir = root_dir
69 d465ec21 Vincent Le Goff
        self.engine = engine
70 e1d2d0dc Vincent Le Goff
        self.values = {}
71
72
    def __getitem__(self, key):
73
        """Return the configured value at the specified key.
74

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

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

87
        """
88
        if "." in key:
89
            keys = key.split(".")
90
            value = self.values
91
            for sub_key in keys:
92
                if sub_key not in value:
93
                    raise KeyError("the key {} cannot be found, cannot " \
94
                            "find {}".format(repr(key), repr(sub_key)))
95
96
                value = value[sub_key]
97
98
        else:
99
            value = self.values[key]
100
101
        return value
102
103
    def __setitem__(self, key, value):
104
        """Change the value of 'key'.
105

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

109
        """
110
        if "." in key:
111
            keys = key.split(".")
112
            last = keys[-1]
113
            del keys[-1]
114
            dictionary = self.values
115
            for sub_key in keys:
116
                if sub_key not in dictionary:
117
                    raise KeyError("the key {} cannot be found, cannot " \
118
                            "find {}".format(repr(key), repr(sub_key)))
119
120
                dictionary = dictionary[sub_key]
121
122
            dictionary[last] = value
123
        else:
124
            self.values[key] = value
125
126
    def load(self):
127
        """Load the configuration."""
128
        raise NotImplementedError
129
130 1df43fe1 Vincent Le Goff
    def load_config_file(self, filename, spec, root_dir=None):
131 e1d2d0dc Vincent Le Goff
        """Load the specified file using ConfigObj."""
132 1df43fe1 Vincent Le Goff
        root_dir = root_dir or self.root_dir
133
        fullpath = os.path.join(root_dir, filename)
134 e1d2d0dc Vincent Le Goff
135
        # Create the directory structure if necessary
136
        directories = os.path.dirname(fullpath).split("/")
137
        base = directories[0]
138
        if not os.path.exists(base):
139
            os.mkdir(base)
140
141
        for directory in directories[1:]:
142 1df43fe1 Vincent Le Goff
            base += os.path.sep + directory
143 e1d2d0dc Vincent Le Goff
            if not os.path.exists(base):
144
                os.mkdir(base)
145
146
        # Create the ConfigObj
147 ebe2010d Vincent Le Goff
        try:
148 45185932 Vincent Le Goff
            config = ConfigObj(fullpath + ".conf", encoding="UTF8",
149 ebe2010d Vincent Le Goff
                    configspec=spec.split("\n"))
150 45185932 Vincent Le Goff
            config.backup_encoding = "utf-8"
151 be3c374a Vincent Le Goff
        except (ParseError, UnicodeError):
152
            logger.warning("Unable to parse {}, try in latin-1 encoding".format(
153 ebe2010d Vincent Le Goff
                    repr(fullpath)))
154 be3c374a Vincent Le Goff
            config = ConfigObj(fullpath + ".conf", configspec=spec.split("\n"), encoding="latin-1")
155 45185932 Vincent Le Goff
            config.encoding = "UTF8"
156
            config.backup_encoding = "utf-8"
157 ebe2010d Vincent Le Goff
            config.write()
158 e1d2d0dc Vincent Le Goff
159
        # Validates the configuration
160
        validator = Validator()
161
        result = config.validate(validator)
162
163
        # Saves the ConfigObj
164
        values = self.values
165 1df43fe1 Vincent Le Goff
        for path in os.path.dirname(filename).split("/")[1:]:
166 e1d2d0dc Vincent Le Goff
            if path not in values:
167
                values[path] = {}
168
            values = values[path]
169
170
        values[os.path.basename(fullpath)] = config
171
172 7e3e3813 Vincent Le Goff
    def load_YAML_file(self, filename):
173
        """Load the YAML file."""
174
        fullpath = self.root_dir + os.sep + filename
175
        if os.path.exists(fullpath + ".yml"):
176
            file = open(fullpath + ".yml", "r")
177
            datas = safe_load(file.read())
178
        else:
179
            datas = {}
180
181
        values = self.values
182
        for path in os.path.dirname(fullpath).split("/")[1:]:
183
            if path not in values:
184
                values[path] = {}
185
            values = values[path]
186
187
        values[os.path.basename(fullpath)] = datas
188
189 d465ec21 Vincent Le Goff
    def write_YAML_file(self, filename, data):
190
        """Write the YAML associated with the data.
191

192
        Arguments:
193
            filename: the filename relative to the rootdir without extension
194
            data: the data as a dictionary.
195

196
        """
197
        fullpath = self.root_dir + os.sep + filename + ".yml"
198
        file = open(fullpath, "w")
199
        try:
200
            safe_dump(data, file, default_flow_style=False)
201
        finally:
202
            file.close()
203 e1d2d0dc Vincent Le Goff
204 01dfce9e Vincent Le Goff
205 e1d2d0dc Vincent Le Goff
class Settings(Configuration):
206
207
    """Special configuration in the 'settings' directory."""
208
209 9d5f9ca3 Vincent Le Goff
    LANGUAGES = (
210
        ("en", "English"),
211 eb9f2e41 Vincent Le Goff
        ("es", "Spanish"),
212 9d5f9ca3 Vincent Le Goff
        ("fr", "French"),
213
    )
214 01dfce9e Vincent Le Goff
215 a88028b6 Vincent Le Goff
    def __init__(self, engine, config_dir):
216
        Configuration.__init__(self, os.path.join(config_dir, "settings"), engine)
217
        self.config_dir = config_dir
218 e1d2d0dc Vincent Le Goff
219 01dfce9e Vincent Le Goff
    def get_language(self):
220
        """Return the configured language.
221

222
        If the configuration hasn't been loaded, or the configured
223
        language isn't valid, return "en" (English).
224

225
        """
226
        default = "en"
227 9d5f9ca3 Vincent Le Goff
        codes = [c[0] for c in type(self).LANGUAGES]
228 01dfce9e Vincent Le Goff
        try:
229
            lang = self["options.general.language"]
230 9d5f9ca3 Vincent Le Goff
            assert lang in codes
231 01dfce9e Vincent Le Goff
        except (KeyError, AssertionError):
232
            return default
233
234
        return lang
235
236 e1d2d0dc Vincent Le Goff
    def load(self):
237
        """Load all the files."""
238
        self.load_options()
239 1df43fe1 Vincent Le Goff
240
        # Load the world configuration
241 a88028b6 Vincent Le Goff
        for directory in os.listdir(os.path.join(self.engine.config_dir, "worlds")):
242 1df43fe1 Vincent Le Goff
            world = World(location=directory)
243 a88028b6 Vincent Le Goff
            world.engine = self.engine
244 1df43fe1 Vincent Le Goff
            settings = GameSettings(self.engine, world)
245
            settings.load()
246
            self.engine.worlds[world.name] = world
247 c63ff6dd Vincent Le Goff
            world.load_characters()
248 1df43fe1 Vincent Le Goff
249 e1d2d0dc Vincent Le Goff
    def load_options(self):
250
        """Load the file containing the options."""
251 01dfce9e Vincent Le Goff
        lang = locale.getdefaultlocale()[0].split("_")[0]
252 45185932 Vincent Le Goff
        spec = dedent(u"""
253 01dfce9e Vincent Le Goff
            [general]
254
                language = option('en', 'fr', default='{lang}')
255 7b7e38e9 Vincent Le Goff
                encoding = string(default="iso8859_15")
256 0a42d45d Vincent Le Goff
                screenreader = boolean(default=True)
257 01dfce9e Vincent Le Goff

258 57cb7efc Vincent Le Goff
            [input]
259
                command_stacking = string(default=";")
260 45185932 Vincent Le Goff
                auto_send_paste = boolean(default=True)
261 57cb7efc Vincent Le Goff

262 f2eac2d3 Vincent Le Goff
            [output]
263
                richtext = boolean(default=True)
264

265 e1d2d0dc Vincent Le Goff
            [TTS]
266
                on = boolean(default=True)
267
                outside = boolean(default=True)
268 a2e48056 Vincent Le Goff
                interrupt = boolean(default=True)
269 01dfce9e Vincent Le Goff
        """.format(lang=lang).strip("\n"))
270 7e3e3813 Vincent Le Goff
        self.load_config_file("options", spec)
271 d465ec21 Vincent Le Goff
272
    def write_macros(self):
273
        """Write the YAML data file."""
274
        macros = {}
275
        for macro in self.engine.macros.values():
276
            macros[macro.shortcut] = macro.action
277
278
        self.write_YAML_file("macros", macros)
279 1df43fe1 Vincent Le Goff
280
class GameSettings(Configuration):
281
282
    """Game settings (specific to a game/world)."""
283
284
    def __init__(self, engine, world):
285
        Configuration.__init__(self, "settings", engine)
286
        self.world = world
287
288
    def load(self):
289
        """Load all the files."""
290
        self.load_options()
291
292
    def load_options(self):
293
        """Load the file containing the options."""
294
        world = self.world
295 45185932 Vincent Le Goff
        spec = dedent(u"""
296 1df43fe1 Vincent Le Goff
            [connection]
297
                name = string
298
                hostname = string
299
                port = integer
300 5e6fddad Vincent Le Goff
                protocol = string(default="telnet")
301 1df43fe1 Vincent Le Goff
        """).strip("\n")
302
        self.load_config_file("options", spec, world.path)
303
        world.name = self["options.connection.name"]
304
        world.hostname = self["options.connection.hostname"]
305
        world.port = self["options.connection.port"]
306 5e6fddad Vincent Le Goff
        world.protocol = self["options.connection.protocol"]
307 1df43fe1 Vincent Le Goff
        world.settings = self["options"]