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

Revision 760, 8.2 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites:

+ reverted the definition of FontGlyphs?
+ made it so that leading spaces aren't accidentally trimmed from options

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