File: ConfParser.py

package info (click to toggle)
getmail 3.2.5-3
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 428 kB
  • ctags: 161
  • sloc: python: 1,777; sh: 31; makefile: 12
file content (445 lines) | stat: -rw-r--r-- 15,158 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
#!/usr/bin/python
'''Configuration file parser for files similar to MS-Windows .ini files.
Meant as a replacement for the broken ConfigParser module in the Python
standard library.

Copyright (C) 2000-2003 Charles Cazabon <getmail @ discworld.dyndns.org>

This program is free software; you can redistribute it and/or
modify it under the terms of version 2 of the GNU General Public License
as published by the Free Software Foundation.  A copy of this license should
be included in the file COPYING.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

For documentation, see the Python standard module ConfigParser documentation.
This module is similar, except:
    o Options are supplied with option_name=option_value pairs only (not ':').
    o Comments are allowed on the same lines as data, with '#' as the comment
        delimiter.
    o Leading and trailing whitespace is ignored.
    o Whitespace surrounding the '=' sign is ignored.
    o Option values can be quoted with single or double quotes, to preserve
        leading or trailing whitespace, or if they contain a whitespace or the
        "#" symbol which would otherwise mark the start of a comment.
    o Empty option values must be quoted; use the empty string ("" or '')
    o Option values are returned as either:
        o a string, if the option name occurs once in the section
        o a list of strings, if the option name occurs multiple times
    o All the limitations on what characters can be in section headers and
        option values are gone, except that '#' is forbidden (because it
        starts a comment), and option names cannot contain '=' (because that
        starts a value).
'''

__version__ = '3.3.1'
__author__ = 'Charles Cazabon <software @ discworld.dyndns.org>'

#
# Imports
#

import string
import UserDict
import sys
import shlex
import cStringIO
from types import *


#
# ConfParser exception classes
#

# Base class for all ConfParser exceptions
class ConfParserException(Exception):
    pass

# Specific exceptions
class NoSectionError(ConfParserException):
    '''Exception raised when a specified section is not found.
    '''
    pass

class DuplicateSectionError(ConfParserException):
    '''Exception raised when mutliple sections with the same name are found, or
    if add_section() is called with the name of a section that is already
    present.
    '''
    pass

class NoOptionError(ConfParserException):
    '''Exception raised when a specified option is not found in the specified
    section.
    '''
    pass

class MissingSectionHeaderError(ConfParserException):
    '''Exception raised when attempting to parse a file which has no section
    headers.
    '''
    pass

class ParsingError(ConfParserException):
    '''Exception raised when errors occur attempting to parse a file.
    Also raised if defaults is not a dictionary, or when reading a file fails.
    These errors are not covered by exceptions in the standard Python
    ConfigParser module.
    '''
    pass

class ConversionError(ConfParserException):
    '''Exception raised when a value conversion (i.e. to integer) fails.
    '''
    pass


#
# Globals
#

debug = 0

#
# Helper functions
#

#######################################
def log(msg):
    if not debug:  return
    sys.stderr.write(msg + '\n')
    sys.stderr.flush()

#
# ConfParser SmartDict class
#

#######################################
class SmartDict(UserDict.UserDict):
    '''Dictionary class which handles lists and singletons intelligently.
    '''
    #######################################
    def __init__(self, initialdata = {}):
        '''Constructor.
        '''
        UserDict.UserDict.__init__(self, {})
        for (key, value) in initialdata.items():
            self.__setitem(key, value)

    #######################################
    def __getitem__(self, key):
        '''
        '''
        try:
            value = self.data[key]
            if len(value) == 1:
                return value[0]
            return value
        except KeyError, txt:
            raise KeyError(txt)

    #######################################
    def __setitem__(self, key, value):
        '''
        '''
        if type(value) in (ListType, TupleType):
            self.data[key] = list(value)
        else:
            self.data[key] = [value]


#
# Main ConfParser class
#

