Mercurial > parpg-core
view src/parpg/settings.py @ 2:e2a8e3805b04
Made parpg-core the main repository to pull from and build.
* Added parpg-assets as a subrepo under the data folder.
* Moved the SConstruct script into parpg-core and modified it to build and install parpg-assets as well.
* Made a few minor changes to the SConstruct file from the defunct parpg-main repository.
* Modified unix parpg executable script template to be more forgiving of installing the parpg Python package outside of PYTHONPATH.
* Updated the .hgignore file to include temporary SCons files.
author | M. George Hansen <technopolitica@gmail.com> |
---|---|
date | Sun, 15 May 2011 14:51:41 -0700 |
parents | 1fd2201f5c36 |
children | 4706e0194af3 |
line wrap: on
line source
#!/usr/bin/env python2 # Copyright (C) 2011 Edwin Marshall <emarshall85@gmail.com> # This file is part of PARPG. # # PARPG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # PARPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PARPG. If not, see <http://www.gnu.org/licenses/>. """ Provides a class used for reading and writing various configurable options throughout the game This class produces an INI formated settings file as opposed to an XML formatted one. The reason that python's built-in ConfigurationParser isn't sufficient is because comments aren't preserved when writing a settings file, the order in which the options are written isn't preserved, and the interface used with this class is arguably more convenient that ConfigParser's. Default Settings may be generated by envoking this module from the command line: python -m settings.py [system] [data_directory] where [system] is one of local, windows, or linux (mac coming soon), and data_directory is the base path for the data files to be loaded. Both [system] and [data_directory] are option. If omitted, both default to whichever what is reasonable based on the system settings.py is run on """ import os import sys import platform #TODO: add logging to replace print statements class Section(object): """ An object that represents a section in a settings file. Options can be added to a section by simply assigning a value to an attribute: section.foo = baz would produce: [section] foo = baz in the settings file. Options that do not exist on assignment are created dynamcially. Values are automatically converted to the appropriate python type. Options that begin and end with brackets([, ]) are converted to lists, and options that are double-quoted (") are converted to strings. Section also recognizes booleans regardless of case, in addition to the literals 'yes' and 'no' of any case. Except in the case of double-quoted strings, extra white-space is trimmed, so you need not worry. For example: foo = bar is equivalent to : foo = baz """ def __init__(self, name): """ Initialize a new section. @param name: name of the section. In the INI file, sections are surrounded by brackets ([name]) @type name: string """ self.name = name def __setattr__(self, option, value): """ Assign a value to an option, converting types when appropriate. @param option: name of the option to assign a value to. @type option: string @param value: value to be assigned to the option. @type value: int, float, string, boolean, or list """ value = str(value) if value.startswith('[') and value.endswith(']'): value = [item.strip() for item in value[1:-1].split(',')] elif value.lower() == 'true' or value.lower() == 'yes': value = True elif value.lower() == 'false' or value.lower() == 'no': value = False elif value.isdigit(): value = int(value) else: try: value = float(value) except ValueError: # leave as string pass self.__dict__[option] = value def __getattribute__(self, option): """ Returns the option's value""" # Remove leading and trailing quotes from strings that have them return_value = object.__getattribute__(self, option) try: for key, value in return_value.iteritems(): if (hasattr(value, 'split') and value.startswith("\"") and value.endswith("\"")): return_value[key] = value[1:-1] except AttributeError: pass return return_value @property def options(self): """ Returns a dictionary of existing options """ options = self.__dict__ # get rid of properties that aren't actually options if options.has_key('name'): options.pop('name') return options class Settings(object): """ An object that represents a settings file, its sectons, and the options defined within those sections. """ def __init__(self, settings_path='', system_path='', user_path='', suffix='.cfg'): """ initializes a new settings object. If no paths are given, they are guessed based on whatever platform the script was run on. Examples: paths = ['/etc/parpg', '/home/user_name/.config/parpg'] settings = Settings(*paths) paths = {'system': '/etc/parpg', 'user': '/home/user_name/.config/parpg'} settings = Settings(**paths) settings = Settings('.') settigns = Settings() @param system_path: Path to the system settings file. @type system_path: string (must be a valid path) @param user_path: Path to the user settings file. Options that are missing from this file are propogated from the system settings file and saved on request @type user_path: string (must be a valid path) @param suffix: Suffix of the settings file that will be generated. @type suffix: string """ if not suffix.startswith('.'): suffix = '.' + suffix self.suffix = suffix self.settings_file = '' self.paths = {} if not system_path and not user_path and not settings_path: # use platform-specific values as paths (self.paths['system'], self.paths['user'], self.paths['settings']) = self.platform_paths() else: # convert supplied paths to absolute paths abs_paths = [os.path.expanduser(path) for path in [system_path, user_path, settings_path]] (self.paths['system'], self.paths['user'], self.paths['settings']) = abs_paths self.read() def __getattr__(self, name): """ Returns a Section object to be used for assignment, creating one if it doesn't exist. @param name: name of section to be retrieved @type name: string """ if name in ['get', 'set']: raise AttributeError("{0} is deprecated. Please consult Settings' " "documentation for information on how to " "create/modify sections and their respective " "options".format(name)) else: if not self.__dict__.has_key(name): setattr(self, name, Section(name)) return getattr(self, name) def platform_paths(self, system=None): if system is None: system = platform.system().lower() if system == 'linux': return (os.path.join(os.sep, 'usr', 'share', 'parpg'), os.path.join(os.environ['XDG_CONFIG_HOME'], 'parpg'), os.path.join(os.sep, 'etc', 'parpg')) elif system == 'windows': return (os.path.join(os.environ['PROGRAMFILES'], 'PARPG'), os.path.join(os.environ['USERDATA'], 'PARPG'), os.path.join(os.environ['PROGRAMFILES'], 'PARPG')) else: # TODO: determine values for Mac return None def read(self, filenames=None): """ Reads a settings file and populates the settings object with its sections and options. Calling this method without any arguments simply re-reads the previously defined filename and paths @param filenames: name of files to be parsed. @type path: string or list """ if filenames is None: filenames = [os.path.join(self.paths['settings'], 'system{0}'.format(self.suffix)), os.path.join(self.paths['user'], 'user{0}'.format(self.suffix))] elif hasattr(filenames, 'split'): filenames = [filenames] for filename in filenames: section = None try: self.settings_file = open(filename, 'r').readlines() except IOError as (errno, strerror): if errno == 2: if os.path.basename(filename).startswith('system'): print ('{0} could not be found. Please supply a ' 'different path or generate a system settings ' 'file with:\n' 'python2 -m parpg.settings').format(filename) sys.exit(1) else: print 'Error No. {0}: {1} {2}'.format(errno, filename, strerror) sys.exit(1) for line in self.settings_file: if line.startswith('#') or line.strip() == '': continue elif line.startswith('[') and line.endswith(']\n'): getattr(self, line[1:-2]) section = line[1:-2] else: option, value = [item.strip() for item in line.split('=', 1)] setattr(getattr(self, section), option, value) def write(self, filename=None): """ Writes a settings file based on the settings object's sections and options @param filename: Name of file to save to. By default, this is the user settings file. @type path: string """ if filename is None: filename = os.path.join(self.paths['user'], 'user{0}'.format(self.suffix)) for section in self.sections: if '[{0}]\n'.format(section) not in self.settings_file: self.settings_file.append('\n[{0}]\n'.format(section)) for option, value in getattr(self, section).options.iteritems(): template = '{0} = {1}\n'.format(option, value) self.settings_file.append(template) else: start_of_section = (self.settings_file .index('[{0}]\n'.format(section)) + 1) for option, value in getattr(self, section).options.iteritems(): if hasattr(value, 'sort'): value = '[{0}]'.format(', '.join(value)) new_option = False template = '{0} = {1}\n'.format(option, value) for index, line in enumerate(self.settings_file[:]): if option in line: new_option = False if str(value) not in line: self.settings_file[index] = template break else: new_option = True if new_option: while self.settings_file[start_of_section].startswith('#'): start_of_section += 1 self.settings_file.insert(start_of_section, template) with open(filename, 'w') as out_stream: for line in self.settings_file: out_stream.write(line) @property def sections(self): """ Returns a list of existing sections""" sections = self.__dict__.keys() sections.pop(sections.index('settings_file')) sections.pop(sections.index('paths')) sections.pop(sections.index('suffix')) return sections @property def system_path(self): return self.paths['system'] @property def user_path(self): return self.paths['user'] @property def settings_path(self): return self.paths['settings'] DEFAULT_SETTINGS = """\ [fife] #------------------------------------------------------------------------------ # Options marked with ? are untested/unknown # Game window's title (string) DO NOT EDIT! WindowTitle = PARPG Techdemo 2 # Icon to use for the game window's border (filename) DO NOT EDIT! WindowIcon = window_icon.png # Video driver to use. (?) VideoDriver = "" # Backend to use for graphics (OpenGL|SDL) RenderBackend = OpenGL # Run the game in fullscreen mode or not. (True|False) FullScreen = False # Screen Resolution's width. Not used if FullScreen is set to False (800|1024|etc) ScreenWidth = 1024 # Screen Resolution's height. Not used if FullScreen is set to False (600|768|etc) ScreenHeight = 768 # Screen DPI? (?) BitsPerPixel = 0 # ? (?) SDLRemoveFakeAlpha = 1 # Subdirectory to load icons from (path) IconsPath = icons # ? ([R, G, B]) ColorKey = [250, 0, 250] # ? (True|False) ColorKeyEnabled = False # Turn on sound effects and music (True|False) EnableSound = True # Initial volume of sound effects and music (0.0-100.0?) InitialVolume = 5.0 # Characters to use to render fonts. DO NOT EDIT! FontGlyphs = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\"" # Subdirectory to load fronts from (path) FontsPath = fonts # Font to load when game starts Font = oldtypewriter.ttf # Size of in-game fonts DefaultFontSize = 12 # ? (?) LogModules = [controller] # ? (?) PychanDebug = False # use Psyco Acceperation (True|False) UsePsyco = False # ? (?) ProfilingOn = False # Lighting Model to use (0-2) Lighting = 0 [parpg] #------------------------------------------------------------------------------ # System subdirectory to load maps from (path) MapsPath = maps # YAML file that contains the available maps (filename) MapsFile = maps.yaml # Map to load when game starts (filename) Map = Mall # ? (filename) AllAgentsFile = all_agents.yaml # System subdirectory to load objects from (path) ObjectsPath = objects # YAML file that contains the database of availabel objects (filename) ObjectDatabaseFile = object_database.yaml # System subdirectory to load dialogues from (path) DialoguesPath = dialogue # System subdirectory to load quests from (path) QuestsPath = quests # User subdirectory to save screenshots to ScreenshotsPath = screenshots # User subdirectory to save games to SavesPath = saves # System subdirectory where gui files are loaded from (path) GuiPath = gui # System subdirectory where cursors are loaded from (path) CursorPath = cursors # File to use for default cursor (filename) CursorDefault = cursor_plain.png # File to use for up cursor (filename) CursorUp = cursor_up.png # File to use for right cursor (filename) CursorRight = cursor_right.png # File to use for down cursor (filename) CursorDown = cursor_down.png # File to use for left cursor (filename) CursorLeft = cursor_left.png # Player walk speed (digit) PCSpeed = 3\ """ if __name__ == '__main__': from optparse import OptionParser usage = "usage: %prog [options] system[, system, ...]" parser = OptionParser(usage=usage) parser.add_option('-f', '--filename', default='system.cfg', help='Filename of output configuration file') opts, args = parser.parse_args() with open(opts.filename, 'w') as f: for line in DEFAULT_SETTINGS: f.write(line)