iniparse-0.4/0000775000076400007640000000000011404735056012170 5ustar paramparaminiparse-0.4/iniparse/0000775000076400007640000000000011404735056014002 5ustar paramparaminiparse-0.4/iniparse/config.py0000664000076400007640000002016311371340677015627 0ustar paramparamclass ConfigNamespace(object): """Abstract class representing the interface of Config objects. A ConfigNamespace is a collection of names mapped to values, where the values may be nested namespaces. Values can be accessed via container notation - obj[key] - or via dotted notation - obj.key. Both these access methods are equivalent. To minimize name conflicts between namespace keys and class members, the number of class members should be minimized, and the names of all class members should start with an underscore. Subclasses must implement the methods for container-like access, and this class will automatically provide dotted access. """ # Methods that must be implemented by subclasses def _getitem(self, key): return NotImplementedError(key) def __setitem__(self, key, value): raise NotImplementedError(key, value) def __delitem__(self, key): raise NotImplementedError(key) def __iter__(self): return NotImplementedError() def _new_namespace(self, name): raise NotImplementedError(name) def __contains__(self, key): try: self._getitem(key) except KeyError: return False return True # Machinery for converting dotted access into container access, # and automatically creating new sections/namespaces. # # To distinguish between accesses of class members and namespace # keys, we first call object.__getattribute__(). If that succeeds, # the name is assumed to be a class member. Otherwise it is # treated as a namespace key. # # Therefore, member variables should be defined in the class, # not just in the __init__() function. See BasicNamespace for # an example. def __getitem__(self, key): try: return self._getitem(key) except KeyError: return Undefined(key, self) def __getattr__(self, name): try: return self._getitem(name) except KeyError: if name.startswith('__') and name.endswith('__'): raise AttributeError return Undefined(name, self) def __setattr__(self, name, value): try: object.__getattribute__(self, name) object.__setattr__(self, name, value) except AttributeError: self.__setitem__(name, value) def __delattr__(self, name): try: object.__getattribute__(self, name) object.__delattr__(self, name) except AttributeError: self.__delitem__(name) # During unpickling, Python checks if the class has a __setstate__ # method. But, the data dicts have not been initialised yet, which # leads to _getitem and hence __getattr__ raising an exception. So # we explicitly impement default __setstate__ behavior. def __setstate__(self, state): self.__dict__.update(state) class Undefined(object): """Helper class used to hold undefined names until assignment. This class helps create any undefined subsections when an assignment is made to a nested value. For example, if the statement is "cfg.a.b.c = 42", but "cfg.a.b" does not exist yet. """ def __init__(self, name, namespace): object.__setattr__(self, 'name', name) object.__setattr__(self, 'namespace', namespace) def __setattr__(self, name, value): obj = self.namespace._new_namespace(self.name) obj[name] = value def __setitem__(self, name, value): obj = self.namespace._new_namespace(self.name) obj[name] = value # ---- Basic implementation of a ConfigNamespace class BasicConfig(ConfigNamespace): """Represents a hierarchical collection of named values. Values are added using dotted notation: >>> n = BasicConfig() >>> n.x = 7 >>> n.name.first = 'paramjit' >>> n.name.last = 'oberoi' ...and accessed the same way, or with [...]: >>> n.x 7 >>> n.name.first 'paramjit' >>> n.name.last 'oberoi' >>> n['x'] 7 >>> n['name']['first'] 'paramjit' Iterating over the namespace object returns the keys: >>> l = list(n) >>> l.sort() >>> l ['name', 'x'] Values can be deleted using 'del' and printed using 'print'. >>> n.aaa = 42 >>> del n.x >>> print n aaa = 42 name.first = paramjit name.last = oberoi Nested namepsaces are also namespaces: >>> isinstance(n.name, ConfigNamespace) True >>> print n.name first = paramjit last = oberoi >>> sorted(list(n.name)) ['first', 'last'] Finally, values can be read from a file as follows: >>> from StringIO import StringIO >>> sio = StringIO(''' ... # comment ... ui.height = 100 ... ui.width = 150 ... complexity = medium ... have_python ... data.secret.password = goodness=gracious me ... ''') >>> n = BasicConfig() >>> n._readfp(sio) >>> print n complexity = medium data.secret.password = goodness=gracious me have_python ui.height = 100 ui.width = 150 """ # this makes sure that __setattr__ knows this is not a namespace key _data = None def __init__(self): self._data = {} def _getitem(self, key): return self._data[key] def __setitem__(self, key, value): self._data[key] = value def __delitem__(self, key): del self._data[key] def __iter__(self): return iter(self._data) def __str__(self, prefix=''): lines = [] keys = self._data.keys() keys.sort() for name in keys: value = self._data[name] if isinstance(value, ConfigNamespace): lines.append(value.__str__(prefix='%s%s.' % (prefix,name))) else: if value is None: lines.append('%s%s' % (prefix, name)) else: lines.append('%s%s = %s' % (prefix, name, value)) return '\n'.join(lines) def _new_namespace(self, name): obj = BasicConfig() self._data[name] = obj return obj def _readfp(self, fp): while True: line = fp.readline() if not line: break line = line.strip() if not line: continue if line[0] == '#': continue data = line.split('=', 1) if len(data) == 1: name = line value = None else: name = data[0].strip() value = data[1].strip() name_components = name.split('.') ns = self for n in name_components[:-1]: if n in ns: ns = ns[n] if not isinstance(ns, ConfigNamespace): raise TypeError('value-namespace conflict', n) else: ns = ns._new_namespace(n) ns[name_components[-1]] = value # ---- Utility functions def update_config(target, source): """Imports values from source into target. Recursively walks the ConfigNamespace and inserts values into the ConfigNamespace. For example: >>> n = BasicConfig() >>> n.playlist.expand_playlist = True >>> n.ui.display_clock = True >>> n.ui.display_qlength = True >>> n.ui.width = 150 >>> print n playlist.expand_playlist = True ui.display_clock = True ui.display_qlength = True ui.width = 150 >>> from iniparse import ini >>> i = ini.INIConfig() >>> update_config(i, n) >>> print i [playlist] expand_playlist = True [ui] display_clock = True display_qlength = True width = 150 """ for name in source: value = source[name] if isinstance(value, ConfigNamespace): if name in target: myns = target[name] if not isinstance(myns, ConfigNamespace): raise TypeError('value-namespace conflict') else: myns = target._new_namespace(name) update_config(myns, value) else: target[name] = value iniparse-0.4/iniparse/compat.py0000664000076400007640000002750611371132636015647 0ustar paramparam# Copyright (c) 2001, 2002, 2003 Python Software Foundation # Copyright (c) 2004-2008 Paramjit Oberoi # All Rights Reserved. See LICENSE-PSF & LICENSE for details. """Compatibility interfaces for ConfigParser Interfaces of ConfigParser, RawConfigParser and SafeConfigParser should be completely identical to the Python standard library versions. Tested with the unit tests included with Python-2.3.4 The underlying INIConfig object can be accessed as cfg.data """ import re from ConfigParser import DuplicateSectionError, \ NoSectionError, NoOptionError, \ InterpolationMissingOptionError, \ InterpolationDepthError, \ InterpolationSyntaxError, \ DEFAULTSECT, MAX_INTERPOLATION_DEPTH # These are imported only for compatiability. # The code below does not reference them directly. from ConfigParser import Error, InterpolationError, \ MissingSectionHeaderError, ParsingError import ini class RawConfigParser(object): def __init__(self, defaults=None, dict_type=dict): if dict_type != dict: raise ValueError('Custom dict types not supported') self.data = ini.INIConfig(defaults=defaults, optionxformsource=self) def optionxform(self, optionstr): return optionstr.lower() def defaults(self): d = {} secobj = self.data._defaults for name in secobj._options: d[name] = secobj._compat_get(name) return d def sections(self): """Return a list of section names, excluding [DEFAULT]""" return list(self.data) def add_section(self, section): """Create a new section in the configuration. Raise DuplicateSectionError if a section by the specified name already exists. Raise ValueError if name is DEFAULT or any of its case-insensitive variants. """ # The default section is the only one that gets the case-insensitive # treatment - so it is special-cased here. if section.lower() == "default": raise ValueError, 'Invalid section name: %s' % section if self.has_section(section): raise DuplicateSectionError(section) else: self.data._new_namespace(section) def has_section(self, section): """Indicate whether the named section is present in the configuration. The DEFAULT section is not acknowledged. """ return (section in self.data) def options(self, section): """Return a list of option names for the given section name.""" if section in self.data: return list(self.data[section]) else: raise NoSectionError(section) def read(self, filenames): """Read and parse a filename or a list of filenames. Files that cannot be opened are silently ignored; this is designed so that you can specify a list of potential configuration file locations (e.g. current directory, user's home directory, systemwide directory), and all existing configuration files in the list will be read. A single filename may also be given. """ files_read = [] if isinstance(filenames, basestring): filenames = [filenames] for filename in filenames: try: fp = open(filename) except IOError: continue files_read.append(filename) self.data._readfp(fp) fp.close() return files_read def readfp(self, fp, filename=None): """Like read() but the argument must be a file-like object. The `fp' argument must have a `readline' method. Optional second argument is the `filename', which if not given, is taken from fp.name. If fp has no `name' attribute, `' is used. """ self.data._readfp(fp) def get(self, section, option, vars=None): if not self.has_section(section): raise NoSectionError(section) if vars is not None and option in vars: value = vars[option] sec = self.data[section] if option in sec: return sec._compat_get(option) else: raise NoOptionError(option, section) def items(self, section): if section in self.data: ans = [] for opt in self.data[section]: ans.append((opt, self.get(section, opt))) return ans else: raise NoSectionError(section) def getint(self, section, option): return int(self.get(section, option)) def getfloat(self, section, option): return float(self.get(section, option)) _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False} def getboolean(self, section, option): v = self.get(section, option) if v.lower() not in self._boolean_states: raise ValueError, 'Not a boolean: %s' % v return self._boolean_states[v.lower()] def has_option(self, section, option): """Check for the existence of a given option in a given section.""" if section in self.data: sec = self.data[section] else: raise NoSectionError(section) return (option in sec) def set(self, section, option, value): """Set an option.""" if section in self.data: self.data[section][option] = value else: raise NoSectionError(section) def write(self, fp): """Write an .ini-format representation of the configuration state.""" fp.write(str(self.data)) def remove_option(self, section, option): """Remove an option.""" if section in self.data: sec = self.data[section] else: raise NoSectionError(section) if option in sec: del sec[option] return 1 else: return 0 def remove_section(self, section): """Remove a file section.""" if not self.has_section(section): return False del self.data[section] return True class ConfigDict(object): """Present a dict interface to a ini section.""" def __init__(self, cfg, section, vars): self.cfg = cfg self.section = section self.vars = vars def __getitem__(self, key): try: return RawConfigParser.get(self.cfg, self.section, key, self.vars) except (NoOptionError, NoSectionError): raise KeyError(key) class ConfigParser(RawConfigParser): def get(self, section, option, raw=False, vars=None): """Get an option value for a given section. All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument `raw' is true. Additional substitutions may be provided using the `vars' argument, which must be a dictionary whose contents overrides any pre-existing defaults. The section DEFAULT is special. """ if section != DEFAULTSECT and not self.has_section(section): raise NoSectionError(section) option = self.optionxform(option) value = RawConfigParser.get(self, section, option, vars) if raw: return value else: d = ConfigDict(self, section, vars) return self._interpolate(section, option, value, d) def _interpolate(self, section, option, rawval, vars): # do the string interpolation value = rawval depth = MAX_INTERPOLATION_DEPTH while depth: # Loop through this until it's done depth -= 1 if "%(" in value: try: value = value % vars except KeyError, e: raise InterpolationMissingOptionError( option, section, rawval, e.args[0]) else: break if value.find("%(") != -1: raise InterpolationDepthError(option, section, rawval) return value def items(self, section, raw=False, vars=None): """Return a list of tuples with (name, value) for each option in the section. All % interpolations are expanded in the return values, based on the defaults passed into the constructor, unless the optional argument `raw' is true. Additional substitutions may be provided using the `vars' argument, which must be a dictionary whose contents overrides any pre-existing defaults. The section DEFAULT is special. """ if section != DEFAULTSECT and not self.has_section(section): raise NoSectionError(section) if vars is None: options = list(self.data[section]) else: options = [] for x in self.data[section]: if x not in vars: options.append(x) options.extend(vars.keys()) if "__name__" in options: options.remove("__name__") d = ConfigDict(self, section, vars) if raw: return [(option, d[option]) for option in options] else: return [(option, self._interpolate(section, option, d[option], d)) for option in options] class SafeConfigParser(ConfigParser): _interpvar_re = re.compile(r"%\(([^)]+)\)s") _badpercent_re = re.compile(r"%[^%]|%$") def set(self, section, option, value): if not isinstance(value, basestring): raise TypeError("option values must be strings") # check for bad percent signs: # first, replace all "good" interpolations tmp_value = self._interpvar_re.sub('', value) # then, check if there's a lone percent sign left m = self._badpercent_re.search(tmp_value) if m: raise ValueError("invalid interpolation syntax in %r at " "position %d" % (value, m.start())) ConfigParser.set(self, section, option, value) def _interpolate(self, section, option, rawval, vars): # do the string interpolation L = [] self._interpolate_some(option, L, rawval, section, vars, 1) return ''.join(L) _interpvar_match = re.compile(r"%\(([^)]+)\)s").match def _interpolate_some(self, option, accum, rest, section, map, depth): if depth > MAX_INTERPOLATION_DEPTH: raise InterpolationDepthError(option, section, rest) while rest: p = rest.find("%") if p < 0: accum.append(rest) return if p > 0: accum.append(rest[:p]) rest = rest[p:] # p is no longer used c = rest[1:2] if c == "%": accum.append("%") rest = rest[2:] elif c == "(": m = self._interpvar_match(rest) if m is None: raise InterpolationSyntaxError(option, section, "bad interpolation variable reference %r" % rest) var = m.group(1) rest = rest[m.end():] try: v = map[var] except KeyError: raise InterpolationMissingOptionError( option, section, rest, var) if "%" in v: self._interpolate_some(option, accum, v, section, map, depth + 1) else: accum.append(v) else: raise InterpolationSyntaxError( option, section, "'%' must be followed by '%' or '(', found: " + repr(rest)) iniparse-0.4/iniparse/utils.py0000664000076400007640000000236211371336512015514 0ustar paramparamimport compat from ini import LineContainer, EmptyLine def tidy(cfg): """Clean up blank lines. This functions makes the configuration look clean and handwritten - consecutive empty lines and empty lines at the start of the file are removed, and one is guaranteed to be at the end of the file. """ if isinstance(cfg, compat.RawConfigParser): cfg = cfg.data cont = cfg._data.contents i = 1 while i < len(cont): if isinstance(cont[i], LineContainer): tidy_section(cont[i]) i += 1 elif (isinstance(cont[i-1], EmptyLine) and isinstance(cont[i], EmptyLine)): del cont[i] else: i += 1 # Remove empty first line if cont and isinstance(cont[0], EmptyLine): del cont[0] # Ensure a last line if cont and not isinstance(cont[-1], EmptyLine): cont.append(EmptyLine()) def tidy_section(lc): cont = lc.contents i = 1 while i < len(cont): if (isinstance(cont[i-1], EmptyLine) and isinstance(cont[i], EmptyLine)): del cont[i] else: i += 1 # Remove empty first line if len(cont) > 1 and isinstance(cont[1], EmptyLine): del cont[1] iniparse-0.4/iniparse/ini.py0000664000076400007640000005025611371336512015140 0ustar paramparam"""Access and/or modify INI files * Compatiable with ConfigParser * Preserves order of sections & options * Preserves comments/blank lines/etc * More conveninet access to data Example: >>> from StringIO import StringIO >>> sio = StringIO('''# configure foo-application ... [foo] ... bar1 = qualia ... bar2 = 1977 ... [foo-ext] ... special = 1''') >>> cfg = INIConfig(sio) >>> print cfg.foo.bar1 qualia >>> print cfg['foo-ext'].special 1 >>> cfg.foo.newopt = 'hi!' >>> cfg.baz.enabled = 0 >>> print cfg # configure foo-application [foo] bar1 = qualia bar2 = 1977 newopt = hi! [foo-ext] special = 1 [baz] enabled = 0 """ # An ini parser that supports ordered sections/options # Also supports updates, while preserving structure # Backward-compatiable with ConfigParser import re from ConfigParser import DEFAULTSECT, ParsingError, MissingSectionHeaderError import config class LineType(object): line = None def __init__(self, line=None): if line is not None: self.line = line.strip('\n') # Return the original line for unmodified objects # Otherwise construct using the current attribute values def __str__(self): if self.line is not None: return self.line else: return self.to_string() # If an attribute is modified after initialization # set line to None since it is no longer accurate. def __setattr__(self, name, value): if hasattr(self,name): self.__dict__['line'] = None self.__dict__[name] = value def to_string(self): raise Exception('This method must be overridden in derived classes') class SectionLine(LineType): regex = re.compile(r'^\[' r'(?P[^]]+)' r'\]\s*' r'((?P;|#)(?P.*))?$') def __init__(self, name, comment=None, comment_separator=None, comment_offset=-1, line=None): super(SectionLine, self).__init__(line) self.name = name self.comment = comment self.comment_separator = comment_separator self.comment_offset = comment_offset def to_string(self): out = '[' + self.name + ']' if self.comment is not None: # try to preserve indentation of comments out = (out+' ').ljust(self.comment_offset) out = out + self.comment_separator + self.comment return out def parse(cls, line): m = cls.regex.match(line.rstrip()) if m is None: return None return cls(m.group('name'), m.group('comment'), m.group('csep'), m.start('csep'), line) parse = classmethod(parse) class OptionLine(LineType): def __init__(self, name, value, separator=' = ', comment=None, comment_separator=None, comment_offset=-1, line=None): super(OptionLine, self).__init__(line) self.name = name self.value = value self.separator = separator self.comment = comment self.comment_separator = comment_separator self.comment_offset = comment_offset def to_string(self): out = '%s%s%s' % (self.name, self.separator, self.value) if self.comment is not None: # try to preserve indentation of comments out = (out+' ').ljust(self.comment_offset) out = out + self.comment_separator + self.comment return out regex = re.compile(r'^(?P[^:=\s[][^:=]*)' r'(?P[:=]\s*)' r'(?P.*)$') def parse(cls, line): m = cls.regex.match(line.rstrip()) if m is None: return None name = m.group('name').rstrip() value = m.group('value') sep = m.group('name')[len(name):] + m.group('sep') # comments are not detected in the regex because # ensuring total compatibility with ConfigParser # requires that: # option = value ;comment // value=='value' # option = value;1 ;comment // value=='value;1 ;comment' # # Doing this in a regex would be complicated. I # think this is a bug. The whole issue of how to # include ';' in the value needs to be addressed. # Also, '#' doesn't mark comments in options... coff = value.find(';') if coff != -1 and value[coff-1].isspace(): comment = value[coff+1:] csep = value[coff] value = value[:coff].rstrip() coff = m.start('value') + coff else: comment = None csep = None coff = -1 return cls(name, value, sep, comment, csep, coff, line) parse = classmethod(parse) def change_comment_syntax(comment_chars='%;#', allow_rem=False): comment_chars = re.sub(r'([\]\-\^])', r'\\\1', comment_chars) regex = r'^(?P[%s]' % comment_chars if allow_rem: regex += '|[rR][eE][mM]' regex += r')(?P.*)$' CommentLine.regex = re.compile(regex) class CommentLine(LineType): regex = re.compile(r'^(?P[;#]|[rR][eE][mM])' r'(?P.*)$') def __init__(self, comment='', separator='#', line=None): super(CommentLine, self).__init__(line) self.comment = comment self.separator = separator def to_string(self): return self.separator + self.comment def parse(cls, line): m = cls.regex.match(line.rstrip()) if m is None: return None return cls(m.group('comment'), m.group('csep'), line) parse = classmethod(parse) class EmptyLine(LineType): # could make this a singleton def to_string(self): return '' value = property(lambda _: '') def parse(cls, line): if line.strip(): return None return cls(line) parse = classmethod(parse) class ContinuationLine(LineType): regex = re.compile(r'^\s+(?P.*)$') def __init__(self, value, value_offset=None, line=None): super(ContinuationLine, self).__init__(line) self.value = value if value_offset is None: value_offset = 8 self.value_offset = value_offset def to_string(self): return ' '*self.value_offset + self.value def parse(cls, line): m = cls.regex.match(line.rstrip()) if m is None: return None return cls(m.group('value'), m.start('value'), line) parse = classmethod(parse) class LineContainer(object): def __init__(self, d=None): self.contents = [] self.orgvalue = None if d: if isinstance(d, list): self.extend(d) else: self.add(d) def add(self, x): self.contents.append(x) def extend(self, x): for i in x: self.add(i) def get_name(self): return self.contents[0].name def set_name(self, data): self.contents[0].name = data def get_value(self): if self.orgvalue is not None: return self.orgvalue elif len(self.contents) == 1: return self.contents[0].value else: return '\n'.join([('%s' % x.value) for x in self.contents if not isinstance(x, CommentLine)]) def set_value(self, data): self.orgvalue = data lines = ('%s' % data).split('\n') # If there is an existing ContinuationLine, use its offset value_offset = None for v in self.contents: if isinstance(v, ContinuationLine): value_offset = v.value_offset break # Rebuild contents list, preserving initial OptionLine self.contents = self.contents[0:1] self.contents[0].value = lines[0] del lines[0] for line in lines: if line.strip(): self.add(ContinuationLine(line, value_offset)) else: self.add(EmptyLine()) name = property(get_name, set_name) value = property(get_value, set_value) def __str__(self): s = [x.__str__() for x in self.contents] return '\n'.join(s) def finditer(self, key): for x in self.contents[::-1]: if hasattr(x, 'name') and x.name==key: yield x def find(self, key): for x in self.finditer(key): return x raise KeyError(key) def _make_xform_property(myattrname, srcattrname=None): private_attrname = myattrname + 'value' private_srcname = myattrname + 'source' if srcattrname is None: srcattrname = myattrname def getfn(self): srcobj = getattr(self, private_srcname) if srcobj is not None: return getattr(srcobj, srcattrname) else: return getattr(self, private_attrname) def setfn(self, value): srcobj = getattr(self, private_srcname) if srcobj is not None: setattr(srcobj, srcattrname, value) else: setattr(self, private_attrname, value) return property(getfn, setfn) class INISection(config.ConfigNamespace): _lines = None _options = None _defaults = None _optionxformvalue = None _optionxformsource = None _compat_skip_empty_lines = set() def __init__(self, lineobj, defaults = None, optionxformvalue=None, optionxformsource=None): self._lines = [lineobj] self._defaults = defaults self._optionxformvalue = optionxformvalue self._optionxformsource = optionxformsource self._options = {} _optionxform = _make_xform_property('_optionxform') def _compat_get(self, key): # identical to __getitem__ except that _compat_XXX # is checked for backward-compatible handling if key == '__name__': return self._lines[-1].name if self._optionxform: key = self._optionxform(key) try: value = self._options[key].value del_empty = key in self._compat_skip_empty_lines except KeyError: if self._defaults and key in self._defaults._options: value = self._defaults._options[key].value del_empty = key in self._defaults._compat_skip_empty_lines else: raise if del_empty: value = re.sub('\n+', '\n', value) return value def _getitem(self, key): if key == '__name__': return self._lines[-1].name if self._optionxform: key = self._optionxform(key) try: return self._options[key].value except KeyError: if self._defaults and key in self._defaults._options: return self._defaults._options[key].value else: raise def __setitem__(self, key, value): if self._optionxform: xkey = self._optionxform(key) else: xkey = key if xkey in self._compat_skip_empty_lines: self._compat_skip_empty_lines.remove(xkey) if xkey not in self._options: # create a dummy object - value may have multiple lines obj = LineContainer(OptionLine(key, '')) self._lines[-1].add(obj) self._options[xkey] = obj # the set_value() function in LineContainer # automatically handles multi-line values self._options[xkey].value = value def __delitem__(self, key): if self._optionxform: key = self._optionxform(key) if key in self._compat_skip_empty_lines: self._compat_skip_empty_lines.remove(key) for l in self._lines: remaining = [] for o in l.contents: if isinstance(o, LineContainer): n = o.name if self._optionxform: n = self._optionxform(n) if key != n: remaining.append(o) else: remaining.append(o) l.contents = remaining del self._options[key] def __iter__(self): d = set() for l in self._lines: for x in l.contents: if isinstance(x, LineContainer): if self._optionxform: ans = self._optionxform(x.name) else: ans = x.name if ans not in d: yield ans d.add(ans) if self._defaults: for x in self._defaults: if x not in d: yield x d.add(x) def _new_namespace(self, name): raise Exception('No sub-sections allowed', name) def make_comment(line): return CommentLine(line.rstrip('\n')) def readline_iterator(f): """iterate over a file by only using the file object's readline method""" have_newline = False while True: line = f.readline() if not line: if have_newline: yield "" return if line.endswith('\n'): have_newline = True else: have_newline = False yield line def lower(x): return x.lower() class INIConfig(config.ConfigNamespace): _data = None _sections = None _defaults = None _optionxformvalue = None _optionxformsource = None _sectionxformvalue = None _sectionxformsource = None _parse_exc = None _bom = False def __init__(self, fp=None, defaults=None, parse_exc=True, optionxformvalue=lower, optionxformsource=None, sectionxformvalue=None, sectionxformsource=None): self._data = LineContainer() self._parse_exc = parse_exc self._optionxformvalue = optionxformvalue self._optionxformsource = optionxformsource self._sectionxformvalue = sectionxformvalue self._sectionxformsource = sectionxformsource self._sections = {} if defaults is None: defaults = {} self._defaults = INISection(LineContainer(), optionxformsource=self) for name, value in defaults.iteritems(): self._defaults[name] = value if fp is not None: self._readfp(fp) _optionxform = _make_xform_property('_optionxform', 'optionxform') _sectionxform = _make_xform_property('_sectionxform', 'optionxform') def _getitem(self, key): if key == DEFAULTSECT: return self._defaults if self._sectionxform: key = self._sectionxform(key) return self._sections[key] def __setitem__(self, key, value): raise Exception('Values must be inside sections', key, value) def __delitem__(self, key): if self._sectionxform: key = self._sectionxform(key) for line in self._sections[key]._lines: self._data.contents.remove(line) del self._sections[key] def __iter__(self): d = set() d.add(DEFAULTSECT) for x in self._data.contents: if isinstance(x, LineContainer): if x.name not in d: yield x.name d.add(x.name) def _new_namespace(self, name): if self._data.contents: self._data.add(EmptyLine()) obj = LineContainer(SectionLine(name)) self._data.add(obj) if self._sectionxform: name = self._sectionxform(name) if name in self._sections: ns = self._sections[name] ns._lines.append(obj) else: ns = INISection(obj, defaults=self._defaults, optionxformsource=self) self._sections[name] = ns return ns def __str__(self): if self._bom: fmt = u'\ufeff%s' else: fmt = '%s' return fmt % self._data.__str__() __unicode__ = __str__ _line_types = [EmptyLine, CommentLine, SectionLine, OptionLine, ContinuationLine] def _parse(self, line): for linetype in self._line_types: lineobj = linetype.parse(line) if lineobj: return lineobj else: # can't parse line return None def _readfp(self, fp): cur_section = None cur_option = None cur_section_name = None cur_option_name = None pending_lines = [] pending_empty_lines = False try: fname = fp.name except AttributeError: fname = '' linecount = 0 exc = None line = None for line in readline_iterator(fp): # Check for BOM on first line if linecount == 0 and isinstance(line, unicode): if line[0] == u'\ufeff': line = line[1:] self._bom = True lineobj = self._parse(line) linecount += 1 if not cur_section and not isinstance(lineobj, (CommentLine, EmptyLine, SectionLine)): if self._parse_exc: raise MissingSectionHeaderError(fname, linecount, line) else: lineobj = make_comment(line) if lineobj is None: if self._parse_exc: if exc is None: exc = ParsingError(fname) exc.append(linecount, line) lineobj = make_comment(line) if isinstance(lineobj, ContinuationLine): if cur_option: if pending_lines: cur_option.extend(pending_lines) pending_lines = [] if pending_empty_lines: optobj._compat_skip_empty_lines.add(cur_option_name) pending_empty_lines = False cur_option.add(lineobj) else: # illegal continuation line - convert to comment if self._parse_exc: if exc is None: exc = ParsingError(fname) exc.append(linecount, line) lineobj = make_comment(line) if isinstance(lineobj, OptionLine): if pending_lines: cur_section.extend(pending_lines) pending_lines = [] pending_empty_lines = False cur_option = LineContainer(lineobj) cur_section.add(cur_option) if self._optionxform: cur_option_name = self._optionxform(cur_option.name) else: cur_option_name = cur_option.name if cur_section_name == DEFAULTSECT: optobj = self._defaults else: optobj = self._sections[cur_section_name] optobj._options[cur_option_name] = cur_option if isinstance(lineobj, SectionLine): self._data.extend(pending_lines) pending_lines = [] pending_empty_lines = False cur_section = LineContainer(lineobj) self._data.add(cur_section) cur_option = None cur_option_name = None if cur_section.name == DEFAULTSECT: self._defaults._lines.append(cur_section) cur_section_name = DEFAULTSECT else: if self._sectionxform: cur_section_name = self._sectionxform(cur_section.name) else: cur_section_name = cur_section.name if cur_section_name not in self._sections: self._sections[cur_section_name] = \ INISection(cur_section, defaults=self._defaults, optionxformsource=self) else: self._sections[cur_section_name]._lines.append(cur_section) if isinstance(lineobj, (CommentLine, EmptyLine)): pending_lines.append(lineobj) if isinstance(lineobj, EmptyLine): pending_empty_lines = True self._data.extend(pending_lines) if line and line[-1]=='\n': self._data.add(EmptyLine()) if exc: raise exc iniparse-0.4/iniparse/__init__.py0000664000076400007640000000212711371336512016112 0ustar paramparam# Copyright (c) 2001, 2002, 2003 Python Software Foundation # Copyright (c) 2004-2008 Paramjit Oberoi # Copyright (c) 2007 Tim Lauridsen # All Rights Reserved. See LICENSE-PSF & LICENSE for details. from ini import INIConfig, change_comment_syntax from config import BasicConfig, ConfigNamespace from compat import RawConfigParser, ConfigParser, SafeConfigParser from utils import tidy from ConfigParser import DuplicateSectionError, \ NoSectionError, NoOptionError, \ InterpolationMissingOptionError, \ InterpolationDepthError, \ InterpolationSyntaxError, \ DEFAULTSECT, MAX_INTERPOLATION_DEPTH __all__ = [ 'BasicConfig', 'ConfigNamespace', 'INIConfig', 'tidy', 'change_comment_syntax', 'RawConfigParser', 'ConfigParser', 'SafeConfigParser', 'DuplicateSectionError', 'NoSectionError', 'NoOptionError', 'InterpolationMissingOptionError', 'InterpolationDepthError', 'InterpolationSyntaxError', 'DEFAULTSECT', 'MAX_INTERPOLATION_DEPTH', ] iniparse-0.4/MANIFEST.in0000664000076400007640000000032010656247226013726 0ustar paramparaminclude iniparse/*.py include tests/*.py include html/*.html include html/style.css include Makefile runtests.py include MANIFEST.in setup.py python-iniparse.spec include README Changelog LICENSE LICENSE-PSF iniparse-0.4/tests/0000775000076400007640000000000011404735056013332 5ustar paramparaminiparse-0.4/tests/test_tidy.py0000664000076400007640000000534211371336512015715 0ustar paramparamimport unittest from textwrap import dedent from StringIO import StringIO from iniparse import tidy,INIConfig from iniparse.ini import EmptyLine from iniparse.compat import ConfigParser class test_tidy(unittest.TestCase): def setUp(self): self.cfg = INIConfig() def test_empty_file(self): self.assertEqual(str(self.cfg), '') tidy(self.cfg) self.assertEqual(str(self.cfg), '') def test_last_line(self): self.cfg.newsection.newproperty = "Ok" self.assertEqual(str(self.cfg), dedent("""\ [newsection] newproperty = Ok""")) tidy(self.cfg) self.assertEqual(str(self.cfg), dedent("""\ [newsection] newproperty = Ok """)) def test_first_line(self): s = dedent("""\ [newsection] newproperty = Ok """) self.cfg._readfp(StringIO(s)) tidy(self.cfg) self.assertEqual(str(self.cfg), dedent("""\ [newsection] newproperty = Ok """)) def test_remove_newlines(self): s = dedent("""\ [newsection] newproperty = Ok [newsection2] newproperty2 = Ok newproperty3 = yup [newsection4] # remove blank lines, but leave continuation lines unharmed a = 1 b = l1 l2 # asdf l5 c = 2 """) self.cfg._readfp(StringIO(s)) tidy(self.cfg) self.assertEqual(str(self.cfg), dedent("""\ [newsection] newproperty = Ok [newsection2] newproperty2 = Ok newproperty3 = yup [newsection4] # remove blank lines, but leave continuation lines unharmed a = 1 b = l1 l2 # asdf l5 c = 2 """)) def test_compat(self): s = dedent(""" [sec1] a=1 [sec2] b=2 c=3 """) cfg = ConfigParser() cfg.readfp(StringIO(s)) tidy(cfg) self.assertEqual(str(cfg.data), dedent("""\ [sec1] a=1 [sec2] b=2 c=3 """)) class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_tidy, 'test'), ]) iniparse-0.4/tests/test_ini.py0000664000076400007640000002632411371336443015531 0ustar paramparamimport unittest from StringIO import StringIO from iniparse import ini from iniparse import compat from iniparse import config class test_section_line(unittest.TestCase): invalid_lines = [ '# this is a comment', '; this is a comment', ' [sections must start on column1]', '[incomplete', '[ no closing ]brackets]', 'ice-cream = mmmm', 'ic[e-c]ream = mmmm', '[ice-cream] = mmmm', '-$%^', ] def test_invalid(self): for l in self.invalid_lines: p = ini.SectionLine.parse(l) self.assertEqual(p, None) lines = [ ('[section]' , ('section', None, None, -1)), ('[se\ct%[ion\t]' , ('se\ct%[ion\t', None, None, -1)), ('[sec tion] ; hi' , ('sec tion', ' hi', ';', 12)), ('[section] #oops!' , ('section', 'oops!', '#', 11)), ('[section] ; ' , ('section', '', ';', 12)), ('[section] ' , ('section', None, None, -1)), ] def test_parsing(self): for l in self.lines: p = ini.SectionLine.parse(l[0]) self.assertNotEqual(p, None) self.assertEqual(p.name, l[1][0]) self.assertEqual(p.comment, l[1][1]) self.assertEqual(p.comment_separator, l[1][2]) self.assertEqual(p.comment_offset, l[1][3]) def test_printing(self): for l in self.lines: p = ini.SectionLine.parse(l[0]) self.assertEqual(str(p), l[0]) self.assertEqual(p.to_string(), l[0].strip()) indent_test_lines = [ ('[oldname] ; comment', 'long new name', '[long new name] ; comment'), ('[oldname] ; comment', 'short', '[short] ; comment'), ('[oldname] ; comment', 'really long new name', '[really long new name] ; comment'), ] def test_preserve_indentation(self): for l in self.indent_test_lines: p = ini.SectionLine.parse(l[0]) p.name = l[1] self.assertEqual(str(p), l[2]) class test_option_line(unittest.TestCase): lines = [ ('option = value', 'option', ' = ', 'value', None, None, -1), ('option: value', 'option', ': ', 'value', None, None, -1), ('option=value', 'option', '=', 'value', None, None, -1), ('op[ti]on=value', 'op[ti]on', '=', 'value', None, None, -1), ('option = value # no comment', 'option', ' = ', 'value # no comment', None, None, -1), ('option = value ;', 'option', ' = ', 'value', ';', '', 19), ('option = value ; comment', 'option', ' = ', 'value', ';', ' comment', 19), ('option = value;1 ; comment', 'option', ' = ', 'value;1 ; comment', None, None, -1), ('op;ti on = value ;; comm;ent', 'op;ti on', ' = ', 'value', ';', '; comm;ent', 22), ] def test_parsing(self): for l in self.lines: p = ini.OptionLine.parse(l[0]) self.assertEqual(p.name, l[1]) self.assertEqual(p.separator, l[2]) self.assertEqual(p.value, l[3]) self.assertEqual(p.comment_separator, l[4]) self.assertEqual(p.comment, l[5]) self.assertEqual(p.comment_offset, l[6]) invalid_lines = [ ' option = value', '# comment', '; comment', '[section 7]', '[section=option]', 'option', ] def test_invalid(self): for l in self.invalid_lines: p = ini.OptionLine.parse(l) self.assertEqual(p, None) print_lines = [ 'option = value', 'option= value', 'option : value', 'option: value ', 'option = value ', 'option = value ;', 'option = value;2 ;; 4 5', 'option = value ; hi!', ] def test_printing(self): for l in self.print_lines: p = ini.OptionLine.parse(l) self.assertEqual(str(p), l) self.assertEqual(p.to_string(), l.rstrip()) indent_test_lines = [ ('option = value ;comment', 'newoption', 'newval', 'newoption = newval ;comment'), ('option = value ;comment', 'newoption', 'newval', 'newoption = newval ;comment'), ] def test_preserve_indentation(self): for l in self.indent_test_lines: p = ini.OptionLine.parse(l[0]) p.name = l[1] p.value = l[2] self.assertEqual(str(p), l[3]) class test_comment_line(unittest.TestCase): invalid_lines = [ '[section]', 'option = value ;comment', ' # must start on first column', ] def test_invalid(self): for l in self.invalid_lines: p = ini.CommentLine.parse(l) self.assertEqual(p, None) lines = [ '#this is a comment', ';; this is also a comment', '; so is this ', 'Rem and this', 'remthis too!' ] def test_parsing(self): for l in self.lines: p = ini.CommentLine.parse(l) self.assertEqual(str(p), l) self.assertEqual(p.to_string(), l.rstrip()) class test_other_lines(unittest.TestCase): def test_empty(self): for s in ['asdf', '; hi', ' #rr', '[sec]', 'opt=val']: self.assertEqual(ini.EmptyLine.parse(s), None) for s in ['', ' ', '\t \t']: self.assertEqual(str(ini.EmptyLine.parse(s)), s) def test_continuation(self): for s in ['asdf', '; hi', '[sec]', 'a=3']: self.assertEqual(ini.ContinuationLine.parse(s), None) for s in [' asdfasd ', '\t mmmm']: self.assertEqual(ini.ContinuationLine.parse(s).value, s.strip()) self.assertEqual(ini.ContinuationLine.parse(s).to_string(), s.rstrip().replace('\t',' ')) class test_ini(unittest.TestCase): s1 = """ [section1] help = me I'm = desperate ; really! [section2] # comment and empty line before the first option just = what? just = kidding [section1] help = yourself but = also me """ def test_basic(self): sio = StringIO(self.s1) p = ini.INIConfig(sio) self.assertEqual(str(p), self.s1) self.assertEqual(p._data.find('section1').find('but').value, 'also me') self.assertEqual(p._data.find('section1').find('help').value, 'yourself') self.assertEqual(p._data.find('section2').find('just').value, 'kidding') itr = p._data.finditer('section1') v = itr.next() self.assertEqual(v.find('help').value, 'yourself') self.assertEqual(v.find('but').value, 'also me') v = itr.next() self.assertEqual(v.find('help').value, 'me') self.assertEqual(v.find('I\'m').value, 'desperate') self.assertRaises(StopIteration, itr.next) self.assertRaises(KeyError, p._data.find, 'section') self.assertRaises(KeyError, p._data.find('section2').find, 'ahem') def test_lookup(self): sio = StringIO(self.s1) p = ini.INIConfig(sio) self.assertEqual(p.section1.help, 'yourself') self.assertEqual(p.section1.but, 'also me') self.assertEqual(getattr(p.section1, 'I\'m'), 'desperate') self.assertEqual(p.section2.just, 'kidding') self.assertEqual(p.section1.just.__class__, config.Undefined) self.assertEqual(p.section2.help.__class__, config.Undefined) def test_newsection(self): sio = StringIO(self.s1) p = ini.INIConfig(sio) p.new1.created = 1 setattr(getattr(p, 'new2'), 'created', 1) p.new3['created'] = 1 p['new4'].created = 1 p['new5']['created'] = 1 self.assertEqual(p.new1.created, 1) self.assertEqual(p.new2.created, 1) self.assertEqual(p.new3.created, 1) self.assertEqual(p.new4.created, 1) self.assertEqual(p.new5.created, 1) def test_order(self): sio = StringIO(self.s1) p = ini.INIConfig(sio) self.assertEqual(list(p), ['section1','section2']) self.assertEqual(list(p.section1), ['help', "i'm", 'but']) self.assertEqual(list(p.section2), ['just']) def test_delete(self): sio = StringIO(self.s1) p = ini.INIConfig(sio) del p.section1.help self.assertEqual(list(p.section1), ["i'm", 'but']) self.assertEqual(str(p), """ [section1] I'm = desperate ; really! [section2] # comment and empty line before the first option just = what? just = kidding [section1] but = also me """) del p.section2 self.assertEqual(str(p), """ [section1] I'm = desperate ; really! [section1] but = also me """) def check_order(self, c): sio = StringIO(self.s1) c = c({'pi':'3.14153'}) c.readfp(sio) self.assertEqual(c.sections(), ['section1','section2']) self.assertEqual(c.options('section1'), ['help', "i'm", 'but', 'pi']) self.assertEqual(c.items('section1'), [ ('help', 'yourself'), ("i'm", 'desperate'), ('but', 'also me'), ('pi', '3.14153'), ]) def test_compat_order(self): self.check_order(compat.RawConfigParser) self.check_order(compat.ConfigParser) inv = ( (""" # values must be in a section value = 5 """, """ # values must be in a section #value = 5 """), (""" # continuation lines only allowed after options [section] op1 = qwert yuiop op2 = qwert yuiop op3 = qwert # yup yuiop [another section] hmmm """, """ # continuation lines only allowed after options [section] op1 = qwert yuiop op2 = qwert yuiop op3 = qwert # yup yuiop [another section] # hmmm """)) def test_invalid(self): for (org, mod) in self.inv: ip = ini.INIConfig(StringIO(org), parse_exc=False) self.assertEqual(str(ip), mod) # test multi-line values s2 = ( """ [section] option = foo bar baz yam """ ) s3 = ( """ [section] option = foo bar mum baz yam """ ) def test_option_continuation(self): ip = ini.INIConfig(StringIO(self.s2)) self.assertEqual(str(ip), self.s2) value = ip.section.option.split('\n') value.insert(3, 'mum') ip.section.option = '\n'.join(value) self.assertEqual(str(ip), self.s3) s5 = ( """ [section] option = foo bar """ ) s6 = ( """ [section] option = foo another = baz """ ) def test_option_continuation_single(self): ip = ini.INIConfig(StringIO(self.s5)) self.assertEqual(str(ip), self.s5) ip.section.option = '\n'.join(['', '', '', 'foo', '', '', '']) ip.section.another = 'baz' self.assertEqual(str(ip), self.s6) class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_section_line, 'test'), unittest.makeSuite(test_option_line, 'test'), unittest.makeSuite(test_comment_line, 'test'), unittest.makeSuite(test_other_lines, 'test'), unittest.makeSuite(test_ini, 'test'), ]) iniparse-0.4/tests/test_multiprocessing.py0000664000076400007640000000152111370724335020171 0ustar paramparamimport unittest try: from multiprocessing import Process, Queue, Pipe disabled = False except ImportError: disabled = True from iniparse import compat, ini class test_ini(unittest.TestCase): """Test sending INIConfig objects.""" def test_queue(self): def getxy(q, w): cfg = q.get_nowait() w.put(cfg.x.y) cfg = ini.INIConfig() cfg.x.y = '42' q = Queue() w = Queue() q.put(cfg) p = Process(target=getxy, args=(q, w)) p.start() self.assertEqual(w.get(timeout=1), '42') class suite(unittest.TestSuite): def __init__(self): if disabled: unittest.TestSuite.__init__(self, []) else: unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_ini, 'test'), ]) iniparse-0.4/tests/test_misc.py0000664000076400007640000003242311370177373015706 0ustar paramparamimport re import unittest import pickle import ConfigParser from textwrap import dedent from StringIO import StringIO from iniparse import compat, ini class CaseSensitiveConfigParser(compat.ConfigParser): """Case Sensitive version of ConfigParser""" def optionxform(self, option): """Use str()""" return str(option) class test_optionxform_override(unittest.TestCase): def test_derivedclass(self): c = CaseSensitiveConfigParser() c.add_section('foo') c.set('foo', 'bar', 'a') c.set('foo', 'Bar', 'b') self.assertEqual(c.get('foo', 'bar'), 'a') self.assertEqual(c.get('foo', 'Bar'), 'b') def test_assignment(self): c = compat.ConfigParser() c.optionxform = str c.add_section('foo') c.set('foo', 'bar', 'a') c.set('foo', 'Bar', 'b') self.assertEqual(c.get('foo', 'bar'), 'a') self.assertEqual(c.get('foo', 'Bar'), 'b') def test_dyanamic(self): c = compat.ConfigParser() c.optionxform = str c.add_section('foo') c.set('foo', 'bar', 'a') c.set('foo', 'Bar', 'b') c.set('foo', 'BAR', 'c') c.optionxform = str.upper self.assertEqual(c.get('foo', 'Bar'), 'c') c.optionxform = str.lower self.assertEqual(c.get('foo', 'Bar'), 'a') c.optionxform = str self.assertEqual(c.get('foo', 'Bar'), 'b') class OnlyReadline: def __init__(self, s): self.sio = StringIO(s) def readline(self): return self.sio.readline() class test_readline(unittest.TestCase): """Test that the file object passed to readfp only needs to support the .readline() method. As of Python-2.4.4, this is true of the standard librariy's ConfigParser, and so other code uses that to guide what is sufficiently file-like.""" test_strings = [ """\ [foo] bar=7 baz=8""", """\ [foo] bar=7 baz=8 """, """\ [foo] bar=7 baz=8 """] def test_readline_iniconfig(self): for s in self.test_strings: fp = OnlyReadline(s) c = ini.INIConfig() c._readfp(fp) self.assertEqual(s, str(c)) def test_readline_configparser(self): for s in self.test_strings: fp = OnlyReadline(s) c = compat.ConfigParser() c.readfp(fp) ss = StringIO() c.write(ss) self.assertEqual(s, ss.getvalue()) class test_multiline_with_comments(unittest.TestCase): """Test that multiline values are allowed to span comments.""" s = """\ [sec] opt = 1 2 # comment 3""" def test_read(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) self.assertEqual(c.sec.opt, '1\n2\n\n3') def test_write(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) c.sec.opt = 'xyz' self.assertEqual(str(c), """\ [sec] opt = xyz""") class test_empty_file(unittest.TestCase): """Test if it works with an blank file""" s = "" def test_read(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) self.assertEqual(str(c), '') def test_write(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) c.sec.opt = 'xyz' self.assertEqual(str(c), """\ [sec] opt = xyz""") class test_custom_dict(unittest.TestCase): def test_custom_dict_not_supported(self): self.assertRaises(ValueError, compat.RawConfigParser, None, 'foo') class test_compat(unittest.TestCase): """Miscellaneous compatibility tests.""" s = dedent("""\ [DEFAULT] pi = 3.1415 three = 3 poet = e e cummings NH = live free or die [sec] opt = 6 three = 3.0 no-three = one two four longopt = foo bar # empty line should not be part of value baz bat """) def do_test(self, c): # default section is not acknowledged self.assertEqual(c.sections(), ['sec']) # options in the default section are merged with other sections self.assertEqual(sorted(c.options('sec')), ['longopt', 'nh', 'no-three', 'opt', 'pi', 'poet', 'three']) # empty lines are stripped from multi-line values self.assertEqual(c.get('sec', 'poet').split('\n'), ['e e', 'cummings']) self.assertEqual(c.get('DEFAULT', 'poet').split('\n'), ['e e', 'cummings']) self.assertEqual(c.get('sec', 'longopt').split('\n'), ['foo', 'bar', 'baz', 'bat']) self.assertEqual(c.get('sec', 'NH').split('\n'), ['', 'live free', 'or die']) # check that empy-line stripping happens on all access paths # defaults() self.assertEqual(c.defaults(), { 'poet': 'e e\ncummings', 'nh': '\nlive free\nor die', 'pi': '3.1415', 'three': '3', }) # items() l = c.items('sec') l.sort() self.assertEqual(l, [ ('longopt', 'foo\nbar\nbaz\nbat'), ('nh', '\nlive free\nor die'), ('no-three', 'one\ntwo\nfour'), ('opt', '6'), ('pi', '3.1415'), ('poet', 'e e\ncummings'), ('three', '3.0'), ]) # empty lines are preserved on explicitly set values c.set('sec', 'longopt', '\n'.join(['a', 'b', '', 'c', '', '', 'd'])) c.set('DEFAULT', 'NH', '\nlive free\n\nor die') self.assertEqual(c.get('sec', 'longopt').split('\n'), ['a', 'b', '', 'c', '', '', 'd']) self.assertEqual(c.get('sec', 'NH').split('\n'), ['', 'live free', '', 'or die']) self.assertEqual(c.defaults(), { 'poet': 'e e\ncummings', 'nh': '\nlive free\n\nor die', 'pi': '3.1415', 'three': '3', }) # items() l = c.items('sec') l.sort() self.assertEqual(l, [ ('longopt', 'a\nb\n\nc\n\n\nd'), ('nh', '\nlive free\n\nor die'), ('no-three', 'one\ntwo\nfour'), ('opt', '6'), ('pi', '3.1415'), ('poet', 'e e\ncummings'), ('three', '3.0'), ]) # empty line special magic goes away after remove_option() self.assertEqual(c.get('sec', 'no-three').split('\n'), ['one', 'two','four']) c.remove_option('sec', 'no-three') c.set('sec', 'no-three', 'q\n\nw') self.assertEqual(c.get('sec', 'no-three'), 'q\n\nw') c.remove_option('sec', 'no-three') def do_configparser_test(self, cfg_class): c = cfg_class() c.readfp(StringIO(self.s)) self.do_test(c) o = StringIO() c.write(o) self.assertEqual(o.getvalue().split('\n'), [ '[DEFAULT]', 'poet = e e', '\tcummings', 'nh = ', '\tlive free', '\t', '\tor die', 'pi = 3.1415', 'three = 3', '', '[sec]', 'opt = 6', 'longopt = a', '\tb', '\t', '\tc', '\t', '\t', '\td', 'three = 3.0', '', '']) def test_py_rawcfg(self): self.do_configparser_test(ConfigParser.RawConfigParser) def test_py_cfg(self): self.do_configparser_test(ConfigParser.ConfigParser) def test_py_safecfg(self): self.do_configparser_test(ConfigParser.SafeConfigParser) def do_compat_test(self, cfg_class): c = cfg_class() c.readfp(StringIO(self.s)) self.do_test(c) o = StringIO() c.write(o) self.assertEqual(o.getvalue().split('\n'), [ '[DEFAULT]', 'pi = 3.1415', 'three = 3', 'poet = e e', '', ' cummings', 'NH =', ' live free', '', ' or die', '', '[sec]', 'opt = 6', 'three = 3.0', 'longopt = a', ' b', '', ' c', '', '', ' d', '', '']) def test_py_rawcfg(self): self.do_compat_test(compat.RawConfigParser) def test_py_cfg(self): self.do_compat_test(compat.ConfigParser) def test_py_safecfg(self): self.do_compat_test(compat.SafeConfigParser) class test_pickle(unittest.TestCase): s = dedent("""\ [DEFAULT] pi = 3.1415 three = 3 poet = e e cummings NH = live free or die [sec] opt = 6 three = 3.0 no-three = one two four james = bond """) def do_compat_checks(self, c): self.assertEqual(c.sections(), ['sec']) self.assertEqual(sorted(c.options('sec')), ['james', 'nh', 'no-three', 'opt', 'pi', 'poet', 'three']) self.assertEqual(c.defaults(), { 'poet': 'e e\ncummings', 'nh': '\nlive free\nor die', 'pi': '3.1415', 'three': '3', }) l = c.items('sec') l.sort() self.assertEqual(l, [ ('james', 'bond'), ('nh', '\nlive free\nor die'), ('no-three', 'one\ntwo\nfour'), ('opt', '6'), ('pi', '3.1415'), ('poet', 'e e\ncummings'), ('three', '3.0'), ]) self.do_ini_checks(c.data) def do_ini_checks(self, c): self.assertEqual(list(c), ['sec']) self.assertEqual(sorted(c['sec']), ['james', 'nh', 'no-three', 'opt', 'pi', 'poet', 'three']) self.assertEqual(c._defaults['pi'], '3.1415') self.assertEqual(c.sec.opt, '6') self.assertEqual(c.sec.three, '3.0') self.assertEqual(c.sec['no-three'], 'one\ntwo\n\nfour') self.assertEqual(c.sec.james, 'bond') self.assertEqual(c.sec.pi, '3.1415') self.assertEqual(c.sec.poet, 'e e\n\ncummings') self.assertEqual(c.sec.NH, '\nlive free\n\nor die') self.assertEqual(str(c), self.s) def test_compat(self): for cfg_class in (compat.ConfigParser, compat.RawConfigParser, compat.SafeConfigParser): c = cfg_class() c.readfp(StringIO(self.s)) self.do_compat_checks(c) for i in range(0, pickle.HIGHEST_PROTOCOL+1): p = pickle.dumps(c, protocol=i) c2 = pickle.loads(p) self.do_compat_checks(c2) def test_ini(self): c = ini.INIConfig() c._readfp(StringIO(self.s)) self.do_ini_checks(c) for i in range(0, pickle.HIGHEST_PROTOCOL+1): p = pickle.dumps(c, protocol=i) c2 = pickle.loads(p) self.do_ini_checks(c2) class test_comment_syntax(unittest.TestCase): """Test changing comment syntax with change_comment_syntax""" def test_regex(self): # original regular expression org_regex = re.compile(r'^(?P[;#]|[rR][eE][mM])(?P.*)$') ini.change_comment_syntax(';#', True) self.assertEqual(ini.CommentLine.regex, org_regex) # mercurial-safe comment line regex, as given by Steve Borho & Paul Lambert # bitbucket.org/tortoisehg/stable/src/tip/tortoisehg/hgtk/thgconfig.py#cl-1084 # http://groups.google.com/group/iniparse-discuss/msg/b41a54aa185a9b7c hg_regex = re.compile(r'^(?P[%;#])(?P.*)$') ini.change_comment_syntax('%;#', False) self.assertEqual(ini.CommentLine.regex, hg_regex) # change_comment_syntax() defaults to hg regex ini.change_comment_syntax() self.assertEqual(ini.CommentLine.regex, hg_regex) # test escaping of special chars in pattern regex = re.compile(r'^(?P[;#\-\^[\]])(?P.*)$') ini.change_comment_syntax(';#-^[]') self.assertEqual(ini.CommentLine.regex, regex) def test_ignore_includes(self): ini.change_comment_syntax() cfg = ini.INIConfig(StringIO(dedent(""" # This is a mercurial-style config % include foobar [ui] username = Firstname Lastname """))) self.assertEqual(cfg.ui.username, 'Firstname Lastname ') self.assertEqual(str(cfg), dedent(""" # This is a mercurial-style config % include foobar [ui] username = Firstname Lastname """)) def tearDown(self): ini.change_comment_syntax(';#', True) class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_optionxform_override, 'test'), unittest.makeSuite(test_readline, 'test'), unittest.makeSuite(test_multiline_with_comments, 'test'), unittest.makeSuite(test_empty_file, 'test'), unittest.makeSuite(test_custom_dict, 'test'), unittest.makeSuite(test_compat, 'test'), unittest.makeSuite(test_pickle, 'test'), unittest.makeSuite(test_comment_syntax, 'test'), ]) iniparse-0.4/tests/test_fuzz.py0000664000076400007640000001071511371336512015742 0ustar paramparamimport re import os import random import unittest import ConfigParser from StringIO import StringIO from iniparse import compat, ini, tidy # TODO: # tabs # substitutions def random_string(maxlen=200): length = random.randint(0, maxlen) s = [] for i in range(length): s.append(chr(random.randint(32, 126))) return ''.join(s) def random_space(maxlen=10): length = random.randint(0, maxlen) return ' '*length def random_ini_file(): num_lines = random.randint(0, 100) lines = [] for i in range(num_lines): x = random.random() if x < 0.1: # empty line lines.append(random_space()) elif x < 0.3: # comment sep = random.choice(['#', ';']) lines.append(sep + random_string()) elif x < 0.5: # section if random.random() < 0.1: name = 'DEFAULT' else: name = random_string() name = re.sub(']', '' , name) l = '[' + name + ']' if random.randint(0,1): l += random_space() if random.randint(0,1): sep = random.choice(['#', ';']) l += sep + random_string() lines.append(l) elif x < 0.7: # option name = random_string() name = re.sub(':|=| |\[', '', name) sep = random.choice([':', '=']) l = name + random_space() + sep + random_space() + random_string() if random.randint(0,1): l += ' ' + random_space() + ';' +random_string() lines.append(l) elif x < 0.9: # continuation lines.append(' ' + random_space() + random_string()) else: # junk lines.append(random_string()) return '\n'.join(lines) class test_fuzz(unittest.TestCase): def test_fuzz(self): random.seed(42) try: num_iter = int(os.environ['INIPARSE_FUZZ_ITERATIONS']) except (KeyError, ValueError): num_iter = 100 for fuzz_iter in range(num_iter): try: # parse random file with errors disabled s = random_ini_file() c = ini.INIConfig(parse_exc=False) c._readfp(StringIO(s)) # check that file is preserved, except for # commenting out erroneous lines l1 = s.split('\n') l2 = str(c).split('\n') self.assertEqual(len(l1), len(l2)) good_lines = [] for i in range(len(l1)): try: self.assertEqual(l1[i], l2[i]) good_lines.append(l1[i]) except AssertionError: self.assertEqual('#'+l1[i], l2[i]) # parse the good subset of the file # using ConfigParser s = '\n'.join(good_lines) cc = compat.RawConfigParser() cc.readfp(StringIO(s)) cc_py = ConfigParser.RawConfigParser() cc_py.readfp(StringIO(s)) # compare the two configparsers self.assertEqualConfig(cc_py, cc) # check that tidy does not change semantics tidy(cc) cc_tidy = ConfigParser.RawConfigParser() cc_tidy.readfp(StringIO(str(cc.data))) self.assertEqualConfig(cc_py, cc_tidy) except AssertionError: fname = 'fuzz-test-iter-%d.ini' % fuzz_iter print 'Fuzz test failed at iteration', fuzz_iter print 'Writing out failing INI file as', fname f = open(fname, 'w') f.write(s) f.close() raise def assertEqualConfig(self, c1, c2): self.assertEqualSorted(c1.sections(), c2.sections()) self.assertEqualSorted(c1.defaults().items(), c2.defaults().items()) for sec in c1.sections(): self.assertEqualSorted(c1.options(sec), c2.options(sec)) for opt in c1.options(sec): self.assertEqual(c1.get(sec, opt), c2.get(sec, opt)) def assertEqualSorted(self, l1, l2): l1.sort() l2.sort() self.assertEqual(l1, l2) class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_fuzz, 'test'), ]) iniparse-0.4/tests/test_compat.py0000644000076400007640000004457611116450302016230 0ustar paramparamfrom iniparse import compat as ConfigParser import StringIO import unittest import UserDict from test import test_support class SortedDict(UserDict.UserDict): def items(self): result = self.data.items() result.sort() return result def keys(self): result = self.data.keys() result.sort() return result def values(self): result = self.items() return [i[1] for i in values] def iteritems(self): return iter(self.items()) def iterkeys(self): return iter(self.keys()) __iter__ = iterkeys def itervalues(self): return iter(self.values()) class TestCaseBase(unittest.TestCase): def newconfig(self, defaults=None): if defaults is None: self.cf = self.config_class() else: self.cf = self.config_class(defaults) return self.cf def fromstring(self, string, defaults=None): cf = self.newconfig(defaults) sio = StringIO.StringIO(string) cf.readfp(sio) return cf def test_basic(self): cf = self.fromstring( "[Foo Bar]\n" "foo=bar\n" "[Spacey Bar]\n" "foo = bar\n" "[Commented Bar]\n" "foo: bar ; comment\n" "[Long Line]\n" "foo: this line is much, much longer than my editor\n" " likes it.\n" "[Section\\with$weird%characters[\t]\n" "[Internationalized Stuff]\n" "foo[bg]: Bulgarian\n" "foo=Default\n" "foo[en]=English\n" "foo[de]=Deutsch\n" "[Spaces]\n" "key with spaces : value\n" "another with spaces = splat!\n" ) L = cf.sections() L.sort() eq = self.assertEqual eq(L, [r'Commented Bar', r'Foo Bar', r'Internationalized Stuff', r'Long Line', r'Section\with$weird%characters[' '\t', r'Spaces', r'Spacey Bar', ]) # The use of spaces in the section names serves as a # regression test for SourceForge bug #583248: # http://www.python.org/sf/583248 eq(cf.get('Foo Bar', 'foo'), 'bar') eq(cf.get('Spacey Bar', 'foo'), 'bar') eq(cf.get('Commented Bar', 'foo'), 'bar') eq(cf.get('Spaces', 'key with spaces'), 'value') eq(cf.get('Spaces', 'another with spaces'), 'splat!') self.failIf('__name__' in cf.options("Foo Bar"), '__name__ "option" should not be exposed by the API!') # Make sure the right things happen for remove_option(); # added to include check for SourceForge bug #123324: self.failUnless(cf.remove_option('Foo Bar', 'foo'), "remove_option() failed to report existance of option") self.failIf(cf.has_option('Foo Bar', 'foo'), "remove_option() failed to remove option") self.failIf(cf.remove_option('Foo Bar', 'foo'), "remove_option() failed to report non-existance of option" " that was removed") self.assertRaises(ConfigParser.NoSectionError, cf.remove_option, 'No Such Section', 'foo') eq(cf.get('Long Line', 'foo'), 'this line is much, much longer than my editor\nlikes it.') def test_case_sensitivity(self): cf = self.newconfig() cf.add_section("A") cf.add_section("a") L = cf.sections() L.sort() eq = self.assertEqual eq(L, ["A", "a"]) cf.set("a", "B", "value") eq(cf.options("a"), ["b"]) eq(cf.get("a", "b"), "value", "could not locate option, expecting case-insensitive option names") self.failUnless(cf.has_option("a", "b")) cf.set("A", "A-B", "A-B value") for opt in ("a-b", "A-b", "a-B", "A-B"): self.failUnless( cf.has_option("A", opt), "has_option() returned false for option which should exist") eq(cf.options("A"), ["a-b"]) eq(cf.options("a"), ["b"]) cf.remove_option("a", "B") eq(cf.options("a"), []) # SF bug #432369: cf = self.fromstring( "[MySection]\nOption: first line\n\tsecond line\n") eq(cf.options("MySection"), ["option"]) eq(cf.get("MySection", "Option"), "first line\nsecond line") # SF bug #561822: cf = self.fromstring("[section]\nnekey=nevalue\n", defaults={"key":"value"}) self.failUnless(cf.has_option("section", "Key")) def test_default_case_sensitivity(self): cf = self.newconfig({"foo": "Bar"}) self.assertEqual( cf.get("DEFAULT", "Foo"), "Bar", "could not locate option, expecting case-insensitive option names") cf = self.newconfig({"Foo": "Bar"}) self.assertEqual( cf.get("DEFAULT", "Foo"), "Bar", "could not locate option, expecting case-insensitive defaults") def test_parse_errors(self): self.newconfig() self.parse_error(ConfigParser.ParsingError, "[Foo]\n extra-spaces: splat\n") self.parse_error(ConfigParser.ParsingError, "[Foo]\n extra-spaces= splat\n") self.parse_error(ConfigParser.ParsingError, "[Foo]\noption-without-value\n") self.parse_error(ConfigParser.ParsingError, "[Foo]\n:value-without-option-name\n") self.parse_error(ConfigParser.ParsingError, "[Foo]\n=value-without-option-name\n") self.parse_error(ConfigParser.MissingSectionHeaderError, "No Section!\n") def parse_error(self, exc, src): sio = StringIO.StringIO(src) self.assertRaises(exc, self.cf.readfp, sio) def test_query_errors(self): cf = self.newconfig() self.assertEqual(cf.sections(), [], "new ConfigParser should have no defined sections") self.failIf(cf.has_section("Foo"), "new ConfigParser should have no acknowledged sections") self.assertRaises(ConfigParser.NoSectionError, cf.options, "Foo") self.assertRaises(ConfigParser.NoSectionError, cf.set, "foo", "bar", "value") self.get_error(ConfigParser.NoSectionError, "foo", "bar") cf.add_section("foo") self.get_error(ConfigParser.NoOptionError, "foo", "bar") def get_error(self, exc, section, option): try: self.cf.get(section, option) except exc, e: return e else: self.fail("expected exception type %s.%s" % (exc.__module__, exc.__name__)) def test_boolean(self): cf = self.fromstring( "[BOOLTEST]\n" "T1=1\n" "T2=TRUE\n" "T3=True\n" "T4=oN\n" "T5=yes\n" "F1=0\n" "F2=FALSE\n" "F3=False\n" "F4=oFF\n" "F5=nO\n" "E1=2\n" "E2=foo\n" "E3=-1\n" "E4=0.1\n" "E5=FALSE AND MORE" ) for x in range(1, 5): self.failUnless(cf.getboolean('BOOLTEST', 't%d' % x)) self.failIf(cf.getboolean('BOOLTEST', 'f%d' % x)) self.assertRaises(ValueError, cf.getboolean, 'BOOLTEST', 'e%d' % x) def test_weird_errors(self): cf = self.newconfig() cf.add_section("Foo") self.assertRaises(ConfigParser.DuplicateSectionError, cf.add_section, "Foo") def test_write(self): cf = self.fromstring( "[Long Line]\n" "foo: this line is much, much longer than my editor\n" " likes it.\n" "[DEFAULT]\n" "foo: another very\n" " long line" ) output = StringIO.StringIO() cf.write(output) self.assertEqual( output.getvalue(), "[Long Line]\n" "foo: this line is much, much longer than my editor\n" " likes it.\n" "[DEFAULT]\n" "foo: another very\n" " long line" ) def test_set_string_types(self): cf = self.fromstring("[sect]\n" "option1=foo\n") # Check that we don't get an exception when setting values in # an existing section using strings: class mystr(str): pass cf.set("sect", "option1", "splat") cf.set("sect", "option1", mystr("splat")) cf.set("sect", "option2", "splat") cf.set("sect", "option2", mystr("splat")) try: unicode except NameError: pass else: cf.set("sect", "option1", unicode("splat")) cf.set("sect", "option2", unicode("splat")) def test_read_returns_file_list(self): file1 = test_support.findfile("cfgparser.1") # check when we pass a mix of readable and non-readable files: cf = self.newconfig() parsed_files = cf.read([file1, "nonexistant-file"]) self.assertEqual(parsed_files, [file1]) self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") # check when we pass only a filename: cf = self.newconfig() parsed_files = cf.read(file1) self.assertEqual(parsed_files, [file1]) self.assertEqual(cf.get("Foo Bar", "foo"), "newbar") # check when we pass only missing files: cf = self.newconfig() parsed_files = cf.read(["nonexistant-file"]) self.assertEqual(parsed_files, []) # check when we pass no files: cf = self.newconfig() parsed_files = cf.read([]) self.assertEqual(parsed_files, []) # shared by subclasses def get_interpolation_config(self): return self.fromstring( "[Foo]\n" "bar=something %(with1)s interpolation (1 step)\n" "bar9=something %(with9)s lots of interpolation (9 steps)\n" "bar10=something %(with10)s lots of interpolation (10 steps)\n" "bar11=something %(with11)s lots of interpolation (11 steps)\n" "with11=%(with10)s\n" "with10=%(with9)s\n" "with9=%(with8)s\n" "with8=%(With7)s\n" "with7=%(WITH6)s\n" "with6=%(with5)s\n" "With5=%(with4)s\n" "WITH4=%(with3)s\n" "with3=%(with2)s\n" "with2=%(with1)s\n" "with1=with\n" "\n" "[Mutual Recursion]\n" "foo=%(bar)s\n" "bar=%(foo)s\n" "\n" "[Interpolation Error]\n" "name=%(reference)s\n", # no definition for 'reference' defaults={"getname": "%(__name__)s"}) def check_items_config(self, expected): cf = self.fromstring( "[section]\n" "name = value\n" "key: |%(name)s| \n" "getdefault: |%(default)s|\n" "getname: |%(__name__)s|", defaults={"default": ""}) L = list(cf.items("section")) L.sort() self.assertEqual(L, expected) class ConfigParserTestCase(TestCaseBase): config_class = ConfigParser.ConfigParser def test_interpolation(self): cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "getname"), "Foo") eq(cf.get("Foo", "bar"), "something with interpolation (1 step)") eq(cf.get("Foo", "bar9"), "something with lots of interpolation (9 steps)") eq(cf.get("Foo", "bar10"), "something with lots of interpolation (10 steps)") self.get_error(ConfigParser.InterpolationDepthError, "Foo", "bar11") def test_interpolation_missing_value(self): cf = self.get_interpolation_config() e = self.get_error(ConfigParser.InterpolationError, "Interpolation Error", "name") self.assertEqual(e.reference, "reference") self.assertEqual(e.section, "Interpolation Error") self.assertEqual(e.option, "name") def test_items(self): self.check_items_config([('default', ''), ('getdefault', '||'), ('getname', '|section|'), ('key', '|value|'), ('name', 'value')]) def test_set_nonstring_types(self): cf = self.newconfig() cf.add_section('non-string') cf.set('non-string', 'int', 1) cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13, '%(']) cf.set('non-string', 'dict', {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'}) cf.set('non-string', 'string_with_interpolation', '%(list)s') self.assertEqual(cf.get('non-string', 'int', raw=True), 1) self.assertRaises(TypeError, cf.get, 'non-string', 'int') self.assertEqual(cf.get('non-string', 'list', raw=True), [0, 1, 1, 2, 3, 5, 8, 13, '%(']) self.assertRaises(TypeError, cf.get, 'non-string', 'list') self.assertEqual(cf.get('non-string', 'dict', raw=True), {'pi': 3.14159, '%(': 1, '%(list)': '%(list)'}) self.assertRaises(TypeError, cf.get, 'non-string', 'dict') self.assertEqual(cf.get('non-string', 'string_with_interpolation', raw=True), '%(list)s') self.assertRaises(ValueError, cf.get, 'non-string', 'string_with_interpolation', raw=False) class RawConfigParserTestCase(TestCaseBase): config_class = ConfigParser.RawConfigParser def test_interpolation(self): cf = self.get_interpolation_config() eq = self.assertEqual eq(cf.get("Foo", "getname"), "%(__name__)s") eq(cf.get("Foo", "bar"), "something %(with1)s interpolation (1 step)") eq(cf.get("Foo", "bar9"), "something %(with9)s lots of interpolation (9 steps)") eq(cf.get("Foo", "bar10"), "something %(with10)s lots of interpolation (10 steps)") eq(cf.get("Foo", "bar11"), "something %(with11)s lots of interpolation (11 steps)") def test_items(self): self.check_items_config([('default', ''), ('getdefault', '|%(default)s|'), ('getname', '|%(__name__)s|'), ('key', '|%(name)s|'), ('name', 'value')]) def test_set_nonstring_types(self): cf = self.newconfig() cf.add_section('non-string') cf.set('non-string', 'int', 1) cf.set('non-string', 'list', [0, 1, 1, 2, 3, 5, 8, 13]) cf.set('non-string', 'dict', {'pi': 3.14159}) self.assertEqual(cf.get('non-string', 'int'), 1) self.assertEqual(cf.get('non-string', 'list'), [0, 1, 1, 2, 3, 5, 8, 13]) self.assertEqual(cf.get('non-string', 'dict'), {'pi': 3.14159}) class SafeConfigParserTestCase(ConfigParserTestCase): config_class = ConfigParser.SafeConfigParser def test_safe_interpolation(self): # See http://www.python.org/sf/511737 cf = self.fromstring("[section]\n" "option1=xxx\n" "option2=%(option1)s/xxx\n" "ok=%(option1)s/%%s\n" "not_ok=%(option2)s/%%s") self.assertEqual(cf.get("section", "ok"), "xxx/%s") self.assertEqual(cf.get("section", "not_ok"), "xxx/xxx/%s") def test_set_malformatted_interpolation(self): cf = self.fromstring("[sect]\n" "option1=foo\n") self.assertEqual(cf.get('sect', "option1"), "foo") self.assertRaises(ValueError, cf.set, "sect", "option1", "%foo") self.assertRaises(ValueError, cf.set, "sect", "option1", "foo%") self.assertRaises(ValueError, cf.set, "sect", "option1", "f%oo") self.assertEqual(cf.get('sect', "option1"), "foo") def test_set_nonstring_types(self): cf = self.fromstring("[sect]\n" "option1=foo\n") # Check that we get a TypeError when setting non-string values # in an existing section: self.assertRaises(TypeError, cf.set, "sect", "option1", 1) self.assertRaises(TypeError, cf.set, "sect", "option1", 1.0) self.assertRaises(TypeError, cf.set, "sect", "option1", object()) self.assertRaises(TypeError, cf.set, "sect", "option2", 1) self.assertRaises(TypeError, cf.set, "sect", "option2", 1.0) self.assertRaises(TypeError, cf.set, "sect", "option2", object()) def test_add_section_default_1(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, "default") def test_add_section_default_2(self): cf = self.newconfig() self.assertRaises(ValueError, cf.add_section, "DEFAULT") class SortedTestCase(RawConfigParserTestCase): def newconfig(self, defaults=None): self.cf = self.config_class(defaults=defaults, dict_type=SortedDict) return self.cf def test_sorted(self): self.fromstring("[b]\n" "o4=1\n" "o3=2\n" "o2=3\n" "o1=4\n" "[a]\n" "k=v\n") output = StringIO.StringIO() self.cf.write(output) self.assertEquals(output.getvalue(), "[a]\n" "k = v\n\n" "[b]\n" "o1 = 4\n" "o2 = 3\n" "o3 = 2\n" "o4 = 1\n\n") def test_main(): test_support.run_unittest( ConfigParserTestCase, RawConfigParserTestCase, SafeConfigParserTestCase, SortedTestCase ) class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(RawConfigParserTestCase, 'test'), unittest.makeSuite(ConfigParserTestCase, 'test'), unittest.makeSuite(SafeConfigParserTestCase, 'test'), #unittest.makeSuite(SortedTestCase, 'test'), ]) if __name__ == "__main__": test_main() iniparse-0.4/tests/test_unicode.py0000664000076400007640000000242111116417541016364 0ustar paramparamimport unittest from StringIO import StringIO from iniparse import compat, ini class test_unicode(unittest.TestCase): """Test files read in unicode-mode.""" s1 = u"""\ [foo] bar = fish """ s2 = u"""\ \ufeff[foo] bar = mammal baz = Marc-Andr\202 """ def basic_tests(self, s, strable): f = StringIO(s) i = ini.INIConfig(f) self.assertEqual(unicode(i), s) self.assertEqual(type(i.foo.bar), unicode) if strable: self.assertEqual(str(i), str(s)) else: self.assertRaises(UnicodeEncodeError, lambda: str(i)) return i def test_ascii(self): i = self.basic_tests(self.s1, strable=True) self.assertEqual(i.foo.bar, 'fish') def test_unicode_without_bom(self): i = self.basic_tests(self.s2[1:], strable=False) self.assertEqual(i.foo.bar, 'mammal') self.assertEqual(i.foo.baz, u'Marc-Andr\202') def test_unicode_with_bom(self): i = self.basic_tests(self.s2, strable=False) self.assertEqual(i.foo.bar, 'mammal') self.assertEqual(i.foo.baz, u'Marc-Andr\202') class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ unittest.makeSuite(test_unicode, 'test'), ]) iniparse-0.4/tests/__init__.py0000664000076400007640000000124311370356146015444 0ustar paramparamimport unittest, doctest import test_ini import test_misc import test_fuzz import test_compat import test_unicode import test_tidy import test_multiprocessing from iniparse import config from iniparse import ini class suite(unittest.TestSuite): def __init__(self): unittest.TestSuite.__init__(self, [ doctest.DocTestSuite(config), doctest.DocTestSuite(ini), test_ini.suite(), test_misc.suite(), test_fuzz.suite(), test_compat.suite(), test_unicode.suite(), test_tidy.suite(), test_multiprocessing.suite(), ]) iniparse-0.4/README0000664000076400007640000000230511152012455013037 0ustar paramparamIntroduction to iniparse iniparse is a INI parser for Python which is: * Compatible with ConfigParser: Backward compatible implementations of ConfigParser, RawConfigParser, and SafeConfigParser are included that are API-compatible with the Python standard library. * Preserves structure of INI files: Order of sections & options, indentation, comments, and blank lines are preserved as far as possible when data is updated. * More convenient: Values can be accessed using dotted notation (cfg.user.name), or using container syntax (cfg['user']['name']). It is very useful for config files that are updated both by users and by programs, since it is very disorienting for a user to have her config file completely rearranged whenever a program changes it. iniparse also allows making the order of entries in a config file significant, which is desirable in applications like image galleries. Website: http://code.google.com/p/iniparse/ Mailing List: iniparse-discuss@googlegroups.com Copyright (c) 2001-2008 Python Software Foundation Copyright (c) 2004-2009 Paramjit Oberoi Copyright (c) 2007 Tim Lauridsen All Rights Reserved. See LICENSE-PSF & LICENSE for details. iniparse-0.4/Changelog0000664000076400007640000000531211404731225013775 0ustar paramparam2010-06-12 * released 0.4 2010-05-08 * reorganize code to remove circular imports * auto-create config settings when they are accessed via square bracket syntax (for example, cfg[x][y]). Previously this raised KeyError. 2010-05-07 * Ensure that __special__ method lookups don't cause config attributes to be added. 2010-05-06 * Fix problems with pickling INIConfig objects. This also fixes multiprocessing problems. 2010-04-17 * released 0.3.2 2010-02-26 * added tidy() and change_comment_syntax() based on patch by Paul Lambert. * added ConfigParser exceptions and constants to the iniparse module 2009-03-23 * Added debian package support contributed by Gavin Kinsey 2009-03-02 * released 0.3.1 2009-03-01 * Fix empty line bugs introduced by the compatibility hack 2009-02-27 * released 0.3.0 2009-02-22 * Make INIConfig objects pickle-able * Fix the defaults() method * Replicate ConfigParser behavior regarding empty lines in multi-line values - empty lines are stripped on parsing, but preserved when the value is explicitly set. 2009-02-10 * Skip DEFAULT section when listing sections (issue 8) 2009-02-03 * Bugfixes for continuation line behavior, based on patch by sidnei.da.silva - (1) preserve empty lines in multi-line values, and (2) fix assignment to multi-line values 2008-12-06 * released 0.2.4 2008-12-06 * upgraded test_compat to the tests included with python-2.6.1 * fixed compatibility warnings generated by the '-3' option * Python's ConfigParser has acquired the ability to use custom dict types - presumably to support user-controlled ordering. This feature does not seem to make sense in the context of iniparse, so I'm not planning on adding support for it (unless I hear otherwise from users). 2008-12-05 * add hack to fix unicode support on python-2.4.x * use the built-in set() type instead of the pre-2.4 sets module 2008-04-06 * support files opened in unicode mode * handle BOMs in unicode mode 2008-03-30 * cleanup ConfigNamespace docs * rename readfp() to _readfp() * replace import_namespace() with an update_config() utility function. 2007-12-11 * released 0.2.3 2007-12-09 * preserve whitespace around '=' and ':' 2007-10-02 * handle empty files 2007-09-24 * released 0.2.2 2007-09-09 * allow multi-line values to span comments and blank lines 2007-08-07 * released version 0.2.1 2007-07-28 * only use .readline() on file objects for better ConfigParser compatibility * spec file fixes for fedora 2007-07-19 * released version 0.2 2007-07-10 * renamed project to iniparse * renamed classes to reflect new project name * made names more friendly and PEP 8 compliant 2007-07-10 * imported into google-code 2004-10-03 * packaged, added licences, etc. * released version 0.1 iniparse-0.4/python-iniparse.spec0000664000076400007640000000502011404731365016171 0ustar paramparam%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} Name: python-iniparse Version: 0.4 Release: 1%{?dist} Summary: Python Module for Accessing and Modifying Configuration Data in INI files Group: Development/Libraries License: MIT URL: http://code.google.com/p/iniparse/ Source0: http://iniparse.googlecode.com/files/iniparse-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: python-setuptools BuildArch: noarch %description iniparse is an INI parser for Python which is API compatible with the standard library's ConfigParser, preserves structure of INI files (order of sections & options, indentation, comments, and blank lines are preserved when data is updated), and is more convenient to use. %prep %setup -q -n iniparse-%{version} %build %{__python} setup.py build %install rm -rf $RPM_BUILD_ROOT %{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT # fixes chmod 644 $RPM_BUILD_ROOT//usr/share/doc/iniparse-%{version}/index.html mv $RPM_BUILD_ROOT/usr/share/doc/iniparse-%{version} $RPM_BUILD_ROOT/usr/share/doc/python-iniparse-%{version} %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %doc %{_docdir}/python-iniparse-%{version}/* %{python_sitelib}/iniparse %{python_sitelib}/iniparse-%{version}-py*.egg-info %changelog * Sat Jun 12 2010 Paramjit Oberoi - 0.4-1 - Release 0.4 * Sat Apr 17 2010 Paramjit Oberoi - 0.3.2-1 - Release 0.3.2 * Mon Mar 2 2009 Paramjit Oberoi - 0.3.1-1 - Release 0.3.1 * Fri Feb 27 2009 Paramjit Oberoi - 0.3.0-1 - Release 0.3.0 * Tue Dec 6 2008 Paramjit Oberoi - 0.2.4-1 - Release 0.2.4 - added egg-info file to %%files * Tue Dec 11 2007 Paramjit Oberoi - 0.2.3-1 - Release 0.2.3 * Tue Sep 24 2007 Paramjit Oberoi - 0.2.2-1 - Release 0.2.2 * Tue Aug 7 2007 Paramjit Oberoi - 0.2.1-1 - Release 0.2.1 * Fri Jul 27 2007 Tim Lauridsen - 0.2-3 - relocated doc to %{_docdir}/python-iniparse-%{version} * Thu Jul 26 2007 Tim Lauridsen - 0.2-2 - changed name from iniparse to python-iniparse * Tue Jul 17 2007 Tim Lauridsen - 0.2-1 - Release 0.2 - Added html/* to %%doc * Fri Jul 13 2007 Tim Lauridsen - 0.1-1 - Initial build. iniparse-0.4/setup.py0000664000076400007640000000304711404731254013702 0ustar paramparam#!/usr/bin/env python from distutils.core import setup VERSION = '0.4' setup(name ='iniparse', version = VERSION, description = 'Accessing and Modifying INI files', author = 'Paramjit Oberoi', author_email = 'param@cs.wisc.edu', url = 'http://code.google.com/p/iniparse/', license = 'MIT', long_description = '''\ iniparse is an INI parser for Python which is API compatible with the standard library's ConfigParser, preserves structure of INI files (order of sections & options, indentation, comments, and blank lines are preserved when data is updated), and is more convenient to use.''', classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: Python Software Foundation License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages = ['iniparse'], data_files = [ ('share/doc/iniparse-%s' % VERSION, ['README', 'LICENSE-PSF', 'LICENSE', 'Changelog', 'html/index.html', 'html/style.css', ]), ], ) iniparse-0.4/LICENSE-PSF0000664000076400007640000003146610644737342013642 0ustar paramparamA. HISTORY OF THE SOFTWARE ========================== Python was created in the early 1990s by Guido van Rossum at Stichting Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands as a successor of a language called ABC. Guido remains Python's principal author, although it includes many contributions from others. In 1995, Guido continued his work on Python at the Corporation for National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) in Reston, Virginia where he released several versions of the software. In May 2000, Guido and the Python core development team moved to BeOpen.com to form the BeOpen PythonLabs team. In October of the same year, the PythonLabs team moved to Digital Creations (now Zope Corporation, see http://www.zope.com). In 2001, the Python Software Foundation (PSF, see http://www.python.org/psf/) was formed, a non-profit organization created specifically to own Python-related Intellectual Property. Zope Corporation is a sponsoring member of the PSF. All Python releases are Open Source (see http://www.opensource.org for the Open Source Definition). Historically, most, but not all, Python releases have also been GPL-compatible; the table below summarizes the various releases. Release Derived Year Owner GPL- from compatible? (1) 0.9.0 thru 1.2 1991-1995 CWI yes 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes 1.6 1.5.2 2000 CNRI no 2.0 1.6 2000 BeOpen.com no 1.6.1 1.6 2001 CNRI yes (2) 2.1 2.0+1.6.1 2001 PSF no 2.0.1 2.0+1.6.1 2001 PSF yes 2.1.1 2.1+2.0.1 2001 PSF yes 2.2 2.1.1 2001 PSF yes 2.1.2 2.1.1 2002 PSF yes 2.1.3 2.1.2 2002 PSF yes 2.2.1 2.2 2002 PSF yes 2.2.2 2.2.1 2002 PSF yes 2.2.3 2.2.2 2003 PSF yes 2.3 2.2.2 2002-2003 PSF yes 2.3.1 2.3 2002-2003 PSF yes 2.3.2 2.3.1 2002-2003 PSF yes 2.3.3 2.3.2 2002-2003 PSF yes Footnotes: (1) GPL-compatible doesn't mean that we're distributing Python under the GPL. All Python licenses, unlike the GPL, let you distribute a modified version without making your changes open source. The GPL-compatible licenses make it possible to combine Python with other software that is released under the GPL; the others don't. (2) According to Richard Stallman, 1.6.1 is not GPL-compatible, because its license has a choice of law clause. According to CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 is "not incompatible" with the GPL. Thanks to the many outside volunteers who have worked under Guido's direction to make these releases possible. B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON =============================================================== PSF LICENSE AGREEMENT FOR PYTHON 2.3 ------------------------------------ 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 2.3 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 2.3 alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003 Python Software Foundation; All Rights Reserved" are retained in Python 2.3 alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 2.3 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 2.3. 4. PSF is making Python 2.3 available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.3 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 2.3 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.3, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python 2.3, Licensee agrees to be bound by the terms and conditions of this License Agreement. BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 ------------------------------------------- BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the Individual or Organization ("Licensee") accessing and otherwise using this software in source or binary form and its associated documentation ("the Software"). 2. Subject to the terms and conditions of this BeOpen Python License Agreement, BeOpen hereby grants Licensee a non-exclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use the Software alone or in any derivative version, provided, however, that the BeOpen Python License is retained in the Software, alone or in any derivative version prepared by Licensee. 3. BeOpen is making the Software available to Licensee on an "AS IS" basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 5. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 6. This License Agreement shall be governed by and interpreted in all respects by the law of the State of California, excluding conflict of law provisions. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between BeOpen and Licensee. This License Agreement does not grant permission to use BeOpen trademarks or trade names in a trademark sense to endorse or promote products or services of Licensee, or any third party. As an exception, the "BeOpen Python" logos available at http://www.pythonlabs.com/logos.html may be used according to the permissions granted on that web page. 7. By copying, installing or otherwise using the software, Licensee agrees to be bound by the terms and conditions of this License Agreement. CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 --------------------------------------- 1. This LICENSE AGREEMENT is between the Corporation for National Research Initiatives, having an office at 1895 Preston White Drive, Reston, VA 20191 ("CNRI"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 1.6.1 software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, CNRI hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 1.6.1 alone or in any derivative version, provided, however, that CNRI's License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) 1995-2001 Corporation for National Research Initiatives; All Rights Reserved" are retained in Python 1.6.1 alone or in any derivative version prepared by Licensee. Alternately, in lieu of CNRI's License Agreement, Licensee may substitute the following text (omitting the quotes): "Python 1.6.1 is made available subject to the terms and conditions in CNRI's License Agreement. This Agreement together with Python 1.6.1 may be located on the Internet using the following unique, persistent identifier (known as a handle): 1895.22/1013. This Agreement may also be obtained from a proxy server on the Internet using the following URL: http://hdl.handle.net/1895.22/1013". 3. In the event Licensee prepares a derivative work that is based on or incorporates Python 1.6.1 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 1.6.1. 4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. This License Agreement shall be governed by the federal intellectual property law of the United States, including without limitation the federal copyright law, and, to the extent such U.S. federal law does not apply, by the law of the Commonwealth of Virginia, excluding Virginia's conflict of law provisions. Notwithstanding the foregoing, with regard to derivative works based on Python 1.6.1 that incorporate non-separable material that was previously distributed under the GNU General Public License (GPL), the law of the Commonwealth of Virginia shall govern this License Agreement only as to issues arising under or with respect to Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between CNRI and Licensee. This License Agreement does not grant permission to use CNRI trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By clicking on the "ACCEPT" button where indicated, or by copying, installing or otherwise using Python 1.6.1, Licensee agrees to be bound by the terms and conditions of this License Agreement. ACCEPT CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 -------------------------------------------------- Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, The Netherlands. All rights reserved. Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. iniparse-0.4/Makefile0000664000076400007640000000105711362376334013636 0ustar paramparamPKGNAME = iniparse SPECVERSION=$(shell awk '/Version:/ { print $$2 }' python-${PKGNAME}.spec) SETUPVERSION=$(shell awk -F\' '/VERSION =/ { print $$2 }' setup.py) clean: rm -f *.pyc *.pyo *~ *.bak rm -f iniparse/*.pyc iniparse/*.pyo iniparse/*~ iniparse/*.bak rm -f tests/*.pyc tests/*.pyo tests/*~ tests/*.bak rm -f *.tar.gz MANIFEST archive: python setup.py sdist -d . @echo "The archive is in ${PKGNAME}-$(SETUPVERSION).tar.gz" rpmbuild: archive rpmbuild -ta ${PKGNAME}-$(SPECVERSION).tar.gz pychecker: pychecker --stdlib iniparse/*py tests/*py iniparse-0.4/LICENSE0000664000076400007640000000306411116571360013174 0ustar paramparamCopyright (c) 2001, 2002, 2003 Python Software Foundation Copyright (c) 2004-2008 Paramjit Oberoi Copyright (c) 2007 Tim Lauridsen All Rights Reserved. iniparse/compat.py and tests/test_compat.py contain code derived from lib/python-2.3/ConfigParser.py and lib/python-2.3/test/test_cfgparse.py respectively. Other code may contain small snippets from those two files as well. The Python license (LICENSE-PSF) applies to that code. --------------------------------------------------------------------------- The MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. iniparse-0.4/runtests.py0000775000076400007640000000042411116430037014423 0ustar paramparam#!/usr/bin/env python import sys import tests if __name__ == '__main__': if len(sys.argv) == 2 and sys.argv[1] == '-g': import unittestgui unittestgui.main('tests.suite') else: import unittest unittest.main(defaultTest='tests.suite') iniparse-0.4/PKG-INFO0000664000076400007640000000207311404735056013267 0ustar paramparamMetadata-Version: 1.0 Name: iniparse Version: 0.4 Summary: Accessing and Modifying INI files Home-page: http://code.google.com/p/iniparse/ Author: Paramjit Oberoi Author-email: param@cs.wisc.edu License: MIT Description: iniparse is an INI parser for Python which is API compatible with the standard library's ConfigParser, preserves structure of INI files (order of sections & options, indentation, comments, and blank lines are preserved when data is updated), and is more convenient to use. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.4 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Topic :: Software Development :: Libraries :: Python Modules iniparse-0.4/html/0000775000076400007640000000000011404735056013134 5ustar paramparaminiparse-0.4/html/style.css0000664000076400007640000000326010646507740015013 0ustar paramparam/* All colors are at the beginning */ /* Colors are based on a "My Yahoo" theme */ body, #title, #title A:link, #title A:visited, #title a:hover { color: black; background: #a0b8c8; } .box { color: black; background: #ffffcc; } .boxtitle { color: black; background: #dcdcdc; } #menu { color: black; background: #dcdcdc; } /* Colors finished; now for the layout */ body { margin-left: 10%; margin-right: 10%; font-family: sans-serif; font-size: 0.8em; } #title { text-align: center; } #title h1 { margin-bottom: .3em; padding-bottom: .2em; font-size: 1.8em; letter-spacing: .2em; border-bottom: 1px solid; } #title p { letter-spacing: .2em; margin-top: .1em; margin-bottom: .1em; } #title A:link, #title A:visited { text-decoration: none; } #title A:hover { text-decoration: underline; } #menu { margin-top: 2em; padding: 0.3em; text-align: center; font-weight: bold; border: 1px solid; } #footer { margin-top: 1em; padding-right: 0.2em; padding-left: 0.2em; font-style: italic; text-align: center; } #footer p { margin: 0.5em; } .box { margin-top: 2em; padding: 0em; border: 1px solid; } .boxtitle { margin: 0; padding-bottom: 0.1em; padding-top: 0.2em; text-align: center; font-size: 1.3em; font-weight: bold; border-bottom: 1px solid; } .boxitem { margin: 1em; } ul { margin-top: 0.2em; margin-bottom: 0; } li { margin-bottom: 0.1em; } .leftfloat { width: 50%; float: left; } .rightfloat { width: 50%; float: right; } .clear { clear: both; width: 0; height: 0; } iniparse-0.4/html/index.html0000775000076400007640000001162211116570114015126 0ustar paramparam iniparse

