Project

Profile

Help

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

github / src / config.py @ 96664dff

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 e1d2d0dc Vincent Le Goff
from configobj import ConfigObj
38
from validate import Validator
39
40 1df43fe1 Vincent Le Goff
from world import World
41
42 01dfce9e Vincent Le Goff
class Configuration(object):
43 e1d2d0dc Vincent Le Goff
44
    """Class describing CocoMUD's configuration.
45

46 7e3e3813 Vincent Le Goff
    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 e1d2d0dc Vincent Le Goff

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 d465ec21 Vincent Le Goff
    def __init__(self, root_dir, engine):
66 e1d2d0dc Vincent Le Goff
        self.root_dir = root_dir
67 d465ec21 Vincent Le Goff
        self.engine = engine
68 e1d2d0dc Vincent Le Goff
        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 1df43fe1 Vincent Le Goff
    def load_config_file(self, filename, spec, root_dir=None):
129 e1d2d0dc Vincent Le Goff
        """Load the specified file using ConfigObj."""
130 1df43fe1 Vincent Le Goff
        root_dir = root_dir or self.root_dir
131
        fullpath = os.path.join(root_dir, filename)
132 e1d2d0dc Vincent Le Goff
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 1df43fe1 Vincent Le Goff
            base += os.path.sep + directory
141 e1d2d0dc Vincent Le Goff
            if not os.path.exists(base):
142
                os.mkdir(base)
143
144
        # Create the ConfigObj
145 96664dff Vincent Le Goff
        config = ConfigObj(fullpath + ".conf", encoding="latin-1",
146
                configspec=spec.split("\n"))
147 e1d2d0dc Vincent Le Goff
148
        # Validates the configuration
149
        validator = Validator()
150
        result = config.validate(validator)
151
152
        # Saves the ConfigObj
153
        values = self.values
154 1df43fe1 Vincent Le Goff
        for path in os.path.dirname(filename).split("/")[1:]:
155 e1d2d0dc Vincent Le Goff
            if path not in values:
156
                values[path] = {}
157
            values = values[path]
158
159
        values[os.path.basename(fullpath)] = config
160
161 7e3e3813 Vincent Le Goff
    def load_YAML_file(self, filename):
162
        """Load the YAML file."""
163
        fullpath = self.root_dir + os.sep + filename
164
        if os.path.exists(fullpath + ".yml"):
165
            file = open(fullpath + ".yml", "r")
166
            datas = safe_load(file.read())
167
        else:
168
            datas = {}
169
170
        values = self.values
171
        for path in os.path.dirname(fullpath).split("/")[1:]:
172
            if path not in values:
173
                values[path] = {}
174
            values = values[path]
175
176
        values[os.path.basename(fullpath)] = datas
177
178 d465ec21 Vincent Le Goff
    def write_YAML_file(self, filename, data):
179
        """Write the YAML associated with the data.
180

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

185
        """
186
        fullpath = self.root_dir + os.sep + filename + ".yml"
187
        file = open(fullpath, "w")
188
        try:
189
            safe_dump(data, file, default_flow_style=False)
190
        finally:
191
            file.close()
192 e1d2d0dc Vincent Le Goff
193 01dfce9e Vincent Le Goff
194 e1d2d0dc Vincent Le Goff
class Settings(Configuration):
195
196
    """Special configuration in the 'settings' directory."""
197
198 9d5f9ca3 Vincent Le Goff
    LANGUAGES = (
199
        ("en", "English"),
200
        ("fr", "French"),
201
    )
202 01dfce9e Vincent Le Goff
203 d465ec21 Vincent Le Goff
    def __init__(self, engine):
204
        Configuration.__init__(self, "settings", engine)
205 e1d2d0dc Vincent Le Goff
206 01dfce9e Vincent Le Goff
    def get_language(self):
207
        """Return the configured language.
208

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

212
        """
213
        default = "en"
214 9d5f9ca3 Vincent Le Goff
        codes = [c[0] for c in type(self).LANGUAGES]
215 01dfce9e Vincent Le Goff
        try:
216
            lang = self["options.general.language"]
217 9d5f9ca3 Vincent Le Goff
            assert lang in codes
218 01dfce9e Vincent Le Goff
        except (KeyError, AssertionError):
219
            return default
220
221
        return lang
222
223 e1d2d0dc Vincent Le Goff
    def load(self):
224
        """Load all the files."""
225
        self.load_options()
226 1df43fe1 Vincent Le Goff
227
        # Load the world configuration
228
        for directory in os.listdir("worlds"):
229
            world = World(location=directory)
230
            settings = GameSettings(self.engine, world)
231
            settings.load()
232
            self.engine.worlds[world.name] = world
233 c63ff6dd Vincent Le Goff
            world.load_characters()
234 1df43fe1 Vincent Le Goff
235 e1d2d0dc Vincent Le Goff
    def load_options(self):
236
        """Load the file containing the options."""
237 01dfce9e Vincent Le Goff
        lang = locale.getdefaultlocale()[0].split("_")[0]
238 e1d2d0dc Vincent Le Goff
        spec = dedent("""
239 01dfce9e Vincent Le Goff
            [general]
240
                language = option('en', 'fr', default='{lang}')
241 7b7e38e9 Vincent Le Goff
                encoding = string(default="iso8859_15")
242 0a42d45d Vincent Le Goff
                screenreader = boolean(default=True)
243 01dfce9e Vincent Le Goff

244 57cb7efc Vincent Le Goff
            [input]
245
                command_stacking = string(default=";")
246

247 f2eac2d3 Vincent Le Goff
            [output]
248
                richtext = boolean(default=True)
249

250 e1d2d0dc Vincent Le Goff
            [TTS]
251
                on = boolean(default=True)
252
                outside = boolean(default=True)
253 a2e48056 Vincent Le Goff
                interrupt = boolean(default=True)
254 01dfce9e Vincent Le Goff
        """.format(lang=lang).strip("\n"))
255 7e3e3813 Vincent Le Goff
        self.load_config_file("options", spec)
256 d465ec21 Vincent Le Goff
257
    def write_macros(self):
258
        """Write the YAML data file."""
259
        macros = {}
260
        for macro in self.engine.macros.values():
261
            macros[macro.shortcut] = macro.action
262
263
        self.write_YAML_file("macros", macros)
264 1df43fe1 Vincent Le Goff
265
class GameSettings(Configuration):
266
267
    """Game settings (specific to a game/world)."""
268
269
    def __init__(self, engine, world):
270
        Configuration.__init__(self, "settings", engine)
271
        self.world = world
272
273
    def load(self):
274
        """Load all the files."""
275
        self.load_options()
276
277
    def load_options(self):
278
        """Load the file containing the options."""
279
        world = self.world
280
        spec = dedent("""
281
            [connection]
282
                name = string
283
                hostname = string
284
                port = integer
285 5e6fddad Vincent Le Goff
                protocol = string(default="telnet")
286 1df43fe1 Vincent Le Goff
        """).strip("\n")
287
        self.load_config_file("options", spec, world.path)
288
        world.name = self["options.connection.name"]
289
        world.hostname = self["options.connection.hostname"]
290
        world.port = self["options.connection.port"]
291 5e6fddad Vincent Le Goff
        world.protocol = self["options.connection.protocol"]
292 1df43fe1 Vincent Le Goff
        world.settings = self["options"]