Commits

holger krekel  committed a744d63

add iniconfig development version

  • Participants
  • Parent commits c598023
  • Branches trunk

Comments (0)

Files changed (5)

   to "pytools" package)
 - removed the old deprecated "py.magic" namespace
 - use apipkg-1.1
+- add py.iniconfig package for brain-dead easy ini-config file parsing
 
 Changes between 1.3.3 and 1.3.4
 ==================================================

File py/__init__.py

         'ForkedFunc'     : '._process.forkedfunc:ForkedFunc',
     },
 
+    iniconfig = {
+        'IniConfig'      : '._iniconfig:IniConfig',
+        'ParseError'     : '._iniconfig:ParseError',
+    },
+
     path = {
         '__doc__'        : '._path:__doc__',
         'svnwc'          : '._path.svnwc:SvnWCCommandPath',

File py/_iniconfig.py

+""" brain-dead simple parser for ini-style files.
+(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
+"""
+__version__ = "0.2.dev0"
+
+__all__ = ['IniConfig', 'ParseError']
+
+class ParseError(Exception):
+    def __init__(self, path, lineno, msg):
+        Exception.__init__(self, path, lineno, msg)
+        self.path = path
+        self.lineno = lineno
+        self.msg = msg
+
+    def __str__(self):
+        return "%s:%s: %s" %(self.path, self.lineno+1, self.msg)
+
+class SectionWrapper(object):
+    def __init__(self, config, name):
+        self.config = config
+        self.name = name
+
+    def get(self, key, default=None, convert=str):
+        return self.config.get(self.name, key, convert=convert, default=default)
+
+    def __getitem__(self, key):
+        return self.config.sections[self.name][key]
+
+    def __iter__(self):
+        section = self.config.sections.get(self.name, [])
+        def lineof(key):
+            return self.config.lineof(self.name, key)
+        for name in sorted(section, key=lineof):
+            yield name
+
+    def items(self):
+        for name in self:
+            yield name, self[name]
+
+
+class IniConfig(object):
+    def __init__(self, path, data=None):
+        self.path = str(path) # convenience
+        if data is None:
+            f = open(self.path)
+            data = f.read()
+            f.close()
+        tokens = self._parse(data)
+        
+        self._sources = {}
+        self.sections = {}
+
+        for lineno, section, name, value in tokens:
+            if section is None:
+                self._raise(lineno, 'no section header defined')
+            self._sources[section, name] = lineno
+            if name is None:
+                if section in self.sections:
+                    self._raise(lineno, 'duplicate section %r'%(section, ))
+                self.sections[section] = {}
+            else:
+                if name in self.sections[section]:
+                    self._raise(lineno, 'duplicate name %r'%(name, ))
+                self.sections[section][name] = value
+
+    def _raise(self, lineno, msg):
+        raise ParseError(self.path, lineno, msg)
+
+    def _parse(self, data):
+        result = []
+        section = None
+        for lineno, line in enumerate(data.splitlines(True)):
+            name, data = self._parseline(line, lineno)
+            # new value
+            if name is not None and data is not None:
+                result.append((lineno, section, name, data))
+            # new section
+            elif name is not None and data is None:
+                if not name:
+                    self._raise(lineno, 'empty section name')
+                section = name
+                result.append((lineno, section, None, None))
+            # continuation
+            elif name is None and data is not None:
+                if not result:
+                    self._raise(lineno, 'unexpected value continuation')
+                last = result.pop()
+                last_name, last_data = last[-2:]
+                if last_name is None:
+                    self._raise(lineno, 'unexpected value continuation')
+
+                if last_data:
+                    data = '%s\n%s' % (last_data, data)
+                result.append(last[:-1] + (data,))
+        return result
+
+    def _parseline(self, line, lineno):
+        # comments
+        line = line.split('#')[0].rstrip()
+        # blank lines
+        if not line:
+            return None, None
+        # section
+        if line[0] == '[' and line[-1] == ']':
+            return line[1:-1], None
+        # value
+        elif not line[0].isspace():
+            i = line.find(": ")
+            if i != -1:
+                name, value = line.split(": ", 1)
+            else:
+                try:
+                    name, value = line.split('=', 1)
+                except ValueError:
+                    self._raise(lineno, 'unexpected line: %s')
+            return name.strip(), value.strip()
+        # continuation
+        else:
+            return None, line.strip()
+
+    def lineof(self, section, name=None):
+        lineno = self._sources.get((section, name))
+        if lineno is not None:
+            return lineno + 1
+
+    def get(self, section, name, default=None, convert=str):
+        try:
+            return convert(self.sections[section][name])
+        except KeyError:
+            return default
+
+    def __getitem__(self, name):
+        if name not in self.sections:
+            raise KeyError(name)
+        return SectionWrapper(self, name)
+
+    def __iter__(self):
+        for name in sorted(self.sections, key=self.lineof):
+            yield SectionWrapper(self, name)
+
+    def __contains__(self, arg):
+        return arg in self.sections
 long_description = """
 pylib: cross-python development utils
 
+py.path.local: local path objects
+py.path.svnwc: local subversion WC paths
+py.iniconfig.IniConfig: Parser for IniConfig files
+py.io: io-capturing on filedescriptor or sys.* level
+
 Platforms: Linux, Win32, OSX
 
 Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy
 sdistsrc={distshare}/py-*
 
 [testenv]
-indexserver=http://pypi.testrun.org
 changedir=testing
 commands=
   py.test -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []