source: branches/active/character_customization/game/parpg/settings.py @ 810

Revision 810, 14.8 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites

  • Removed SystemDataDirectory? and UserDataDirectory?
    • it is the the settings module's job to keep track of this sort of thing, not fife or PARPG's
    • consequently, setting.py's command line interface became simple
    • this will also prevent those "I generated a settings file but PARPG doesnt run" complaints
    • I think I'm going to have some helper methods to generate platform-specific paths at run-time
  • User data directory is now properly created
  • added platform_paths(system) which returns the platform-specific paths for the given system
  • if no system is given, it gives the paths for the system that the script was run on
  • changed logic of settings.py so that it is not required to pass a path.
  • not passing a path invokes paltform_paths
  • Property svn:executable set to *
Line 
1#!/usr/bin/env python2
2
3#  Copyright (C) 2011  Edwin Marshall <emarshall85@gmail.com>
4
5#   This file is part of PARPG.
6#
7#   PARPG is free software: you can redistribute it and/or modify
8#   it under the terms of the GNU General Public License as published by
9#   the Free Software Foundation, either version 3 of the License, or
10#   (at your option) any later version.
11#
12#   PARPG is distributed in the hope that it will be useful,
13#   but WITHOUT ANY WARRANTY; without even the implied warranty of
14#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#   GNU General Public License for more details.
16#
17#   You should have received a copy of the GNU General Public License
18#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
19
20""" Provides a class used for reading and writing various configurable options
21    throughout the game
22
23    This class produces an INI formated settings file as opposed to an XML
24    formatted one. The reason that python's built-in ConfigurationParser isn't
25    sufficient is because comments aren't preserved when writing a settings
26    file, the order in which the options are written isn't preserved, and the
27    interface used with this class is arguably more convenient that
28    ConfigParser's.
29
30    Default Settings may be generated by envoking this module from the
31    command line:
32        python -m settings.py [system] [data_directory]
33
34    where [system] is one of local, windows, or linux (mac coming soon),
35    and data_directory is the base path for the data files to be loaded.
36
37    Both [system] and [data_directory] are option. If omitted, both
38    default to whichever what is reasonable based on the system settings.py
39    is run on
40"""
41
42import os
43import sys
44import platform
45
46class Section(object):
47    """ An object that represents a section in a settings file.
48
49        Options can be added to a section by simply assigning a value to an
50        attribute:
51            section.foo = baz
52        would produce:
53            [section]
54            foo = baz
55        in the settings file. Options that do not exist on assignment
56        are created dynamcially.
57
58        Values are automatically converted to the appropriate python type.
59        Options that begin and end with brackets([, ]) are converted to lists,
60        and options that are double-quoted (") are converted to strings.
61        Section also recognizes booleans regardless of case, in addition to the
62        literals 'yes' and 'no' of any case. Except in the case of
63        double-quoted strings, extra white-space is trimmed, so you need not
64        worry. For example:
65            foo = bar
66        is equivalent to :
67            foo    =         baz
68    """
69    def __init__(self, name):
70        """ Initialize a new section.
71
72            @param name: name of the section. In the INI file, sections are surrounded
73                         by brackets ([name])
74            @type name: string
75        """
76        self.name = name
77
78    def __setattr__(self, option, value):
79        """ Assign a value to an option, converting types when appropriate.
80
81            @param option: name of the option to assign a value to.
82            @type option: string @param value: value to be assigned to the option.
83            @type value: int, float, string, boolean, or list
84        """
85        value = str(value)
86        if value.startswith('[') and value.endswith(']'):
87            value = [item.strip() for item in value[1:-1].split(',')]
88        elif value.lower() == 'true' or value.lower() == 'yes':
89            value = True
90        elif value.lower() == 'false' or value.lower() == 'no':
91            value = False
92        elif value.isdigit():
93            value = int(value)
94        else:
95            try:
96                value = float(value)
97            except ValueError:
98                # leave as string
99                pass
100
101        self.__dict__[option] = value
102
103    def __getattribute__(self, option):
104        """ Returns the option's value"""
105        # Remove leading and trailing quotes from strings that have them
106        return_value = object.__getattribute__(self, option)
107        try:
108            for key, value in return_value.iteritems():
109                if (hasattr(value, 'split') and 
110                    value.startswith("\"") and value.endswith("\"")):
111                    return_value[key] = value[1:-1]
112        except AttributeError:
113            pass
114
115        return return_value
116
117    @property
118    def options(self):
119        """ Returns a dictionary of existing options """
120        options = self.__dict__
121        # get rid of properties that aren't actually options
122        if options.has_key('name'):
123            options.pop('name')
124
125        return options
126
127class Settings(object):
128    """ An object that represents a settings file, its sectons,
129        and the options defined within those sections.
130    """
131    def __init__(self, system_path=None, user_path=None, suffix='.cfg'):
132        """ initializes a new settings object. If no paths are given, they are
133            guessed based on whatever platform the script was run on.
134
135            Examples:
136                paths = ['/etc/parpg', '/home/user_name/.config/parpg']
137                settings = Settings(*paths)
138               
139                paths = {'system': '/etc/parpg',
140                         'user': '/home/user_name/.config/parpg'}
141                settings = Settings(**paths)
142
143                settings = Settings('.')
144
145                settigns = Settings()
146
147            @param system_path: Path to the system settings file.
148            @type system_path: string (must be a valid path)
149
150            @param user_path: Path to the user settings file. Options that
151                              are missing from this file are propogated
152                              from the system settings file and saved on
153                              request
154            @type user_path: string (must be a valid path)
155           
156            @param suffix: Suffix of the settings file that will be generated.
157            @type suffix: string
158        """
159        if not suffix.startswith('.'):
160            suffix = '.' + suffix
161
162        self.suffix = suffix
163        self.settings_file = ''
164
165        if system_path is None and user_path is None:
166            # use platform-specific values as paths
167            self.paths = {}
168            self.paths['system'], self.paths['user'] = self.platform_paths()
169        else:
170            self.paths = {'system': system_path, 'user': user_path}
171
172        self.read()
173
174
175    def __getattr__(self, name):
176        """ Returns a Section object to be used for assignment, creating one
177            if it doesn't exist.
178
179            @param name: name of section to be retrieved
180            @type name: string
181        """
182        if name in ['get', 'set']:
183            raise AttributeError("{0} is deprecated. Please consult Settings' "
184                                  "documentation for information on how to "
185                                  "create/modify sections and their respective "
186                                  "options".format(name))
187        else:
188            if not self.__dict__.has_key(name):
189                setattr(self, name, Section(name))
190
191        return getattr(self, name)
192
193    def platform_paths(self, system=None):
194        if system is None:
195            system = platform.system()
196       
197        if system.lower() == 'linux':
198            return (os.path.join(os.environ['XDG_DATA_DIRS'].split(':')[0], 
199                                 'parpg'),
200                    os.path.join(os.environ['XDG_CONFIG_HOME'], 'parpg'))
201        elif system.lower() == 'windows':
202            return (os.path.join(os.environ['PROGRAMFILES'], 'PARPG'),
203                    os.path.join(os.environ['USERDATA'], 'PARPG'))
204        else:
205            return None
206
207
208    def read(self, filenames=None):
209        """ Reads a settings file and populates the settings object
210            with its sections and options. Calling this method without
211            any arguments simply re-reads the previously defined filename
212            and paths
213
214            @param filenames: name of files to be parsed.
215            @type path: string or list
216        """
217       
218        if filenames is None:
219            filenames = [os.path.join(self.paths[key], 
220                                      '{0}{1}'.format(key, self.suffix))
221                         for key in self.paths.keys() 
222                         if self.paths[key] is not None]
223        elif hasattr(filenames, 'split'):
224            filenames = [filenames]
225
226        for filename in filenames:
227            section = None
228            try:
229                self.settings_file = open(filename, 'r').readlines()
230            except IOError:
231                pass
232
233            for line in self.settings_file:
234                if line.startswith('#') or line.strip() == '':
235                    continue
236                elif line.startswith('[') and line.endswith(']\n'):
237                    getattr(self, line[1:-2])
238                    section = line[1:-2]
239                else:
240                    option, value = [item.strip() 
241                                     for item in line.split('=', 1)]
242                    setattr(getattr(self, section), option, value)
243
244    def write(self, filename=None):
245        """ Writes a settings file based on the settings object's
246            sections and options
247
248            @param filename: Name of file to save to. By default, this is
249                             the user settings file.
250            @type path: string
251        """
252        if filename is None:
253            filename = os.path.join(self.paths['user'], 
254                                    'user{0}'.format(self.suffix))
255
256        for section in self.sections:
257            if '[{0}]\n'.format(section) not in self.settings_file:
258                self.settings_file.append('\n[{0}]\n'.format(section))
259                for option, value in getattr(self, section).options.iteritems():
260                    template = '{0} = {1}\n'.format(option, value)
261                    self.settings_file.append(template)
262            else:
263                start_of_section = (self.settings_file
264                                        .index('[{0}]\n'.format(section)) + 1)
265
266                for option, value in getattr(self, 
267                                             section).options.iteritems():
268                    if hasattr(value, 'sort'):
269                        value = '[{0}]'.format(', '.join(value))
270
271                    new_option = False
272                    template = '{0} = {1}\n'.format(option, value)
273                    for index, line in enumerate(self.settings_file[:]):
274                        if option in line:
275                            new_option = False
276                            if str(value) not in line:
277                                self.settings_file[index] = template
278
279                            break
280                        else:
281                            new_option = True
282                    if new_option:
283                        while self.settings_file[start_of_section].startswith('#'):
284                            start_of_section += 1
285
286                        self.settings_file.insert(start_of_section, template)
287
288        with open(filename, 'w') as out_stream:
289            for line in self.settings_file:
290                out_stream.write(line)
291
292    @property
293    def sections(self):
294        """ Returns a list of existing sections"""
295        sections = self.__dict__.keys()
296        sections.pop(sections.index('settings_file'))
297        sections.pop(sections.index('paths'))
298        sections.pop(sections.index('suffix'))
299       
300        return sections
301
302    @property
303    def sys_path(self):
304        return self.paths['system']
305
306    @property
307    def user_path(self):
308        return self.paths['user']
309
310DEFAULT_SETTINGS = """\
311[fife]
312# Options marked with ? are untested/unknown
313
314# Game window's title (string) DO NOT EDIT!
315WindowTitle = PARPG Techdemo 2
316
317# Icon to use for the game window's border (filename) DO NOT EDIT!
318WindowIcon = window_icon.png
319
320# Video driver to use. (?)
321VideoDriver = ""
322
323# Backend to use for graphics (OpenGL|SDL)
324RenderBackend = OpenGL
325
326# Run the game in fullscreen mode or not. (True|False)
327FullScreen = False
328
329# Screen Resolution's width. Not used if FullScreen is set to False (800|1024|etc)
330ScreenWidth = 1024
331
332# Screen Resolution's height. Not used if FullScreen is set to False (600|768|etc)
333ScreenHeight = 768
334
335# Screen DPI? (?)
336BitsPerPixel = 0
337
338# ? (?)
339SDLRemoveFakeAlpha = 1
340
341# Subdirectory to load icons from (path)
342IconsDirectory = icons
343
344# ? ([R, G, B])
345ColorKey = [250, 0, 250]
346
347# ? (True|False)
348ColorKeyEnabled = False
349
350# Turn on sound effects and music (True|False)
351EnableSound = True
352
353# Initial volume of sound effects and music (0.0-100.0?)
354InitialVolume = 5.0
355
356# Characters to use to render fonts. DO NOT EDIT!
357FontGlyphs = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\""
358
359# Subdirectory to load fronts from (path)
360FontsDirectory = fonts
361
362# Font to load when game starts
363Font = oldtypewriter.ttf
364
365# Size of in-game fonts
366DefaultFontSize = 12
367
368# ? (?)
369LogModules = [controller]
370
371# ? (?)
372PychanDebug = False
373
374# use Psyco Acceperation (True|False)
375UsePsyco = False
376
377# ? (?)
378ProfilingOn = False
379
380# Lighting Model to use (0-2)
381Lighting = 0
382
383[parpg]
384
385# System subdirectory to load maps from (path)
386MapsDirectory = maps
387
388# YAML file that contains the available maps (filename)
389MapsFile = maps.yaml
390
391# Map to load when game starts (filename)
392Map = Mall
393
394# ? (filename)
395AllAgentsFile = all_agents.yaml
396
397# System subdirectory to load objects from (path)
398ObjectsDirectory = objects
399
400# YAML file that contains the database of availabel objects (filename)
401ObjectDatabaseFile = object_database.yaml
402
403# System subdirectory to load dialogues from (path)
404DialoguesDirectory = dialogue
405
406# System subdirectory to load quests from (path)
407QuestsDirectory = quests
408
409# User subdirectory to save screenshots to
410ScreenshotsDirectory = screenshots
411
412# User subdirectory to save games to
413SavesDirectory = saves
414
415# System subdirectory where gui files are loaded from (path)
416GuiDirectory = gui
417
418# System subdirectory where cursors are loaded from (path)
419CursorDirectory = cursors
420
421# File to use for default cursor (filename)
422CursorDefault = cursor_plain.png
423
424# File to use for up cursor (filename)
425CursorUp = cursor_up.png
426
427# File to use for right cursor (filename)
428CursorRight = cursor_right.png
429
430# File to use for down cursor (filename)
431CursorDown = cursor_down.png
432
433# File to use for left cursor (filename)
434CursorLeft = cursor_left.png
435
436# Player walk speed (digit)
437PCSpeed = 3\
438"""
439
440if __name__ == '__main__':
441    from optparse import OptionParser
442
443    usage = "usage: %prog [options] system[, system, ...]"
444    parser = OptionParser(usage=usage)
445
446    parser.add_option('-f', '--filename', default='system.cfg',
447                      help='Filename of output configuration file')
448
449    opts, args = parser.parse_args()
450   
451    with open(opts.filename, 'w') as f:
452        for line in DEFAULT_SETTINGS:
453            f.write(line)
Note: See TracBrowser for help on using the repository browser.