Imported Upstream version 1.1.1 upstream upstream/1.1.1
authorDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 07:27:16 +0000 (16:27 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Mon, 5 Apr 2021 07:27:16 +0000 (16:27 +0900)
24 files changed:
.gitignore [new file with mode: 0644]
.hgignore [new file with mode: 0644]
.landscape.yml [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
CHANGELOG [new file with mode: 0644]
LICENSE [new file with mode: 0644]
MANIFEST.in [new file with mode: 0644]
PKG-INFO [new file with mode: 0644]
README.txt [new file with mode: 0644]
example.ini [new file with mode: 0644]
pyproject.toml [new file with mode: 0644]
setup.cfg [new file with mode: 0644]
setup.py [new file with mode: 0644]
src/iniconfig.egg-info/PKG-INFO [new file with mode: 0644]
src/iniconfig.egg-info/SOURCES.txt [new file with mode: 0644]
src/iniconfig.egg-info/dependency_links.txt [new file with mode: 0644]
src/iniconfig.egg-info/not-zip-safe [new file with mode: 0644]
src/iniconfig.egg-info/top_level.txt [new file with mode: 0644]
src/iniconfig/__init__.py [new file with mode: 0644]
src/iniconfig/__init__.pyi [new file with mode: 0644]
src/iniconfig/py.typed [new file with mode: 0644]
testing/conftest.py [new file with mode: 0644]
testing/test_iniconfig.py [new file with mode: 0644]
tox.ini [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..89e6234
--- /dev/null
@@ -0,0 +1,8 @@
+*.egg-info
+*.pyc
+.cache/
+.eggs/
+build/
+dist/
+__pycache__
+.tox/
diff --git a/.hgignore b/.hgignore
new file mode 100644 (file)
index 0000000..e8e91ab
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,16 @@
+
+# These lines are suggested according to the svn:ignore property
+# Feel free to enable them by uncommenting them
+syntax:glob
+*.pyc
+*.pyo
+*.swp
+*.html
+*.class
+
+.tox
+
+build
+dist
+*.egg-info
+
diff --git a/.landscape.yml b/.landscape.yml
new file mode 100644 (file)
index 0000000..5212dde
--- /dev/null
@@ -0,0 +1,5 @@
+pep8:
+  full: true
+python-targets:
+  - 2
+  - 3
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..e3fee06
--- /dev/null
@@ -0,0 +1,18 @@
+language: python
+python:
+- '2.7'
+- '3.4'
+- '3.5'
+- nightly
+- pypy
+install: pip install setuptools_scm tox
+script: tox -e py
+deploy:
+  provider: pypi
+  user: ronny
+  password:
+    secure: DsRVX99HA6+3JoXOVP/nPXeabJy2P73ws7Ager/e4rx3p3jS74bId09XsBU46bAT9ANmRWPR8y5DRi5Zlq0WQ2uXoR55wmsdu2KUegk6bDIS4Iop8DFxY8Kjou9s8RZbDTP27LfuYXKMO1rDW/xa6EhiotYRodekeZUz3P3MYjIi6rBV2Rz3vwmInpkKOti7AFwAsCGmCCK13irmPJEp5nwl3RgeKu2AGaolw9eypJXeNLUcNDVQ88ZUUXQCkwgq7a1BkK6NMeQLMrWAE1bD3amCbVXHCR9TaVx1ZH1dnha5Jcfj3gEFucTmInWWes5u9rypvsCkSxKtSqdiUA7BMJq7XykV7nGNplGLm2sq4+KSYlf3gZXg4XNXQkNOi4EBtRvathfFziD2SZgdtjiQX2neh0dMjf9czc/uCYkKYCFLeozdw2oQQ+BsxhQfsmU2ILGCFHyFikmDbBqZOWfQE5TN3itQqV3TFK8sOHQ8iy3MDShs+lBk9AUwbCA5YbRh8hJKhgXyEsDpisC417Pj22+TbutTj7v3Rmpe/st4hoL740grWc3PSVUBaypG0RsoafSDZWnYnTC+0aakd6QEb5S9wnMkP94kijYjjF6yUInuT05wdbQv5XcSXqAdGzBqB5jNNdfwgWVCOlwGfjnvzKllhF3PmWPW/nfmQpGOQh4=
+  on:
+    tags: true
+    distributions: sdist bdist_wheel
+    repo: RonnyPfannschmidt/iniconfig
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644 (file)
index 0000000..679919f
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,32 @@
+1.1.1
+=========
+
+* fix version determination (thanks @florimondmanca)
+
+1.1.0
+=====
+
+- typing stubs (thanks @bluetech)
+- ci fixes
+
+1.0.1
+======
+
+pytest 5+ support
+
+1.0
+====
+
+- re-sync with pylib codebase
+
+0.2
+==================
+
+- added ability to ask "name in iniconfig", i.e. to check
+  if a section is contained.
+
+- fix bug in "name=value" parsing where value was "x=3"
+
+- allow for ': ' to delimit name=value pairs, so that e.g. .pypirc files
+  like http://docs.python.org/distutils/packageindex.html
+  can be successfully parsed
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..31ecdfb
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+
+  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.
+
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..06be514
--- /dev/null
@@ -0,0 +1,5 @@
+include LICENSE
+include example.ini
+include tox.ini
+include src/iniconfig/py.typed
+recursive-include src *.pyi
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644 (file)
index 0000000..f9a12c2
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,76 @@
+Metadata-Version: 1.1
+Name: iniconfig
+Version: 1.1.1
+Summary: iniconfig: brain-dead simple config-ini parsing
+Home-page: http://github.com/RonnyPfannschmidt/iniconfig
+Author: Ronny Pfannschmidt, Holger Krekel
+Author-email: opensource@ronnypfannschmidt.de, holger.krekel@gmail.com
+License: MIT License
+Description: iniconfig: brain-dead simple parsing of ini files
+        =======================================================
+        
+        iniconfig is a small and simple INI-file parser module
+        having a unique set of features:
+        
+        * tested against Python2.4 across to Python3.2, Jython, PyPy
+        * maintains order of sections and entries
+        * supports multi-line values with or without line-continuations
+        * supports "#" comments everywhere
+        * raises errors with proper line-numbers
+        * no bells and whistles like automatic substitutions
+        * iniconfig raises an Error if two sections have the same name.
+        
+        If you encounter issues or have feature wishes please report them to:
+        
+            http://github.com/RonnyPfannschmidt/iniconfig/issues
+        
+        Basic Example
+        ===================================
+        
+        If you have an ini file like this::
+        
+            # content of example.ini
+            [section1] # comment
+            name1=value1  # comment
+            name1b=value1,value2  # comment
+        
+            [section2]
+            name2=
+                line1
+                line2
+        
+        then you can do::
+        
+            >>> import iniconfig
+            >>> ini = iniconfig.IniConfig("example.ini")
+            >>> ini['section1']['name1'] # raises KeyError if not exists
+            'value1'
+            >>> ini.get('section1', 'name1b', [], lambda x: x.split(","))
+            ['value1', 'value2']
+            >>> ini.get('section1', 'notexist', [], lambda x: x.split(","))
+            []
+            >>> [x.name for x in list(ini)]
+            ['section1', 'section2']
+            >>> list(list(ini)[0].items())
+            [('name1', 'value1'), ('name1b', 'value1,value2')]
+            >>> 'section1' in ini
+            True
+            >>> 'inexistendsection' in ini
+            False
+        
+Platform: unix
+Platform: linux
+Platform: osx
+Platform: cygwin
+Platform: win32
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
diff --git a/README.txt b/README.txt
new file mode 100644 (file)
index 0000000..6bbad9a
--- /dev/null
@@ -0,0 +1,51 @@
+iniconfig: brain-dead simple parsing of ini files
+=======================================================
+
+iniconfig is a small and simple INI-file parser module
+having a unique set of features:
+
+* tested against Python2.4 across to Python3.2, Jython, PyPy
+* maintains order of sections and entries
+* supports multi-line values with or without line-continuations
+* supports "#" comments everywhere
+* raises errors with proper line-numbers
+* no bells and whistles like automatic substitutions
+* iniconfig raises an Error if two sections have the same name.
+
+If you encounter issues or have feature wishes please report them to:
+
+    http://github.com/RonnyPfannschmidt/iniconfig/issues
+
+Basic Example
+===================================
+
+If you have an ini file like this::
+
+    # content of example.ini
+    [section1] # comment
+    name1=value1  # comment
+    name1b=value1,value2  # comment
+
+    [section2]
+    name2=
+        line1
+        line2
+
+then you can do::
+
+    >>> import iniconfig
+    >>> ini = iniconfig.IniConfig("example.ini")
+    >>> ini['section1']['name1'] # raises KeyError if not exists
+    'value1'
+    >>> ini.get('section1', 'name1b', [], lambda x: x.split(","))
+    ['value1', 'value2']
+    >>> ini.get('section1', 'notexist', [], lambda x: x.split(","))
+    []
+    >>> [x.name for x in list(ini)]
+    ['section1', 'section2']
+    >>> list(list(ini)[0].items())
+    [('name1', 'value1'), ('name1b', 'value1,value2')]
+    >>> 'section1' in ini
+    True
+    >>> 'inexistendsection' in ini
+    False
diff --git a/example.ini b/example.ini
new file mode 100644 (file)
index 0000000..65481d2
--- /dev/null
@@ -0,0 +1,10 @@
+
+# content of example.ini
+[section1] # comment
+name1=value1  # comment
+name1b=value1,value2  # comment
+
+[section2]
+name2=
+    line1
+    line2
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644 (file)
index 0000000..b2725d8
--- /dev/null
@@ -0,0 +1,5 @@
+[build-system]
+requires = ["setuptools>=41.2.0", "wheel", "setuptools_scm>3"]
+
+
+[tool.setuptools_scm]
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
new file mode 100644 (file)
index 0000000..adf5ed7
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,7 @@
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..f46f321
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,46 @@
+"""
+iniconfig: brain-dead simple config-ini parsing.
+
+compatible CPython 2.3 through to CPython 3.2, Jython, PyPy
+
+(c) 2010 Ronny Pfannschmidt, Holger Krekel
+"""
+
+from setuptools import setup
+
+
+def main():
+    with open('README.txt') as fp:
+        readme = fp.read()
+    setup(
+        name='iniconfig',
+        packages=['iniconfig'],
+        package_dir={'': 'src'},
+        description='iniconfig: brain-dead simple config-ini parsing',
+        long_description=readme,
+        use_scm_version=True,
+        url='http://github.com/RonnyPfannschmidt/iniconfig',
+        license='MIT License',
+        platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
+        author='Ronny Pfannschmidt, Holger Krekel',
+        author_email=(
+            'opensource@ronnypfannschmidt.de, holger.krekel@gmail.com'),
+        classifiers=[
+            'Development Status :: 4 - Beta',
+            'Intended Audience :: Developers',
+            'License :: OSI Approved :: MIT License',
+            'Operating System :: POSIX',
+            'Operating System :: Microsoft :: Windows',
+            'Operating System :: MacOS :: MacOS X',
+            'Topic :: Software Development :: Libraries',
+            'Topic :: Utilities',
+            'Programming Language :: Python',
+            'Programming Language :: Python :: 2',
+            'Programming Language :: Python :: 3',
+        ],
+        include_package_data=True,
+        zip_safe=False,
+    )
+
+if __name__ == '__main__':
+    main()
diff --git a/src/iniconfig.egg-info/PKG-INFO b/src/iniconfig.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..f9a12c2
--- /dev/null
@@ -0,0 +1,76 @@
+Metadata-Version: 1.1
+Name: iniconfig
+Version: 1.1.1
+Summary: iniconfig: brain-dead simple config-ini parsing
+Home-page: http://github.com/RonnyPfannschmidt/iniconfig
+Author: Ronny Pfannschmidt, Holger Krekel
+Author-email: opensource@ronnypfannschmidt.de, holger.krekel@gmail.com
+License: MIT License
+Description: iniconfig: brain-dead simple parsing of ini files
+        =======================================================
+        
+        iniconfig is a small and simple INI-file parser module
+        having a unique set of features:
+        
+        * tested against Python2.4 across to Python3.2, Jython, PyPy
+        * maintains order of sections and entries
+        * supports multi-line values with or without line-continuations
+        * supports "#" comments everywhere
+        * raises errors with proper line-numbers
+        * no bells and whistles like automatic substitutions
+        * iniconfig raises an Error if two sections have the same name.
+        
+        If you encounter issues or have feature wishes please report them to:
+        
+            http://github.com/RonnyPfannschmidt/iniconfig/issues
+        
+        Basic Example
+        ===================================
+        
+        If you have an ini file like this::
+        
+            # content of example.ini
+            [section1] # comment
+            name1=value1  # comment
+            name1b=value1,value2  # comment
+        
+            [section2]
+            name2=
+                line1
+                line2
+        
+        then you can do::
+        
+            >>> import iniconfig
+            >>> ini = iniconfig.IniConfig("example.ini")
+            >>> ini['section1']['name1'] # raises KeyError if not exists
+            'value1'
+            >>> ini.get('section1', 'name1b', [], lambda x: x.split(","))
+            ['value1', 'value2']
+            >>> ini.get('section1', 'notexist', [], lambda x: x.split(","))
+            []
+            >>> [x.name for x in list(ini)]
+            ['section1', 'section2']
+            >>> list(list(ini)[0].items())
+            [('name1', 'value1'), ('name1b', 'value1,value2')]
+            >>> 'section1' in ini
+            True
+            >>> 'inexistendsection' in ini
+            False
+        
+Platform: unix
+Platform: linux
+Platform: osx
+Platform: cygwin
+Platform: win32
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
diff --git a/src/iniconfig.egg-info/SOURCES.txt b/src/iniconfig.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..62c1d1c
--- /dev/null
@@ -0,0 +1,23 @@
+.gitignore
+.hgignore
+.landscape.yml
+.travis.yml
+CHANGELOG
+LICENSE
+MANIFEST.in
+README.txt
+example.ini
+pyproject.toml
+setup.cfg
+setup.py
+tox.ini
+src/iniconfig/__init__.py
+src/iniconfig/__init__.pyi
+src/iniconfig/py.typed
+src/iniconfig.egg-info/PKG-INFO
+src/iniconfig.egg-info/SOURCES.txt
+src/iniconfig.egg-info/dependency_links.txt
+src/iniconfig.egg-info/not-zip-safe
+src/iniconfig.egg-info/top_level.txt
+testing/conftest.py
+testing/test_iniconfig.py
\ No newline at end of file
diff --git a/src/iniconfig.egg-info/dependency_links.txt b/src/iniconfig.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/iniconfig.egg-info/not-zip-safe b/src/iniconfig.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/src/iniconfig.egg-info/top_level.txt b/src/iniconfig.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..9dda536
--- /dev/null
@@ -0,0 +1 @@
+iniconfig
diff --git a/src/iniconfig/__init__.py b/src/iniconfig/__init__.py
new file mode 100644 (file)
index 0000000..6ad9eaf
--- /dev/null
@@ -0,0 +1,165 @@
+""" brain-dead simple parser for ini-style files.
+(C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed
+"""
+__all__ = ['IniConfig', 'ParseError']
+
+COMMENTCHARS = "#;"
+
+
+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 lineof(self, name):
+        return self.config.lineof(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)
+            try:
+                tokens = self._parse(iter(f))
+            finally:
+                f.close()
+        else:
+            tokens = self._parse(data.splitlines(True))
+
+        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, line_iter):
+        result = []
+        section = None
+        for lineno, line in enumerate(line_iter):
+            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):
+        # blank lines
+        if iscommentline(line):
+            line = ""
+        else:
+            line = line.rstrip()
+        if not line:
+            return None, None
+        # section
+        if line[0] == '[':
+            realline = line
+            for c in COMMENTCHARS:
+                line = line.split(c)[0].rstrip()
+            if line[-1] == "]":
+                return line[1:-1], None
+            return None, realline.strip()
+        # value
+        elif not line[0].isspace():
+            try:
+                name, value = line.split('=', 1)
+                if ":" in name:
+                    raise ValueError()
+            except ValueError:
+                try:
+                    name, value = line.split(":", 1)
+                except ValueError:
+                    self._raise(lineno, 'unexpected line: %r' % line)
+            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
+
+
+def iscommentline(line):
+    c = line.lstrip()[:1]
+    return c in COMMENTCHARS
diff --git a/src/iniconfig/__init__.pyi b/src/iniconfig/__init__.pyi
new file mode 100644 (file)
index 0000000..b6284be
--- /dev/null
@@ -0,0 +1,31 @@
+from typing import Callable, Iterator, Mapping, Optional, Tuple, TypeVar, Union
+from typing_extensions import Final
+
+_D = TypeVar('_D')
+_T = TypeVar('_T')
+
+class ParseError(Exception):
+    # Private __init__.
+    path: Final[str]
+    lineno: Final[int]
+    msg: Final[str]
+
+class SectionWrapper:
+    # Private __init__.
+    config: Final[IniConfig]
+    name: Final[str]
+    def __getitem__(self, key: str) -> str: ...
+    def __iter__(self) -> Iterator[str]: ...
+    def get(self, key: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ...
+    def items(self) -> Iterator[Tuple[str, str]]: ...
+    def lineof(self, name: str) -> Optional[int]: ...
+
+class IniConfig:
+    path: Final[str]
+    sections: Final[Mapping[str, Mapping[str, str]]]
+    def __init__(self, path: str, data: Optional[str] = None): ...
+    def __contains__(self, arg: str) -> bool: ...
+    def __getitem__(self, name: str) -> SectionWrapper: ...
+    def __iter__(self) -> Iterator[SectionWrapper]: ...
+    def get(self, section: str, name: str, default: _D = ..., convert: Callable[[str], _T] = ...) -> Union[_T, _D]: ...
+    def lineof(self, section: str, name: Optional[str] = ...) -> Optional[int]: ...
diff --git a/src/iniconfig/py.typed b/src/iniconfig/py.typed
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/testing/conftest.py b/testing/conftest.py
new file mode 100644 (file)
index 0000000..d265a29
--- /dev/null
@@ -0,0 +1,2 @@
+
+option_doctestglob = "README.txt"
diff --git a/testing/test_iniconfig.py b/testing/test_iniconfig.py
new file mode 100644 (file)
index 0000000..fe12421
--- /dev/null
@@ -0,0 +1,314 @@
+import py
+import pytest
+from iniconfig import IniConfig, ParseError, __all__ as ALL
+from iniconfig import iscommentline
+from textwrap import dedent
+
+
+check_tokens = {
+    'section': (
+        '[section]',
+        [(0, 'section', None, None)]
+    ),
+    'value': (
+        'value = 1',
+        [(0, None, 'value', '1')]
+    ),
+    'value in section': (
+        '[section]\nvalue=1',
+        [(0, 'section', None, None), (1, 'section', 'value', '1')]
+    ),
+    'value with continuation': (
+        'names =\n Alice\n Bob',
+        [(0, None, 'names', 'Alice\nBob')]
+    ),
+    'value with aligned continuation': (
+        'names = Alice\n'
+        '        Bob',
+        [(0, None, 'names', 'Alice\nBob')]
+    ),
+    'blank line': (
+        '[section]\n\nvalue=1',
+        [(0, 'section', None, None), (2, 'section', 'value', '1')]
+    ),
+    'comment': (
+        '# comment',
+        []
+    ),
+    'comment on value': (
+        'value = 1',
+        [(0, None, 'value', '1')]
+    ),
+
+    'comment on section': (
+        '[section] #comment',
+        [(0, 'section', None, None)]
+    ),
+    'comment2': (
+        '; comment',
+        []
+    ),
+
+    'comment2 on section': (
+        '[section] ;comment',
+        [(0, 'section', None, None)]
+    ),
+    'pseudo section syntax in value': (
+        'name = value []',
+        [(0, None, 'name', 'value []')]
+    ),
+    'assignment in value': (
+        'value = x = 3',
+        [(0, None, 'value', 'x = 3')]
+    ),
+    'use of colon for name-values': (
+        'name: y',
+        [(0, None, 'name', 'y')]
+    ),
+    'use of colon without space': (
+        'value:y=5',
+        [(0, None, 'value', 'y=5')]
+    ),
+    'equality gets precedence': (
+        'value=xyz:5',
+        [(0, None, 'value', 'xyz:5')]
+    ),
+
+}
+
+
+@pytest.fixture(params=sorted(check_tokens))
+def input_expected(request):
+    return check_tokens[request.param]
+
+
+@pytest.fixture
+def input(input_expected):
+    return input_expected[0]
+
+
+@pytest.fixture
+def expected(input_expected):
+    return input_expected[1]
+
+
+def parse(input):
+    # only for testing purposes - _parse() does not use state except path
+    ini = object.__new__(IniConfig)
+    ini.path = "sample"
+    return ini._parse(input.splitlines(True))
+
+
+def parse_a_error(input):
+    return py.test.raises(ParseError, parse, input)
+
+
+def test_tokenize(input, expected):
+    parsed = parse(input)
+    assert parsed == expected
+
+
+def test_parse_empty():
+    parsed = parse("")
+    assert not parsed
+    ini = IniConfig("sample", "")
+    assert not ini.sections
+
+
+def test_ParseError():
+    e = ParseError("filename", 0, "hello")
+    assert str(e) == "filename:1: hello"
+
+
+def test_continuation_needs_perceeding_token():
+    excinfo = parse_a_error(' Foo')
+    assert excinfo.value.lineno == 0
+
+
+def test_continuation_cant_be_after_section():
+    excinfo = parse_a_error('[section]\n Foo')
+    assert excinfo.value.lineno == 1
+
+
+def test_section_cant_be_empty():
+    excinfo = parse_a_error('[]')
+    assert excinfo.value.lineno == 0
+
+
+@py.test.mark.parametrize('line', [
+    '!!',
+    ])
+def test_error_on_weird_lines(line):
+    parse_a_error(line)
+
+
+def test_iniconfig_from_file(tmpdir):
+    path = tmpdir/'test.txt'
+    path.write('[metadata]\nname=1')
+
+    config = IniConfig(path=path)
+    assert list(config.sections) == ['metadata']
+    config = IniConfig(path, "[diff]")
+    assert list(config.sections) == ['diff']
+    with pytest.raises(TypeError):
+        IniConfig(data=path.read())
+
+
+def test_iniconfig_section_first(tmpdir):
+    with pytest.raises(ParseError) as excinfo:
+        IniConfig("x", data='name=1')
+    assert excinfo.value.msg == "no section header defined"
+
+
+def test_iniconig_section_duplicate_fails():
+    with pytest.raises(ParseError) as excinfo:
+        IniConfig("x", data='[section]\n[section]')
+    assert 'duplicate section' in str(excinfo.value)
+
+
+def test_iniconfig_duplicate_key_fails():
+    with pytest.raises(ParseError) as excinfo:
+        IniConfig("x", data='[section]\nname = Alice\nname = bob')
+
+    assert 'duplicate name' in str(excinfo.value)
+
+
+def test_iniconfig_lineof():
+    config = IniConfig("x.ini", data=(
+        '[section]\n'
+        'value = 1\n'
+        '[section2]\n'
+        '# comment\n'
+        'value =2'
+    ))
+
+    assert config.lineof('missing') is None
+    assert config.lineof('section') == 1
+    assert config.lineof('section2') == 3
+    assert config.lineof('section', 'value') == 2
+    assert config.lineof('section2', 'value') == 5
+
+    assert config['section'].lineof('value') == 2
+    assert config['section2'].lineof('value') == 5
+
+
+def test_iniconfig_get_convert():
+    config = IniConfig("x", data='[section]\nint = 1\nfloat = 1.1')
+    assert config.get('section', 'int') == '1'
+    assert config.get('section', 'int', convert=int) == 1
+
+
+def test_iniconfig_get_missing():
+    config = IniConfig("x", data='[section]\nint = 1\nfloat = 1.1')
+    assert config.get('section', 'missing', default=1) == 1
+    assert config.get('section', 'missing') is None
+
+
+def test_section_get():
+    config = IniConfig("x", data='[section]\nvalue=1')
+    section = config['section']
+    assert section.get('value', convert=int) == 1
+    assert section.get('value', 1) == "1"
+    assert section.get('missing', 2) == 2
+
+
+def test_missing_section():
+    config = IniConfig("x", data='[section]\nvalue=1')
+    with pytest.raises(KeyError):
+            config["other"]
+
+
+def test_section_getitem():
+    config = IniConfig("x", data='[section]\nvalue=1')
+    assert config['section']['value'] == '1'
+    assert config['section']['value'] == '1'
+
+
+def test_section_iter():
+    config = IniConfig("x", data='[section]\nvalue=1')
+    names = list(config['section'])
+    assert names == ['value']
+    items = list(config['section'].items())
+    assert items == [('value', '1')]
+
+
+def test_config_iter():
+    config = IniConfig("x.ini", data=dedent('''
+          [section1]
+          value=1
+          [section2]
+          value=2
+    '''))
+    l = list(config)
+    assert len(l) == 2
+    assert l[0].name == 'section1'
+    assert l[0]['value'] == '1'
+    assert l[1].name == 'section2'
+    assert l[1]['value'] == '2'
+
+
+def test_config_contains():
+    config = IniConfig("x.ini", data=dedent('''
+          [section1]
+          value=1
+          [section2]
+          value=2
+    '''))
+    assert 'xyz' not in config
+    assert 'section1' in config
+    assert 'section2' in config
+
+
+def test_iter_file_order():
+    config = IniConfig("x.ini", data="""
+[section2] #cpython dict ordered before section
+value = 1
+value2 = 2 # dict ordered before value
+[section]
+a = 1
+b = 2
+""")
+    l = list(config)
+    secnames = [x.name for x in l]
+    assert secnames == ['section2', 'section']
+    assert list(config['section2']) == ['value', 'value2']
+    assert list(config['section']) == ['a', 'b']
+
+
+def test_example_pypirc():
+    config = IniConfig("pypirc", data=dedent('''
+        [distutils]
+        index-servers =
+            pypi
+            other
+
+        [pypi]
+        repository: <repository-url>
+        username: <username>
+        password: <password>
+
+        [other]
+        repository: http://example.com/pypi
+        username: <username>
+        password: <password>
+    '''))
+    distutils, pypi, other = list(config)
+    assert distutils["index-servers"] == "pypi\nother"
+    assert pypi['repository'] == '<repository-url>'
+    assert pypi['username'] == '<username>'
+    assert pypi['password'] == '<password>'
+    assert ['repository', 'username', 'password'] == list(other)
+
+
+def test_api_import():
+    assert ALL == ['IniConfig', 'ParseError']
+
+
+@pytest.mark.parametrize("line", [
+    "#qwe",
+    "  #qwe",
+    ";qwe",
+    " ;qwe",
+])
+def test_iscommentline_true(line):
+    assert iscommentline(line)
diff --git a/tox.ini b/tox.ini
new file mode 100644 (file)
index 0000000..298838b
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,14 @@
+[tox]
+envlist=py27,py26,py33,py34,py35
+
+
+[testenv]
+commands=
+  pytest {posargs}
+deps=
+  pytest
+
+
+[pytest]
+testpaths=
+  testing