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

Revision 761, 8.3 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites

+ removed kludge that assisted in detecting strings that started with a space
+ instead, all extra white space is removed from an options value
+ if leading and trailing whitespace is significant, an option's value should be

wrapped in quotes instead

+ updated settings.ini to comply with the above changes

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
29class Section(object):
30    """ An object that represents a section in a settings file.
31
32        Options can be added to a section by simply assigning a value to an
33            attribute:
34                section.foo = baz
35            would produce:
36                [section]
37                foo = baz
38            in the settings file. Options that do not exist on assignment
39            are created dynamcially.
40    """
41    def __init__(self, name):
42        """ Initialize a new section.
43
44            @param name: name of the section. In the INI file, sections are surrounded
45                by brackets ([name])
46            @type name: string
47        """
48        self.name = name
49
50    def __setattr__(self, option, value):
51        """ Assign a value to an option, converting types when appropriate.
52
53            @param option: name of the option to assign a value to.
54            @type option: string
55            @param value: value to be assigned to the option.
56            @type value: int, float, string, boolean, or list
57        """
58        # needed so that the leading space in FontGlyphs isn't accidentally
59        # striped
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        if 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.keys()
95
96#TODO: remember config filenames/paths
97#TODO: remove hard-coded settings filename/path
98class Settings(object):
99    """ An object that represents a settings file, its sectons,
100        and the options defined within those sections.
101    """
102    def __init__(self, *filenames):
103        """ initializes a new settings object.
104
105            @param filenames: Either a string representing a filename or a list
106                of such strings. If a single string is given, settings
107                sections and options are simply read from it. If a list is given,
108                each file is parsed sequentially with the next file's options
109                taking precedence over the previous one's. Consider:
110                    files = ['foo.cfg' ,'bar.cfg']
111                    config = Config(files)
112                First, foo.cfg is read, then, if similar options in bar.cfg exist,
113                they overwrite the ones previously set by foo.cfg.
114            @type filenames: either a string or list
115            @ivar config_file: Python object representing the settings
116                file. Its purpose is to preserve the order of each section and
117                its options on read and write.
118            @type config_file: list
119        """
120
121        self.config_file = ''
122
123        if hasattr(filenames, 'split'):
124            self.read(filenames)
125        else:
126            for filename in filenames:
127                self.read(filename)
128
129    def __getattr__(self, name):
130        """ Returns a Section object to be used for assignment, creating one
131            if it doesn't exist.
132
133            @param name: name of section to be retrieved
134            @type name: string
135        """
136        if name in ['get', 'set']:
137            raise AttributeError("{0} is not a valid method. Please consult "
138                                 "Settings' documentation for a list of "
139                                 " available methods.".format(name))
140        else:
141            if not self.__dict__.has_key(name):
142                setattr(self, name, Section(name))
143
144        return getattr(self, name)
145
146    def read(self, filename):
147        """ Reads a settings file and populates the settings object
148            with its sections and options.
149
150            @param filename: name of file to be parsed.
151            @type filename: string
152        """
153        section = None
154        try:
155            self.config_file = open(filename, 'r').readlines()
156        except IOError:
157            pass
158
159        for line in self.config_file:
160            if line.startswith('#') or line.strip() == '':
161                continue
162            elif line.startswith('[') and line.endswith(']\n'):
163                getattr(self, line[1:-2])
164                section = line[1:-2]
165            else:
166                option, value = [item.strip() for item in line.split('=', 1)]
167                setattr(getattr(self, section), option, value)
168
169    def write(self, filename):
170        """ Writes a settings file based on the settings object's
171            sections and options
172
173            @param filename: name of file to save to
174            @type filename: string
175        """
176        for section in self.sections():
177            if '[{0}]\n'.format(section) not in self.config_file:
178                self.config_file.append('\n[{0}]\n'.format(section))
179                for option, value in getattr(self, section).options().iteritems():
180                    template = '{0} = {1}\n'.format(option, value)
181                    self.config_file.append(template)
182            else:
183                start_of_section = (self.config_file
184                                        .index('[{0}]\n'.format(section)) + 1)
185                for option, value in getattr(self, 
186                                             section).options().iteritems():
187
188                    if hasattr(value, 'sort'):
189                        value = '[{0}]'.format(', '.join(value))
190
191                    new_option = False
192                    template = '{0} = {1}\n'.format(option, value)
193                    for index, line in enumerate(self.config_file[:]):
194                        if option in line:
195                            new_option = False
196                            self.config_file[index] = template
197                            break
198                        else:
199                            new_option = True
200                    if new_option:
201                        self.config_file.insert(start_of_section, template)
202
203        with open(filename, 'w') as out_stream:
204            for line in self.config_file:
205                out_stream.write(line)
206
207    def sections(self):
208        """ Returns a list of existing sections"""
209        sections = self.__dict__.keys()
210        sections.pop(sections.index('config_file'))
211        return sections
Note: See TracBrowser for help on using the repository browser.