iniparse

Better INI parser for Python

Introduction

iniparse is a INI parser for Python which is:

  • Compatible with ConfigParser: Backward compatible implementations of ConfigParser, RawConfigParser, and SafeConfigParser are included that are API-compatible with the Python standard library.
  • Preserves structure of INI files: Order of sections & options, indentation, comments, and blank lines are preserved as far as possible when data is updated.
  • More convenient: Values can be accessed using dotted notation (cfg.user.name), or using container syntax (cfg['user']['name']).
  • Extensible: It is possible to add other configuration formats, and to convert between different formats (as long as the data models are compatible).

It is very useful for config files that are updated both by users and by programs, since it is very disorienting for a user to have her config file completely rearranged whenever a program changes it. iniparse also allows making the order of entries in a config file significant, which is desirable in applications like image galleries.

Website: http://code.google.com/p/iniparse/

Examples
New API:
  • Open an INI file:
    >>> from iniparse import INIConfig
    >>> cfg = INIConfig(file('options.ini'))
    
  • Access/Modify data:
    >>> print cfg.playlist.expand_playlist
    True
    >>> print cfg.ui.width
    150
    >>> cfg.ui.width = 200
    >>> print cfg['ui']['width']
    200
    
  • Print data:
    >>> print cfg
    [playlist]
    expand_playlist = True
    
    [ui]
    display_clock = True
    display_qlength = True
    width = 200
    
Backward Compatible API:
  • The entire ConfigParser API is supported. This is just a brief example:
    >>> from iniparse import ConfigParser
    >>> cfgpr = ConfigParser()
    >>> cfgpr.read('options.ini')
    >>> print cfgpr.get('ui', 'width')
    150
    >>> cfgpr.set('ui', 'width', 175)
    
  • The new API can also be accessed via backward-compatible objects:
    >>> print cfgpr.data.playlist.expand_playlist
    True
    >>> cfgpr.data.ui.width = 200
    >>> print cfgpr.data.ui.width
    200
    
A non-INI example:
  • A simple dotted format is also implemented:
    >>> from iniparse import BasicConfig
    >>> n = BasicConfig()
    >>> n.x = 7
    >>> n.name.first = 'paramjit'
    >>> n.name.last = 'oberoi'
    >>> print n.x
    7
    >>> print n.name.first
    'paramjit'
    >>> print n
    name.first = paramjit
    name.last = oberoi
    x = 7
    
  • Convert to INI:
    >>> from iniparse import INIConfig
    >>> i = INIConfig()
    >>> del n.x               # since INI doesn't support top-level values
    >>> i.import_config(n)
    >>> print i
    [name]
    first = paramjit
    last = oberoi