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

Revision 765, 9.5 KB checked in by aspidites, 9 years ago (diff)

Patch by Aspidites

+ now possible to close options window

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        if (value.startswith("\"") and value.endswith("\"") or
61            value.startswith("\'") and value.endswith("\'")):
62            # remove quotation marks and evaluate as a string
63            value = value[1:-1]
64        elif value.startswith('[') and value.endswith(']'):
65            value = [item.strip() for item in value[1:-1].split(',')]
66        elif value.lower() == 'true':
67            value = True
68        elif value.lower() == 'false':
69            value = False
70        elif value.isdigit():
71            value = int(value)
72        else:
73            try:
74                value = float(value)
75            except ValueError:
76                # leave as string
77                pass
78
79        self.__dict__[option] = value
80
81    def __getattr__(self, option):
82        """ Retrieve the value of the requested option
83            @param option: name of the option whose value is being requested
84        """
85        return self.__dict__[option]
86
87    def options(self):
88        """ Returns a dictionary of existing options """
89        options = self.__dict__
90        # get rid of properties that aren't actually options
91        if options.has_key('name'):
92            options.pop('name')
93
94        return options
95
96class Settings(object):
97    """ An object that represents a settings file, its sectons,
98        and the options defined within those sections.
99    """
100    def __init__(self, **kwargs):
101        """ initializes a new settings object.
102
103            @param paths: Either a string representing a path to a settings
104                          file or a list of such strings. If a single
105                          string is given, settings sections and options are
106                          simply read from it. If a list is given, each file
107                          is parsed sequentially with the next file's
108                          options taking precedence over the previous one's.
109                          Consider:
110                              files = ['foo.cfg' ,'bar.cfg']
111                              config = Config(files)
112                          First, foo.cfg is read, then, if similar options
113                          in bar.cfg exist, they overwrite the ones
114                          previously set by foo.cfg.
115            @type paths: either a string or list
116            @ivar config_file: Python object representing the settings
117                               file. Its purpose is to preserve the order of
118                               each section and its options on read and write.
119            @type config_file: list
120        """
121
122        self.config_file = ''
123        self.paths = kwargs.get('paths', None)
124        self.filename = kwargs.get('filename', 'settings.ini')
125
126        if self.paths is not None:
127            self.read(self.paths)
128        else:
129            self.paths = '.'
130
131    def __getattr__(self, name):
132        """ Returns a Section object to be used for assignment, creating one
133            if it doesn't exist.
134
135            @param name: name of section to be retrieved
136            @type name: string
137        """
138        if name in ['get', 'set']:
139            raise AttributeError("{0} is not a valid method. Please consult "
140                                 "Settings' documentation for a list of "
141                                 " available methods.".format(name))
142        else:
143            if not self.__dict__.has_key(name):
144                setattr(self, name, Section(name))
145
146        return getattr(self, name)
147
148    def read(self, paths=None, filename=None):
149        """ Reads a settings file and populates the settings object
150            with its sections and options. Calling this method without
151            any arguments simply re-reads the previously defined filename
152            and paths
153
154            @param path: name of file to be parsed.
155            @type path: string
156        """
157        if filename is not None:
158            self.filename = filename
159
160        if paths is None:
161            paths = self.paths
162
163        if hasattr(paths, 'split'):
164            self.paths = [paths]
165        else:
166            self.paths = paths
167
168        for path in self.paths:
169            location = os.path.join(path, self.filename)
170            section = None
171            try:
172                self.config_file = open(location, 'r').readlines()
173            except IOError:
174                pass
175
176            for line in self.config_file:
177                if line.startswith('#') or line.strip() == '':
178                    continue
179                elif line.startswith('[') and line.endswith(']\n'):
180                    getattr(self, line[1:-2])
181                    section = line[1:-2]
182                else:
183                    option, value = [item.strip() 
184                                     for item in line.split('=', 1)]
185                    setattr(getattr(self, section), option, value)
186
187    def write(self, path=None, filename=None):
188        """ Writes a settings file based on the settings object's
189            sections and options
190
191            @param path: Name of file to save to. By default, this is
192                             last file that was read when the Setings object
193                             was created.
194            @type path: string
195        """
196        if path is None:
197            path = self.paths[-1]
198
199        if filename is None:
200            filename = self.filename
201
202        for section in self.sections():
203            if '[{0}]\n'.format(section) not in self.config_file:
204                self.config_file.append('\n[{0}]\n'.format(section))
205                for option, value in getattr(self, section).options().iteritems():
206                    template = '{0} = {1}\n'.format(option, value)
207                    self.config_file.append(template)
208            else:
209                start_of_section = (self.config_file
210                                        .index('[{0}]\n'.format(section)) + 1)
211
212                for option, value in getattr(self, 
213                                             section).options().iteritems():
214
215                    if hasattr(value, 'sort'):
216                        value = '[{0}]'.format(', '.join(value))
217
218                    new_option = False
219                    template = '{0} = {1}\n'.format(option, value)
220                    for index, line in enumerate(self.config_file[:]):
221                        if option in line:
222                            new_option = False
223                            self.config_file[index] = template
224                            break
225                        else:
226                            new_option = True
227                    if new_option:
228                        self.config_file.insert(start_of_section, template)
229
230        location = os.path.join(path, filename)
231        with open(location, 'w') as out_stream:
232            for line in self.config_file:
233                out_stream.write(line)
234
235    def sections(self):
236        """ Returns a list of existing sections"""
237        sections = self.__dict__.keys()
238        sections.pop(sections.index('config_file'))
239        sections.pop(sections.index('paths'))
240        sections.pop(sections.index('filename'))
241        return sections
Note: See TracBrowser for help on using the repository browser.