From: DongHun Kwak Date: Mon, 5 Apr 2021 07:27:16 +0000 (+0900) Subject: Imported Upstream version 1.1.1 X-Git-Tag: upstream/1.1.1^0 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f7659c8a48ce2923a015585c7acb886d56a703d4;p=platform%2Fupstream%2Fpython3-iniconfig.git Imported Upstream version 1.1.1 --- 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