#######################################
class ConfParser:
    '''Class to parse a configuration file without all the limitations in
    ConfigParser.py, but without the dictionary formatting options either.
    '''
    #######################################
    def __init__(self, defaults = {}):
        '''Constructor.
        '''
        self.__rawdata = []
        self.__sectionlist = []
        self.__sections = []
        self.__defaults = SmartDict()

        try:
            for key in defaults.keys():
                self.__defaults[key] = defaults[key]

        except AttributeError:
            raise ParsingError('defaults not a dictionary (%s)' % defaults)

    #######################################
    def read(self, filelist):
        '''Read configuration file(s) from list of 1 or more filenames.
        '''
        if type(filelist) not in (ListType, TupleType):
            filelist = [filelist]

        try:
            for filename in filelist:
                log('Reading configuration file "%s"' % filename)
                f = open(filename, 'r')
                self.__rawdata = self.__rawdata + f.readlines()
                f.close()

        except IOError, txt:
            raise ParsingError('error reading configuration file (%s)' % txt)

        self.__parse()
        return self

    #######################################
    def __parse(self):
        '''Parse the read-in configuration file.
        '''
        config = string.join(self.__rawdata, '\n')
        f = cStringIO.StringIO(config)
        lex = shlex.shlex(f)
        lex.wordchars = lex.wordchars + '|/.,$^\\():;@-+?<>!%&*`~'
        section_name = ''
        option_name = ''
        option_value = ''

        while 1:
            token = lex.get_token()
            if token == '':
                break

            if not section_name:
                if token != '[':
                    raise ParsingError('expected section start, got %s' % token)
                section_name = ''
                while 1:
                    token = lex.get_token()
                    if token == ']':
                        break
                    if token == '':
                        raise ParsingError('expected section end, hit EOF')
                    if section_name:
                        section_name = section_name + ' '
                    section_name = section_name + token
                if not section_name:
                    raise ParsingError('expected section name, got nothing')

                section = SmartDict()
                # Collapse case on section names
                section_name = string.lower(section_name)
                if section_name in self.__sectionlist:
                    raise DuplicateSectionError('duplicate section (%s)' % section_name)
                section['__name__'] = section_name
                continue

            if token == '=':
                raise ParsingError('expected option name, got =')

            if token == '[':
                # Start new section
                lex.push_token(token)
                if section_name in self.__sectionlist:
                    raise DuplicateSectionError, ('duplicate section (%s)' % section_name)
                if section['__name__'] == 'default':
                    self.__defaults.update(section)
                self.__sectionlist.append(section_name)
                self.__sections.append(section.copy())
                section_name = ''
                continue

            if not option_name:
                option_name = token
                token = lex.get_token()
                if token != '=':
                    raise ParsingError('Expected =, got %s' % token)

                token = lex.get_token()
                if token in ('[', '='):
                    raise ParsingError('expected option value, got %s' % token)
                option_value = token

                if option_value[0] in ('"', "'") and option_value[0] == option_value[-1]:
                    option_value = option_value[1:-1]

                if section.has_key(option_name):
                    if type(section[option_name]) == ListType:
                        section[option_name].append(option_value)
                    else:
                        section[option_name] = [section[option_name], option_value]
                else:
                    section[option_name] = option_value

                option_name = ''

        # Done parsing
        if section_name:
            if section_name in self.__sectionlist:
                raise DuplicateSectionError, ('duplicate section (%s)' % section_name)
            if section['__name__'] == 'default':
                self.__defaults.update(section)
            self.__sectionlist.append(section_name)
            self.__sections.append(section.copy())

        if not self.__sectionlist:
            raise MissingSectionHeaderError('no section headers in file')

    #######################################
    def defaults(self):
        '''Return a dictionary containing the passed-in instance-wide defaults.
        '''
        return self.__defaults.copy()

    #######################################
    def has_section(self, section):
        '''Indicates whether the named section is present in the configuration.
        The default section is not acknowledged.
        '''
        section = string.lower(section)
        if section not in self.sections():
            return 0
        return 1

    #######################################
    def sections(self):
        '''Return a list of sections in the configuration file.
        '''
        s = self.__sectionlist[:]
        try:
            # Remove 'default' section from returned list
            i = s.index('default')
            del s[i]
        except ValueError:
            # No default section
            pass

        return s

    #######################################
    def options(self, section):
        '''Return list of options in section.
        '''
        try:
            s = self.__sectionlist.index(string.lower(section))

        except ValueError:
            raise NoSectionError('missing section:  "%s"' % section)

        return self.__sections[s].keys()

    #######################################
    def get(self, section, option, raw=0, _vars={}):
        '''Get an option value for the provided section. All the "%"
        interpolations are expanded in the return values, based on the defaults
        passed into the constructor, as well as the options _vars provided,
        unless the raw argument is true.  __vars contents must be lists.
        '''

        try:
            s = self.__sectionlist.index(string.lower(section))
            options = self.__sections[s]
        except ValueError:
            raise NoSectionError('missing section (%s)' % section)

        expand = self.__defaults.copy()
        expand.update(_vars)

        if not options.has_key(option):
            if expand.has_key(option):
                return expand[option]
            raise NoOptionError('section [%s] missing option (%s)' % (section, option))

        rawval = options[option]

        if raw:
            return rawval

        try:
            value = []
            if type(rawval) != ListType:
                rawval = [rawval]
            for part in rawval:
                try:
                    part = part % expand
                except:
                    raise
                value.append(part)
            if len(value) == 1:
                return value[0]
            return value
        except KeyError, txt:
            raise NoOptionError('section [%s] missing option (%s)' % (section, option))
        except (TypeError, ValueError), txt:
            raise ConversionError('invalid conversion or specification'
                ' for option %s (%s (%s))' % (option, rawval, txt))

    #######################################
    def getint(self, section, option):
        '''A convenience method which coerces the option in the specified
        section to an integer.
        '''
        val = self.get(section, option)
        try:
            return int(val)
        except (TypeError, ValueError), txt:
            raise ConversionError('option %s not an integer (%s)' % (option, val))

    #######################################
    def getfloat(self, section, option):
        '''A convenience method which coerces the option in the specified
        section to a floating point number.
        '''
        val = self.get(section, option)
        try:
            return float(val)
        except (TypeError, ValueError), txt:
            raise ConversionError('option %s not a float (%s)' % (option, val))

    #######################################
    def getboolean(self, section, option):
        '''A convenience method which coerces the option in the specified
        section to a boolean value. Note that the only accepted values for the
        option are "0" and "1", any others will raise ValueError.
        '''
        val = self.getint(section, option)
        return val != 0

    #######################################
    def getstring(self, section, option):
        '''A convenience method which enforces that the option value is a
        single string.  Multiple values will raise ValueError.
        '''
        val = self.get(section, option)
        if type(val) == ListType:
            raise ConversionError('expected single value, got list ([%s] %s == "%s")' % (section, option, val))
        return val

    #######################################
    def dump(self):
        '''Dump the parsed contents of the configuration file.
        '''
        sys.stderr.write('ConfParser dump:\n\n')
        sections = self.__sectionlist[:]
        sections.sort()
        for section in sections:
            sys.stderr.write('  Section [%s]:\n' % section)
            options = self.options(section)
            options.sort()
            for option in options:
                values = self.get(section, option)
                if type(values) == ListType:
                    sys.stderr.write('    %s:\n' % option)
                    for value in values:
                        sys.stderr.write('         %s\n' % value)
                else:
                    sys.stderr.write('    %s:  %s\n' % (option, values))
            sys.stderr.write('\n')