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

Revision 809, 16.3 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites:

  • Modified settings api slightly to prepare for other changes. Settings.filenames is replaced by Settings.paths as it will be more useful, IMHO.
  • Added copyright notice to settings module
  • Renamed DataDirectory? to SystemDataDirectory? and added UserDataDirectory? to [fife] section of default config file.
  • Implemented proper command line interface for settings.py

+ If multiple system names are given, multiple settings files

are generated, with the system name prepended to the filename

+ Both system and user data directories may be specified

+ It is possible to change the filename
+ not speficying anything will assume the system to be the one that

the script was run on and a filename of system.cfg

  • saves directory is now configurable (located in user's data directory by default
  • removed hard coded paths to open and save file browsers
  • removed default argument for FileBrowser?'s gui xml since it didnt' exist
  • screenshot directory now resides in user data directory
  • updated help screen
  • screenshots directory is now created only when its needed
  • when saving of games is implemented, the same should be done for the saves directory
  • 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
83            @param value: value to be assigned to the option.
84            @type value: int, float, string, boolean, or list
85        """
86        value = str(value)
87        if value.startswith('[') and value.endswith(']'):
88            value = [item.strip() for item in value[1:-1].split(',')]
89        elif value.lower() == 'true' or value.lower() == 'yes':
90            value = True
91        elif value.lower() == 'false' or value.lower() == 'no':
92            value = False
93        elif value.isdigit():
94            value = int(value)
95        else:
96            try:
97                value = float(value)
98            except ValueError:
99                # leave as string
100                pass
101
102        self.__dict__[option] = value
103
104    def __getattribute__(self, option):
105        """ Returns the option's value"""
106        # Remove leading and trailing quotes from strings that have them
107        return_value = object.__getattribute__(self, option)
108        try:
109            for key, value in return_value.iteritems():
110                if (hasattr(value, 'split') and 
111                    value.startswith("\"") and value.endswith("\"")):
112                    return_value[key] = value[1:-1]
113        except AttributeError:
114            pass
115
116        return return_value
117
118    @property
119    def options(self):
120        """ Returns a dictionary of existing options """
121        options = self.__dict__
122        # get rid of properties that aren't actually options
123        if options.has_key('name'):
124            options.pop('name')
125
126        return options
127
128class Settings(object):
129    """ An object that represents a settings file, its sectons,
130        and the options defined within those sections.
131    """
132    def __init__(self, system_path, user_path=None, suffix='.cfg'):
133        """ initializes a new settings object.
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            @param system_path: Path to the system settings file.
146            @type system_path: string (must be a valid path)
147
148            @param user_path: Path to the user settings file. Options that
149                              are missing from this file are propogated
150                              from the system settings file and saved on
151                              request
152            @type user_path: string (must be a valid path)
153           
154            @param suffix: Suffix of the settings file that will be generated.
155            @type suffix: string
156        """
157
158        if not suffix.startswith('.'):
159            suffix = '.' + suffix
160
161        self.suffix = suffix
162        self.settings_file = ''
163
164        if user_path is None:
165            user_path = system_path
166
167        self.paths = {'system': system_path, 'user': user_path}
168        self.read()
169
170    def __getattr__(self, name):
171        """ Returns a Section object to be used for assignment, creating one
172            if it doesn't exist.
173
174            @param name: name of section to be retrieved
175            @type name: string
176        """
177        if name in ['get', 'set']:
178            raise AttributeError("{0} is deprecated. Please consult Settings' "
179                                  "documentation for information on how to "
180                                  "create/modify sections and their respective "
181                                  "options".format(name))
182        else:
183            if not self.__dict__.has_key(name):
184                setattr(self, name, Section(name))
185
186        return getattr(self, name)
187
188    def read(self, filenames=None):
189        """ Reads a settings file and populates the settings object
190            with its sections and options. Calling this method without
191            any arguments simply re-reads the previously defined filename
192            and paths
193
194            @param filenames: name of files to be parsed.
195            @type path: string or list
196        """
197       
198        if filenames is None:
199            filenames = [os.path.join(self.paths['system'], 
200                         'system{0}'.format(self.suffix)),
201                         os.path.join(self.paths['user'],
202                         'user{0}'.format(self.suffix))]
203        elif hasattr(filenames, 'split'):
204            filenames = [filenames]
205
206        for filename in filenames:
207            section = None
208            try:
209                self.settings_file = open(filename, 'r').readlines()
210            except IOError:
211                pass
212
213            for line in self.settings_file:
214                if line.startswith('#') or line.strip() == '':
215                    continue
216                elif line.startswith('[') and line.endswith(']\n'):
217                    getattr(self, line[1:-2])
218                    section = line[1:-2]
219                else:
220                    option, value = [item.strip() 
221                                     for item in line.split('=', 1)]
222                    setattr(getattr(self, section), option, value)
223
224    def write(self, filename=None):
225        """ Writes a settings file based on the settings object's
226            sections and options
227
228            @param filename: Name of file to save to. By default, this is
229                             the user settings file.
230            @type path: string
231        """
232        if filename is None:
233            filename = os.path.join(self.paths['user'], 
234                                    'user{0}'.format(self.suffix))
235
236        for section in self.sections:
237            if '[{0}]\n'.format(section) not in self.settings_file:
238                self.settings_file.append('\n[{0}]\n'.format(section))
239                for option, value in getattr(self, section).options.iteritems():
240                    template = '{0} = {1}\n'.format(option, value)
241                    self.settings_file.append(template)
242            else:
243                start_of_section = (self.settings_file
244                                        .index('[{0}]\n'.format(section)) + 1)
245
246                for option, value in getattr(self, 
247                                             section).options.iteritems():
248                    if hasattr(value, 'sort'):
249                        value = '[{0}]'.format(', '.join(value))
250
251                    new_option = False
252                    template = '{0} = {1}\n'.format(option, value)
253                    for index, line in enumerate(self.settings_file[:]):
254                        if option in line:
255                            new_option = False
256                            if str(value) not in line:
257                                self.settings_file[index] = template
258
259                            break
260                        else:
261                            new_option = True
262                    if new_option:
263                        while self.settings_file[start_of_section].startswith('#'):
264                            start_of_section += 1
265
266                        self.settings_file.insert(start_of_section, template)
267
268        with open(filename, 'w') as out_stream:
269            for line in self.settings_file:
270                out_stream.write(line)
271
272    @property
273    def sections(self):
274        """ Returns a list of existing sections"""
275        sections = self.__dict__.keys()
276        sections.pop(sections.index('settings_file'))
277        sections.pop(sections.index('paths'))
278        sections.pop(sections.index('suffix'))
279       
280        return sections
281
282DEFAULT_SETTINGS = """\
283[fife]
284# Options marked with ? are untested/unknown
285
286
287# Game window's title (string) DO NOT EDIT!
288WindowTitle = PARPG Techdemo 2
289
290# Icon to use for the game window's border (filename) DO NOT EDIT!
291WindowIcon = window_icon.png
292
293# Video driver to use. (?)
294VideoDriver = ""
295
296# Backend to use for graphics (OpenGL|SDL)
297RenderBackend = OpenGL
298
299# Run the game in fullscreen mode or not. (True|False)
300FullScreen = False
301
302# Screen Resolution's width. Not used if FullScreen is set to False (800|1024|etc)
303ScreenWidth = 1024
304
305# Screen Resolution's height. Not used if FullScreen is set to False (600|768|etc)
306ScreenHeight = 768
307
308# Screen DPI? (?)
309BitsPerPixel = 0
310
311# ? (?)
312SDLRemoveFakeAlpha = 1
313
314# Subdirectory to load icons from (path)
315IconsDirectory = icons
316
317# ? ([R, G, B])
318ColorKey = [250, 0, 250]
319
320# ? (True|False)
321ColorKeyEnabled = False
322
323# Turn on sound effects and music (True|False)
324EnableSound = True
325
326# Initial volume of sound effects and music (0.0-100.0?)
327InitialVolume = 5.0
328
329# Characters to use to render fonts. DO NOT EDIT!
330FontGlyphs = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\""
331
332# Subdirectory to load fronts from (path)
333FontsDirectory = fonts
334
335# Font to load when game starts
336Font = oldtypewriter.ttf
337
338# Size of in-game fonts
339DefaultFontSize = 12
340
341# ? (?)
342LogModules = [controller]
343
344# ? (?)
345PychanDebug = False
346
347# use Psyco Acceperation (True|False)
348UsePsyco = False
349
350# ? (?)
351ProfilingOn = False
352
353# Lighting Model to use (0-2)
354Lighting = 0
355
356[parpg]
357
358# System subdirectory to load maps from (path)
359MapsDirectory = maps
360
361# YAML file that contains the available maps (filename)
362MapsFile = maps.yaml
363
364# Map to load when game starts (filename)
365Map = Mall
366
367# ? (filename)
368AllAgentsFile = all_agents.yaml
369
370# System subdirectory to load objects from (path)
371ObjectsDirectory = objects
372
373# YAML file that contains the database of availabel objects (filename)
374ObjectDatabaseFile = object_database.yaml
375
376# System subdirectory to load dialogues from (path)
377DialoguesDirectory = dialogue
378
379# System subdirectory to load quests from (path)
380QuestsDirectory = quests
381
382# User subdirectory to save screenshots to
383ScreenshotsDirectory = screenshots
384
385# User subdirectory to save games to
386SavesDirectory = saves
387
388# System subdirectory where gui files are loaded from (path)
389GuiDirectory = gui
390
391# System subdirectory where cursors are loaded from (path)
392CursorDirectory = cursors
393
394# File to use for default cursor (filename)
395CursorDefault = cursor_plain.png
396
397# File to use for up cursor (filename)
398CursorUp = cursor_up.png
399
400# File to use for right cursor (filename)
401CursorRight = cursor_right.png
402
403# File to use for down cursor (filename)
404CursorDown = cursor_down.png
405
406# File to use for left cursor (filename)
407CursorLeft = cursor_left.png
408
409# Player walk speed (digit)
410PCSpeed = 3\
411"""
412
413def write_defaults(system=None, sys_dir="", user_dir="", filename='system.cfg'):
414    #TODO: write defaults for Mac OS X
415    if system is None:
416        system = platform.system().lower()
417
418    # automatically set sys_dir since it wasn't defined
419    if len(sys_dir) == 0:
420        if system == 'local':
421            sys_dir = os.path.join('.')
422        elif system == 'linux':
423            sys_dir = os.path.join(os.environ['XDG_DATA_DIRS'].split(':')[0],
424                                   'parpg')
425        elif system == 'windows':
426            sys_dir = os.path.join(os.environ['PROGRAMFILES'], 'PARPG')
427
428    # automatically set user_dir since it wasn't defined
429    if len(user_dir) == 0:
430        if system == 'local':
431            user_dir = os.path.join('.')
432        elif system == 'linux':
433            user_dir = os.path.join(os.environ['XDG_CONFIG_HOME'], 'parpg')
434        elif system == 'windows':
435            user_dir = os.path.join(os.environ['APPDATA'], 'PARPG')
436
437    # insert the defined system and user data directories into the template
438    default_settings = DEFAULT_SETTINGS.split('\n')
439    default_settings.insert(3, ('# Directory where files necessary to run '
440                                'PARPG are located\n'
441                                'SystemDataDirectory = {0}\n\n'
442                                '# Directory where saved games and '
443                                'screenshots are stored\n'
444                                'UserDataDirectory = {1}').format(sys_dir,
445                                                                  user_dir))
446
447    with open(filename, 'w') as f:
448        for line in default_settings:
449            f.write(line + '\n')
450
451if __name__ == '__main__':
452    from optparse import OptionParser
453
454    usage = "usage: %prog [options] system[, system, ...]"
455    parser = OptionParser(usage=usage)
456
457    parser.add_option('-s', '--sys-dir', default='',
458                      help=('System Data Directory where data files '
459                            'necessary to make the game run are stored'))
460    parser.add_option('-u', '--user-dir', default='',
461                      help=('User Data Directory where saved games and '
462                            'screenshots are stored.')) 
463    parser.add_option('-f', '--filename', default='system.cfg',
464                      help='Filename of output configuration file')
465
466    opts, args = parser.parse_args()
467
468    if len(args) == 0:
469        write_defaults(None, opts.sys_dir, opts.user_dir)
470    else: 
471        valid_systems = ['local', 'linux', 'windows']
472        # remove invalid systems from args
473        args = [arg for arg in args if arg in valid_systems]
474
475        if len(args) == 0:
476            parser.show_help()
477        elif len(args) == 1:
478            write_defaults(arg, opts.sys_dir, opts.user_dir, opts.filename)
479        else:
480            [write_defaults(arg, opts.sys_dir, opts.user_dir,
481                            '{0}_{1}'.format(arg, opts.filename)) 
482             for arg in args]
Note: See TracBrowser for help on using the repository browser.