From f7659c8a48ce2923a015585c7acb886d56a703d4 Mon Sep 17 00:00:00 2001 From: DongHun Kwak Date: Mon, 5 Apr 2021 16:27:16 +0900 Subject: [PATCH] Imported Upstream version 1.1.1 --- .gitignore | 8 + .hgignore | 16 + .landscape.yml | 5 + .travis.yml | 18 ++ CHANGELOG | 32 ++ LICENSE | 19 ++ MANIFEST.in | 5 + PKG-INFO | 76 +++++ README.txt | 51 ++++ example.ini | 10 + pyproject.toml | 5 + setup.cfg | 7 + setup.py | 46 +++ src/iniconfig.egg-info/PKG-INFO | 76 +++++ src/iniconfig.egg-info/SOURCES.txt | 23 ++ src/iniconfig.egg-info/dependency_links.txt | 1 + src/iniconfig.egg-info/not-zip-safe | 1 + src/iniconfig.egg-info/top_level.txt | 1 + src/iniconfig/__init__.py | 165 ++++++++++ src/iniconfig/__init__.pyi | 31 ++ src/iniconfig/py.typed | 0 testing/conftest.py | 2 + testing/test_iniconfig.py | 314 ++++++++++++++++++++ tox.ini | 14 + 24 files changed, 926 insertions(+) create mode 100644 .gitignore create mode 100644 .hgignore create mode 100644 .landscape.yml create mode 100644 .travis.yml create mode 100644 CHANGELOG create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 PKG-INFO create mode 100644 README.txt create mode 100644 example.ini create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/iniconfig.egg-info/PKG-INFO create mode 100644 src/iniconfig.egg-info/SOURCES.txt create mode 100644 src/iniconfig.egg-info/dependency_links.txt create mode 100644 src/iniconfig.egg-info/not-zip-safe create mode 100644 src/iniconfig.egg-info/top_level.txt create mode 100644 src/iniconfig/__init__.py create mode 100644 src/iniconfig/__init__.pyi create mode 100644 src/iniconfig/py.typed create mode 100644 testing/conftest.py create mode 100644 testing/test_iniconfig.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89e6234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.egg-info +*.pyc +.cache/ +.eggs/ +build/ +dist/ +__pycache__ +.tox/ diff --git a/.hgignore b/.hgignore new file mode 100644 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 index 0000000..5212dde --- /dev/null +++ b/.landscape.yml @@ -0,0 +1,5 @@ +pep8: + full: true +python-targets: + - 2 + - 3 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e3fee06 --- /dev/null +++ b/.travis.yml @@ -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 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 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 index 0000000..06be514 --- /dev/null +++ b/MANIFEST.in @@ -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 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 index 0000000..6bbad9a --- /dev/null +++ b/README.txt @@ -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 index 0000000..65481d2 --- /dev/null +++ b/example.ini @@ -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 index 0000000..b2725d8 --- /dev/null +++ b/pyproject.toml @@ -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 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 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 index 0000000..f9a12c2 --- /dev/null +++ b/src/iniconfig.egg-info/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/src/iniconfig.egg-info/SOURCES.txt b/src/iniconfig.egg-info/SOURCES.txt new file mode 100644 index 0000000..62c1d1c --- /dev/null +++ b/src/iniconfig.egg-info/SOURCES.txt @@ -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 index 0000000..8b13789 --- /dev/null +++ b/src/iniconfig.egg-info/dependency_links.txt @@ -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 index 0000000..8b13789 --- /dev/null +++ b/src/iniconfig.egg-info/not-zip-safe @@ -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 index 0000000..9dda536 --- /dev/null +++ b/src/iniconfig.egg-info/top_level.txt @@ -0,0 +1 @@ +iniconfig diff --git a/src/iniconfig/__init__.py b/src/iniconfig/__init__.py new file mode 100644 index 0000000..6ad9eaf --- /dev/null +++ b/src/iniconfig/__init__.py @@ -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 index 0000000..b6284be --- /dev/null +++ b/src/iniconfig/__init__.pyi @@ -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 index 0000000..e69de29 diff --git a/testing/conftest.py b/testing/conftest.py new file mode 100644 index 0000000..d265a29 --- /dev/null +++ b/testing/conftest.py @@ -0,0 +1,2 @@ + +option_doctestglob = "README.txt" diff --git a/testing/test_iniconfig.py b/testing/test_iniconfig.py new file mode 100644 index 0000000..fe12421 --- /dev/null +++ b/testing/test_iniconfig.py @@ -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: + username: + password: + + [other] + repository: http://example.com/pypi + username: + password: + ''')) + distutils, pypi, other = list(config) + assert distutils["index-servers"] == "pypi\nother" + assert pypi['repository'] == '' + assert pypi['username'] == '' + assert pypi['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 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 -- 2.34.1