]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/ConfigParser.py
dist/mkfile: run binds in subshell
[plan9front.git] / sys / lib / python / ConfigParser.py
1 """Configuration file parser.
2
3 A setup file consists of sections, lead by a "[section]" header,
4 and followed by "name: value" entries, with continuations and such in
5 the style of RFC 822.
6
7 The option values can contain format strings which refer to other values in
8 the same section, or values in a special [DEFAULT] section.
9
10 For example:
11
12     something: %(dir)s/whatever
13
14 would resolve the "%(dir)s" to the value of dir.  All reference
15 expansions are done late, on demand.
16
17 Intrinsic defaults can be specified by passing them into the
18 ConfigParser constructor as a dictionary.
19
20 class:
21
22 ConfigParser -- responsible for parsing a list of
23                 configuration files, and managing the parsed database.
24
25     methods:
26
27     __init__(defaults=None)
28         create the parser and specify a dictionary of intrinsic defaults.  The
29         keys must be strings, the values must be appropriate for %()s string
30         interpolation.  Note that `__name__' is always an intrinsic default;
31         its value is the section's name.
32
33     sections()
34         return all the configuration section names, sans DEFAULT
35
36     has_section(section)
37         return whether the given section exists
38
39     has_option(section, option)
40         return whether the given option exists in the given section
41
42     options(section)
43         return list of configuration options for the named section
44
45     read(filenames)
46         read and parse the list of named configuration files, given by
47         name.  A single filename is also allowed.  Non-existing files
48         are ignored.  Return list of successfully read files.
49
50     readfp(fp, filename=None)
51         read and parse one configuration file, given as a file object.
52         The filename defaults to fp.name; it is only used in error
53         messages (if fp has no `name' attribute, the string `<???>' is used).
54
55     get(section, option, raw=False, vars=None)
56         return a string value for the named option.  All % interpolations are
57         expanded in the return values, based on the defaults passed into the
58         constructor and the DEFAULT section.  Additional substitutions may be
59         provided using the `vars' argument, which must be a dictionary whose
60         contents override any pre-existing defaults.
61
62     getint(section, options)
63         like get(), but convert value to an integer
64
65     getfloat(section, options)
66         like get(), but convert value to a float
67
68     getboolean(section, options)
69         like get(), but convert value to a boolean (currently case
70         insensitively defined as 0, false, no, off for False, and 1, true,
71         yes, on for True).  Returns False or True.
72
73     items(section, raw=False, vars=None)
74         return a list of tuples with (name, value) for each option
75         in the section.
76
77     remove_section(section)
78         remove the given file section and all its options
79
80     remove_option(section, option)
81         remove the given option from the given section
82
83     set(section, option, value)
84         set the given option
85
86     write(fp)
87         write the configuration state in .ini format
88 """
89
90 import re
91
92 __all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError",
93            "InterpolationError", "InterpolationDepthError",
94            "InterpolationSyntaxError", "ParsingError",
95            "MissingSectionHeaderError",
96            "ConfigParser", "SafeConfigParser", "RawConfigParser",
97            "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"]
98
99 DEFAULTSECT = "DEFAULT"
100
101 MAX_INTERPOLATION_DEPTH = 10
102
103
104
105 # exception classes
106 class Error(Exception):
107     """Base class for ConfigParser exceptions."""
108
109     def __init__(self, msg=''):
110         self.message = msg
111         Exception.__init__(self, msg)
112
113     def __repr__(self):
114         return self.message
115
116     __str__ = __repr__
117
118 class NoSectionError(Error):
119     """Raised when no section matches a requested option."""
120
121     def __init__(self, section):
122         Error.__init__(self, 'No section: %r' % (section,))
123         self.section = section
124
125 class DuplicateSectionError(Error):
126     """Raised when a section is multiply-created."""
127
128     def __init__(self, section):
129         Error.__init__(self, "Section %r already exists" % section)
130         self.section = section
131
132 class NoOptionError(Error):
133     """A requested option was not found."""
134
135     def __init__(self, option, section):
136         Error.__init__(self, "No option %r in section: %r" %
137                        (option, section))
138         self.option = option
139         self.section = section
140
141 class InterpolationError(Error):
142     """Base class for interpolation-related exceptions."""
143
144     def __init__(self, option, section, msg):
145         Error.__init__(self, msg)
146         self.option = option
147         self.section = section
148
149 class InterpolationMissingOptionError(InterpolationError):
150     """A string substitution required a setting which was not available."""
151
152     def __init__(self, option, section, rawval, reference):
153         msg = ("Bad value substitution:\n"
154                "\tsection: [%s]\n"
155                "\toption : %s\n"
156                "\tkey    : %s\n"
157                "\trawval : %s\n"
158                % (section, option, reference, rawval))
159         InterpolationError.__init__(self, option, section, msg)
160         self.reference = reference
161
162 class InterpolationSyntaxError(InterpolationError):
163     """Raised when the source text into which substitutions are made
164     does not conform to the required syntax."""
165
166 class InterpolationDepthError(InterpolationError):
167     """Raised when substitutions are nested too deeply."""
168
169     def __init__(self, option, section, rawval):
170         msg = ("Value interpolation too deeply recursive:\n"
171                "\tsection: [%s]\n"
172                "\toption : %s\n"
173                "\trawval : %s\n"
174                % (section, option, rawval))
175         InterpolationError.__init__(self, option, section, msg)
176
177 class ParsingError(Error):
178     """Raised when a configuration file does not follow legal syntax."""
179
180     def __init__(self, filename):
181         Error.__init__(self, 'File contains parsing errors: %s' % filename)
182         self.filename = filename
183         self.errors = []
184
185     def append(self, lineno, line):
186         self.errors.append((lineno, line))
187         self.message += '\n\t[line %2d]: %s' % (lineno, line)
188
189 class MissingSectionHeaderError(ParsingError):
190     """Raised when a key-value pair is found before any section header."""
191
192     def __init__(self, filename, lineno, line):
193         Error.__init__(
194             self,
195             'File contains no section headers.\nfile: %s, line: %d\n%r' %
196             (filename, lineno, line))
197         self.filename = filename
198         self.lineno = lineno
199         self.line = line
200
201
202
203 class RawConfigParser:
204     def __init__(self, defaults=None):
205         self._sections = {}
206         self._defaults = {}
207         if defaults:
208             for key, value in defaults.items():
209                 self._defaults[self.optionxform(key)] = value
210
211     def defaults(self):
212         return self._defaults
213
214     def sections(self):
215         """Return a list of section names, excluding [DEFAULT]"""
216         # self._sections will never have [DEFAULT] in it
217         return self._sections.keys()
218
219     def add_section(self, section):
220         """Create a new section in the configuration.
221
222         Raise DuplicateSectionError if a section by the specified name
223         already exists.
224         """
225         if section in self._sections:
226             raise DuplicateSectionError(section)
227         self._sections[section] = {}
228
229     def has_section(self, section):
230         """Indicate whether the named section is present in the configuration.
231
232         The DEFAULT section is not acknowledged.
233         """
234         return section in self._sections
235
236     def options(self, section):
237         """Return a list of option names for the given section name."""
238         try:
239             opts = self._sections[section].copy()
240         except KeyError:
241             raise NoSectionError(section)
242         opts.update(self._defaults)
243         if '__name__' in opts:
244             del opts['__name__']
245         return opts.keys()
246
247     def read(self, filenames):
248         """Read and parse a filename or a list of filenames.
249
250         Files that cannot be opened are silently ignored; this is
251         designed so that you can specify a list of potential
252         configuration file locations (e.g. current directory, user's
253         home directory, systemwide directory), and all existing
254         configuration files in the list will be read.  A single
255         filename may also be given.
256
257         Return list of successfully read files.
258         """
259         if isinstance(filenames, basestring):
260             filenames = [filenames]
261         read_ok = []
262         for filename in filenames:
263             try:
264                 fp = open(filename)
265             except IOError:
266                 continue
267             self._read(fp, filename)
268             fp.close()
269             read_ok.append(filename)
270         return read_ok
271
272     def readfp(self, fp, filename=None):
273         """Like read() but the argument must be a file-like object.
274
275         The `fp' argument must have a `readline' method.  Optional
276         second argument is the `filename', which if not given, is
277         taken from fp.name.  If fp has no `name' attribute, `<???>' is
278         used.
279
280         """
281         if filename is None:
282             try:
283                 filename = fp.name
284             except AttributeError:
285                 filename = '<???>'
286         self._read(fp, filename)
287
288     def get(self, section, option):
289         opt = self.optionxform(option)
290         if section not in self._sections:
291             if section != DEFAULTSECT:
292                 raise NoSectionError(section)
293             if opt in self._defaults:
294                 return self._defaults[opt]
295             else:
296                 raise NoOptionError(option, section)
297         elif opt in self._sections[section]:
298             return self._sections[section][opt]
299         elif opt in self._defaults:
300             return self._defaults[opt]
301         else:
302             raise NoOptionError(option, section)
303
304     def items(self, section):
305         try:
306             d2 = self._sections[section]
307         except KeyError:
308             if section != DEFAULTSECT:
309                 raise NoSectionError(section)
310             d2 = {}
311         d = self._defaults.copy()
312         d.update(d2)
313         if "__name__" in d:
314             del d["__name__"]
315         return d.items()
316
317     def _get(self, section, conv, option):
318         return conv(self.get(section, option))
319
320     def getint(self, section, option):
321         return self._get(section, int, option)
322
323     def getfloat(self, section, option):
324         return self._get(section, float, option)
325
326     _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
327                        '0': False, 'no': False, 'false': False, 'off': False}
328
329     def getboolean(self, section, option):
330         v = self.get(section, option)
331         if v.lower() not in self._boolean_states:
332             raise ValueError, 'Not a boolean: %s' % v
333         return self._boolean_states[v.lower()]
334
335     def optionxform(self, optionstr):
336         return optionstr.lower()
337
338     def has_option(self, section, option):
339         """Check for the existence of a given option in a given section."""
340         if not section or section == DEFAULTSECT:
341             option = self.optionxform(option)
342             return option in self._defaults
343         elif section not in self._sections:
344             return False
345         else:
346             option = self.optionxform(option)
347             return (option in self._sections[section]
348                     or option in self._defaults)
349
350     def set(self, section, option, value):
351         """Set an option."""
352         if not section or section == DEFAULTSECT:
353             sectdict = self._defaults
354         else:
355             try:
356                 sectdict = self._sections[section]
357             except KeyError:
358                 raise NoSectionError(section)
359         sectdict[self.optionxform(option)] = value
360
361     def write(self, fp):
362         """Write an .ini-format representation of the configuration state."""
363         if self._defaults:
364             fp.write("[%s]\n" % DEFAULTSECT)
365             for (key, value) in self._defaults.items():
366                 fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
367             fp.write("\n")
368         for section in self._sections:
369             fp.write("[%s]\n" % section)
370             for (key, value) in self._sections[section].items():
371                 if key != "__name__":
372                     fp.write("%s = %s\n" %
373                              (key, str(value).replace('\n', '\n\t')))
374             fp.write("\n")
375
376     def remove_option(self, section, option):
377         """Remove an option."""
378         if not section or section == DEFAULTSECT:
379             sectdict = self._defaults
380         else:
381             try:
382                 sectdict = self._sections[section]
383             except KeyError:
384                 raise NoSectionError(section)
385         option = self.optionxform(option)
386         existed = option in sectdict
387         if existed:
388             del sectdict[option]
389         return existed
390
391     def remove_section(self, section):
392         """Remove a file section."""
393         existed = section in self._sections
394         if existed:
395             del self._sections[section]
396         return existed
397
398     #
399     # Regular expressions for parsing section headers and options.
400     #
401     SECTCRE = re.compile(
402         r'\['                                 # [
403         r'(?P<header>[^]]+)'                  # very permissive!
404         r'\]'                                 # ]
405         )
406     OPTCRE = re.compile(
407         r'(?P<option>[^:=\s][^:=]*)'          # very permissive!
408         r'\s*(?P<vi>[:=])\s*'                 # any number of space/tab,
409                                               # followed by separator
410                                               # (either : or =), followed
411                                               # by any # space/tab
412         r'(?P<value>.*)$'                     # everything up to eol
413         )
414
415     def _read(self, fp, fpname):
416         """Parse a sectioned setup file.
417
418         The sections in setup file contains a title line at the top,
419         indicated by a name in square brackets (`[]'), plus key/value
420         options lines, indicated by `name: value' format lines.
421         Continuations are represented by an embedded newline then
422         leading whitespace.  Blank lines, lines beginning with a '#',
423         and just about everything else are ignored.
424         """
425         cursect = None                            # None, or a dictionary
426         optname = None
427         lineno = 0
428         e = None                                  # None, or an exception
429         while True:
430             line = fp.readline()
431             if not line:
432                 break
433             lineno = lineno + 1
434             # comment or blank line?
435             if line.strip() == '' or line[0] in '#;':
436                 continue
437             if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
438                 # no leading whitespace
439                 continue
440             # continuation line?
441             if line[0].isspace() and cursect is not None and optname:
442                 value = line.strip()
443                 if value:
444                     cursect[optname] = "%s\n%s" % (cursect[optname], value)
445             # a section header or option header?
446             else:
447                 # is it a section header?
448                 mo = self.SECTCRE.match(line)
449                 if mo:
450                     sectname = mo.group('header')
451                     if sectname in self._sections:
452                         cursect = self._sections[sectname]
453                     elif sectname == DEFAULTSECT:
454                         cursect = self._defaults
455                     else:
456                         cursect = {'__name__': sectname}
457                         self._sections[sectname] = cursect
458                     # So sections can't start with a continuation line
459                     optname = None
460                 # no section header in the file?
461                 elif cursect is None:
462                     raise MissingSectionHeaderError(fpname, lineno, line)
463                 # an option line?
464                 else:
465                     mo = self.OPTCRE.match(line)
466                     if mo:
467                         optname, vi, optval = mo.group('option', 'vi', 'value')
468                         if vi in ('=', ':') and ';' in optval:
469                             # ';' is a comment delimiter only if it follows
470                             # a spacing character
471                             pos = optval.find(';')
472                             if pos != -1 and optval[pos-1].isspace():
473                                 optval = optval[:pos]
474                         optval = optval.strip()
475                         # allow empty values
476                         if optval == '""':
477                             optval = ''
478                         optname = self.optionxform(optname.rstrip())
479                         cursect[optname] = optval
480                     else:
481                         # a non-fatal parsing error occurred.  set up the
482                         # exception but keep going. the exception will be
483                         # raised at the end of the file and will contain a
484                         # list of all bogus lines
485                         if not e:
486                             e = ParsingError(fpname)
487                         e.append(lineno, repr(line))
488         # if any parsing errors occurred, raise an exception
489         if e:
490             raise e
491
492
493 class ConfigParser(RawConfigParser):
494
495     def get(self, section, option, raw=False, vars=None):
496         """Get an option value for a given section.
497
498         All % interpolations are expanded in the return values, based on the
499         defaults passed into the constructor, unless the optional argument
500         `raw' is true.  Additional substitutions may be provided using the
501         `vars' argument, which must be a dictionary whose contents overrides
502         any pre-existing defaults.
503
504         The section DEFAULT is special.
505         """
506         d = self._defaults.copy()
507         try:
508             d.update(self._sections[section])
509         except KeyError:
510             if section != DEFAULTSECT:
511                 raise NoSectionError(section)
512         # Update with the entry specific variables
513         if vars:
514             for key, value in vars.items():
515                 d[self.optionxform(key)] = value
516         option = self.optionxform(option)
517         try:
518             value = d[option]
519         except KeyError:
520             raise NoOptionError(option, section)
521
522         if raw:
523             return value
524         else:
525             return self._interpolate(section, option, value, d)
526
527     def items(self, section, raw=False, vars=None):
528         """Return a list of tuples with (name, value) for each option
529         in the section.
530
531         All % interpolations are expanded in the return values, based on the
532         defaults passed into the constructor, unless the optional argument
533         `raw' is true.  Additional substitutions may be provided using the
534         `vars' argument, which must be a dictionary whose contents overrides
535         any pre-existing defaults.
536
537         The section DEFAULT is special.
538         """
539         d = self._defaults.copy()
540         try:
541             d.update(self._sections[section])
542         except KeyError:
543             if section != DEFAULTSECT:
544                 raise NoSectionError(section)
545         # Update with the entry specific variables
546         if vars:
547             for key, value in vars.items():
548                 d[self.optionxform(key)] = value
549         options = d.keys()
550         if "__name__" in options:
551             options.remove("__name__")
552         if raw:
553             return [(option, d[option])
554                     for option in options]
555         else:
556             return [(option, self._interpolate(section, option, d[option], d))
557                     for option in options]
558
559     def _interpolate(self, section, option, rawval, vars):
560         # do the string interpolation
561         value = rawval
562         depth = MAX_INTERPOLATION_DEPTH
563         while depth:                    # Loop through this until it's done
564             depth -= 1
565             if "%(" in value:
566                 value = self._KEYCRE.sub(self._interpolation_replace, value)
567                 try:
568                     value = value % vars
569                 except KeyError, e:
570                     raise InterpolationMissingOptionError(
571                         option, section, rawval, e[0])
572             else:
573                 break
574         if "%(" in value:
575             raise InterpolationDepthError(option, section, rawval)
576         return value
577
578     _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
579
580     def _interpolation_replace(self, match):
581         s = match.group(1)
582         if s is None:
583             return match.group()
584         else:
585             return "%%(%s)s" % self.optionxform(s)
586
587
588 class SafeConfigParser(ConfigParser):
589
590     def _interpolate(self, section, option, rawval, vars):
591         # do the string interpolation
592         L = []
593         self._interpolate_some(option, L, rawval, section, vars, 1)
594         return ''.join(L)
595
596     _interpvar_match = re.compile(r"%\(([^)]+)\)s").match
597
598     def _interpolate_some(self, option, accum, rest, section, map, depth):
599         if depth > MAX_INTERPOLATION_DEPTH:
600             raise InterpolationDepthError(option, section, rest)
601         while rest:
602             p = rest.find("%")
603             if p < 0:
604                 accum.append(rest)
605                 return
606             if p > 0:
607                 accum.append(rest[:p])
608                 rest = rest[p:]
609             # p is no longer used
610             c = rest[1:2]
611             if c == "%":
612                 accum.append("%")
613                 rest = rest[2:]
614             elif c == "(":
615                 m = self._interpvar_match(rest)
616                 if m is None:
617                     raise InterpolationSyntaxError(option, section,
618                         "bad interpolation variable reference %r" % rest)
619                 var = self.optionxform(m.group(1))
620                 rest = rest[m.end():]
621                 try:
622                     v = map[var]
623                 except KeyError:
624                     raise InterpolationMissingOptionError(
625                         option, section, rest, var)
626                 if "%" in v:
627                     self._interpolate_some(option, accum, v,
628                                            section, map, depth + 1)
629                 else:
630                     accum.append(v)
631             else:
632                 raise InterpolationSyntaxError(
633                     option, section,
634                     "'%%' must be followed by '%%' or '(', found: %r" % (rest,))
635
636     def set(self, section, option, value):
637         """Set an option.  Extend ConfigParser.set: check for string values."""
638         if not isinstance(value, basestring):
639             raise TypeError("option values must be strings")
640         ConfigParser.set(self, section, option, value)