Project

Profile

Help

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

github / src / config.py @ 13e86181

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
        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 1df43fe1 Vincent Le Goff
        for path in os.path.dirname(filename).split("/")[1:]:
154 e1d2d0dc Vincent Le Goff
            if path not in values:
155
                values[path] = {}
156
            values = values[path]
157
158
        values[os.path.basename(fullpath)] = config
159
160 7e3e3813 Vincent Le Goff
    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 d465ec21 Vincent Le Goff
    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 e1d2d0dc Vincent Le Goff
192 01dfce9e Vincent Le Goff
193 e1d2d0dc Vincent Le Goff
class Settings(Configuration):
194
195
    """Special configuration in the 'settings' directory."""
196
197 9d5f9ca3 Vincent Le Goff
    LANGUAGES = (
198
        ("en", "English"),
199
        ("fr", "French"),
200
    )
201 01dfce9e Vincent Le Goff
202 d465ec21 Vincent Le Goff
    def __init__(self, engine):
203
        Configuration.__init__(self, "settings", engine)
204 e1d2d0dc Vincent Le Goff
205 01dfce9e Vincent Le Goff
    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 9d5f9ca3 Vincent Le Goff
        codes = [c[0] for c in type(self).LANGUAGES]
214 01dfce9e Vincent Le Goff
        try:
215
            lang = self["options.general.language"]
216 9d5f9ca3 Vincent Le Goff
            assert lang in codes
217 01dfce9e Vincent Le Goff
        except (KeyError, AssertionError):
218
            return default
219
220
        return lang
221
222 e1d2d0dc Vincent Le Goff
    def load(self):
223
        """Load all the files."""
224
        self.load_options()
225 1df43fe1 Vincent Le Goff
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 7e3e3813 Vincent Le Goff
        self.load_YAML_file("macros")
234 e1d2d0dc Vincent Le Goff
235
    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 01dfce9e Vincent Le Goff

243 9083fa1b Vincent Le Goff
            [accessibility]
244 a235c8cc Vincent Le Goff
                tab_end = boolean(default=True)
245
                nl_end = boolean(default=True)
246 9083fa1b Vincent Le Goff

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