pax_global_header00006660000000000000000000000064137415501310014512gustar00rootroot0000000000000052 comment=4e20f6bcdef72a2ffef1f811a4540778da60733b iniconfig-1.1.1/000077500000000000000000000000001374155013100134575ustar00rootroot00000000000000iniconfig-1.1.1/.gitignore000066400000000000000000000000771374155013100154530ustar00rootroot00000000000000*.egg-info *.pyc .cache/ .eggs/ build/ dist/ __pycache__ .tox/ iniconfig-1.1.1/.hgignore000066400000000000000000000002751374155013100152660ustar00rootroot00000000000000 # 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 iniconfig-1.1.1/.landscape.yml000066400000000000000000000000571374155013100162140ustar00rootroot00000000000000pep8: full: true python-targets: - 2 - 3 iniconfig-1.1.1/.travis.yml000066400000000000000000000017101374155013100155670ustar00rootroot00000000000000language: 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 iniconfig-1.1.1/CHANGELOG000066400000000000000000000010251374155013100146670ustar00rootroot000000000000001.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 iniconfig-1.1.1/LICENSE000066400000000000000000000020451374155013100144650ustar00rootroot00000000000000 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. iniconfig-1.1.1/MANIFEST.in000066400000000000000000000001571374155013100152200ustar00rootroot00000000000000include LICENSE include example.ini include tox.ini include src/iniconfig/py.typed recursive-include src *.pyi iniconfig-1.1.1/README.txt000066400000000000000000000027601374155013100151620ustar00rootroot00000000000000iniconfig: 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 iniconfig-1.1.1/example.ini000066400000000000000000000002161374155013100156120ustar00rootroot00000000000000 # content of example.ini [section1] # comment name1=value1 # comment name1b=value1,value2 # comment [section2] name2= line1 line2 iniconfig-1.1.1/pyproject.toml000066400000000000000000000001451374155013100163730ustar00rootroot00000000000000[build-system] requires = ["setuptools>=41.2.0", "wheel", "setuptools_scm>3"] [tool.setuptools_scm]iniconfig-1.1.1/setup.cfg000066400000000000000000000000321374155013100152730ustar00rootroot00000000000000[bdist_wheel] universal=1 iniconfig-1.1.1/setup.py000066400000000000000000000027101374155013100151710ustar00rootroot00000000000000""" 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() iniconfig-1.1.1/src/000077500000000000000000000000001374155013100142465ustar00rootroot00000000000000iniconfig-1.1.1/src/iniconfig/000077500000000000000000000000001374155013100162135ustar00rootroot00000000000000iniconfig-1.1.1/src/iniconfig/__init__.py000066400000000000000000000121301374155013100203210ustar00rootroot00000000000000""" 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 iniconfig-1.1.1/src/iniconfig/__init__.pyi000066400000000000000000000022651374155013100205020ustar00rootroot00000000000000from 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]: ... iniconfig-1.1.1/src/iniconfig/py.typed000066400000000000000000000000001374155013100177000ustar00rootroot00000000000000iniconfig-1.1.1/testing/000077500000000000000000000000001374155013100151345ustar00rootroot00000000000000iniconfig-1.1.1/testing/conftest.py000066400000000000000000000000431374155013100173300ustar00rootroot00000000000000 option_doctestglob = "README.txt" iniconfig-1.1.1/testing/test_iniconfig.py000066400000000000000000000170711374155013100205200ustar00rootroot00000000000000import 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) iniconfig-1.1.1/tox.ini000066400000000000000000000001771374155013100147770ustar00rootroot00000000000000[tox] envlist=py27,py26,py33,py34,py35 [testenv] commands= pytest {posargs} deps= pytest [pytest] testpaths= testing