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

Revision 790, 9.6 KB checked in by aspidites, 9 years ago (diff)

Patch by Aspidites:

  • removed files related to windows packaging
  • simplified settings module API:

+ paths argument no longer exists. There is now exactly

3 arguments: system_path, user_path, and prefix, which

will eventually be explained in documentation

+ user config file is now properly created, leaving the

system config file untouched

+ by default, the new suffix is '.cfg'.
+ sections and options are now properties instead of callables

  • updated menus.py to cope with these minor changes
  • updated setup scripts (both) with missing path information

+ only thing remaining for the windows installer is to test on

on a windows machine (as opposed to wine), and to get
prerequisites installed correctly (fife installer giving trouble)

Line 
1#   This file is part of PARPG.
2#
3#   PARPG is free software: you can redistribute it and/or modify
4#   it under the terms of the GNU General Public License as published by
5#   the Free Software Foundation, either version 3 of the License, or
6#   (at your option) any later version.
7#
8#   PARPG is distributed in the hope that it will be useful,
9#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#   GNU General Public License for more details.
12#
13#   You should have received a copy of the GNU General Public License
14#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
15
16""" Provides a class used for reading and writing various configurable options
17    throughout the game
18
19    This class produces an INI formated settings file as opposed to an XML
20    formatted one. The reason that python's built-in ConfigurationParser isn't
21    sufficient is because comments aren't preserved when writing a settings
22    file, the order in which the options are written isn't preserved, and the
23    interface used with this class is arguably more convenient that
24    ConfigParser's.
25"""
26
27import os
28
29#TODO: prevent new sections from being created if an option hasn't been defined
30#TODO: update documentation for Settings' __init__, read, and write methods
31class Section(object):
32    """ An object that represents a section in a settings file.
33
34        Options can be added to a section by simply assigning a value to an
35        attribute:
36            section.foo = baz
37        would produce:
38            [section]
39            foo = baz
40        in the settings file. Options that do not exist on assignment
41        are created dynamcially.
42    """
43    def __init__(self, name):
44        """ Initialize a new section.
45
46            @param name: name of the section. In the INI file, sections are surrounded
47                         by brackets ([name])
48            @type name: string
49        """
50        self.name = name
51
52    def __setattr__(self, option, value):
53        """ Assign a value to an option, converting types when appropriate.
54
55            @param option: name of the option to assign a value to.
56            @type option: string
57            @param value: value to be assigned to the option.
58            @type value: int, float, string, boolean, or list
59        """
60        value = str(value)
61        if value.startswith('[') and value.endswith(']'):
62            value = [item.strip() for item in value[1:-1].split(',')]
63        elif value.lower() == 'true':
64            value = True
65        elif value.lower() == 'false':
66            value = False
67        elif value.isdigit():
68            value = int(value)
69        else:
70            try:
71                value = float(value)
72            except ValueError:
73                # leave as string
74                pass
75
76        self.__dict__[option] = value
77
78    def __getattribute__(self, option):
79        # Remove leading and trailing quotes from strings that have them
80        return_value = object.__getattribute__(self, option)
81        try:
82            for key, value in return_value.iteritems():
83                if (hasattr(value, 'split') and 
84                    value.startswith("\"") and value.endswith("\"")):
85                    return_value[key] = value[1:-1]
86        except AttributeError:
87            pass
88
89        return return_value
90
91    @property
92    def options(self):
93        """ Returns a dictionary of existing options """
94        options = self.__dict__
95        # get rid of properties that aren't actually options
96        if options.has_key('name'):
97            options.pop('name')
98
99        return options
100
101class Settings(object):
102    """ An object that represents a settings file, its sectons,
103        and the options defined within those sections.
104    """
105    def __init__(self, system_path, user_path=None, suffix='.cfg'):
106        """ initializes a new settings object.
107
108            @param paths: Either a string representing a path to a settings
109                          file or a list of such strings. If a single
110                          string is given, settings sections and options are
111                          simply read from it. If a list is given, each file
112                          is parsed sequentially with the next file's
113                          options taking precedence over the previous one's.
114                          Consider:
115                              files = ['foo.cfg' ,'bar.cfg']
116                              config = Config(files)
117                          First, foo.cfg is read, then, if similar options
118                          in bar.cfg exist, they overwrite the ones
119                          previously set by foo.cfg.
120            @type paths: either a string or list
121            @ivar settings_file: Python object representing the settings
122                               file. Its purpose is to preserve the order of
123                               each section and its options on read and write.
124            @type settings_file: list
125        """
126
127        self.suffix = suffix
128        self.settings_file = ''
129
130        if user_path is None:
131            user_path = system_path
132
133        self.filenames = {'system': os.path.join(system_path, 
134                                                 'system{0}'.format(self.suffix)),
135                          'user': os.path.join(user_path,
136                                               'user{0}'.format(self.suffix))}
137
138        self.read()
139
140    def __getattr__(self, name):
141        """ Returns a Section object to be used for assignment, creating one
142            if it doesn't exist.
143
144            @param name: name of section to be retrieved
145            @type name: string
146        """
147        if name in ['get', 'set']:
148            raise AttributeError("{0} is not a valid method. Please consult "
149                                 "Settings' documentation for a list of "
150                                 " available methods.".format(name))
151        else:
152            if not self.__dict__.has_key(name):
153                setattr(self, name, Section(name))
154
155        return getattr(self, name)
156
157    def read(self, filenames=None):
158        """ Reads a settings file and populates the settings object
159            with its sections and options. Calling this method without
160            any arguments simply re-reads the previously defined filename
161            and paths
162
163            @param path: name of file to be parsed.
164            @type path: string
165        """
166       
167        if filenames is None:
168            filenames = [self.filenames['system'], self.filenames['user']]
169
170        for filename in filenames:
171            section = None
172            try:
173                self.settings_file = open(filename, 'r').readlines()
174            except IOError:
175                pass
176
177            for line in self.settings_file:
178                if line.startswith('#') or line.strip() == '':
179                    continue
180                elif line.startswith('[') and line.endswith(']\n'):
181                    getattr(self, line[1:-2])
182                    section = line[1:-2]
183                else:
184                    option, value = [item.strip() 
185                                     for item in line.split('=', 1)]
186                    setattr(getattr(self, section), option, value)
187
188    def write(self, filename=None):
189        """ Writes a settings file based on the settings object's
190            sections and options
191
192            @param path: Name of file to save to. By default, this is
193                             last file that was read when the Setings object
194                             was created.
195            @type path: string
196        """
197        if filename is None:
198            filename = self.filenames['user']
199
200        for section in self.sections:
201            if '[{0}]\n'.format(section) not in self.settings_file:
202                self.settings_file.append('\n[{0}]\n'.format(section))
203                for option, value in getattr(self, section).options.iteritems():
204                    template = '{0} = {1}\n'.format(option, value)
205                    self.settings_file.append(template)
206            else:
207                start_of_section = (self.settings_file
208                                        .index('[{0}]\n'.format(section)) + 1)
209
210                for option, value in getattr(self, 
211                                             section).options.iteritems():
212                    if hasattr(value, 'sort'):
213                        value = '[{0}]'.format(', '.join(value))
214
215                    new_option = False
216                    template = '{0} = {1}\n'.format(option, value)
217                    for index, line in enumerate(self.settings_file[:]):
218                        if option in line:
219                            new_option = False
220                            if str(value) not in line:
221                                self.settings_file[index] = template
222
223                            break
224                        else:
225                            new_option = True
226                    if new_option:
227                        self.settings_file.insert(start_of_section, template)
228
229        with open(filename, 'w') as out_stream:
230            for line in self.settings_file:
231                out_stream.write(line)
232
233    @property
234    def sections(self):
235        """ Returns a list of existing sections"""
236        sections = self.__dict__.keys()
237        sections.pop(sections.index('settings_file'))
238        sections.pop(sections.index('filenames'))
239        sections.pop(sections.index('suffix'))
240       
241        return sections
Note: See TracBrowser for help on using the repository browser.