pax_global_header00006660000000000000000000000064132567110650014520gustar00rootroot0000000000000052 comment=0f7a35e31f4e3f50547d7b2a8a886e4189a03761 inifile-0.4/000077500000000000000000000000001325671106500130025ustar00rootroot00000000000000inifile-0.4/PKG-INFO000066400000000000000000000013511325671106500140770ustar00rootroot00000000000000Metadata-Version: 1.0 Name: inifile Version: 0.4 Summary: A small INI library for Python. Home-page: https://pypi.python.org/pypi/inifile Author: Armin Ronacher Author-email: armin.ronacher@active-4.com License: BSD Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: any Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules inifile-0.4/debian/000077500000000000000000000000001325671106500142245ustar00rootroot00000000000000inifile-0.4/debian/changelog000066400000000000000000000002211325671106500160710ustar00rootroot00000000000000inifile (0.4-1) unstable; urgency=medium * Initial release (Closes: #894291) -- Hiro Mon, 26 Mar 2018 14:54:28 +0200 inifile-0.4/debian/changelog~000066400000000000000000000002221325671106500162700ustar00rootroot00000000000000inifile (0.4-1) unstable; urgency=medium * Initial release (Closes: #894291) -- Hiro Mon, 26 Mar 2018 14:54:28 +0200 inifile-0.4/debian/compat000066400000000000000000000000031325671106500154230ustar00rootroot0000000000000010 inifile-0.4/debian/control000066400000000000000000000016241325671106500156320ustar00rootroot00000000000000Source: inifile Section: python Priority: optional Maintainer: Hiro Build-Depends: debhelper (>= 10), dh-python, python-all, python-setuptools, python3-all, python3-setuptools Standards-Version: 4.1.2 Homepage: https://pypi.python.org/pypi/inifile X-Python-Version: >= 2.6 X-Python3-Version: >= 3.2 Vcs-Git: https://salsa.debian.org/debian/python-inifile.git Vcs-Browser: https://salsa.debian.org/debian/python-inifile.git/ Testsuite: autopkgtest-pkg-python Package: python-inifile Architecture: all Depends: ${python:Depends}, ${misc:Depends} Description: A small INI library for Python. (Python 2) . This package installs the library for Python 2. Package: python3-inifile Architecture: all Depends: ${python3:Depends}, ${misc:Depends} Description: A small INI library for Python. (Python 3) . This package installs the library for Python 3. inifile-0.4/debian/control~000066400000000000000000000016701325671106500160310ustar00rootroot00000000000000Source: inifile Section: python Priority: optional Maintainer: Hiro Build-Depends: debhelper (>= 10), dh-python, python-all, python-setuptools, python3-all, python3-setuptools Standards-Version: 4.1.2 Homepage: https://pypi.python.org/pypi/inifile X-Python-Version: >= 2.6 X-Python3-Version: >= 3.2 #Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/inifile.git #Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/inifile.git/ #Testsuite: autopkgtest-pkg-python Package: python-inifile Architecture: all Depends: ${python:Depends}, ${misc:Depends} Description: A small INI library for Python. (Python 2) . This package installs the library for Python 2. Package: python3-inifile Architecture: all Depends: ${python3:Depends}, ${misc:Depends} Description: A small INI library for Python. (Python 3) . This package installs the library for Python 3. inifile-0.4/debian/copyright000066400000000000000000000047501325671106500161650ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: inifile Source: https://pypi.python.org/pypi/inifile Files: * Copyright: 2015-2018 Armin Ronacher. License: BSD-3-Clause License: BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* Copyright: 2018 Hiro License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. . This package is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".inifile-0.4/debian/rules000077500000000000000000000010741325671106500153060ustar00rootroot00000000000000#!/usr/bin/make -f # See debhelper(7) (uncomment to enable) # output every command that modifies files on the build system. #export DH_VERBOSE = 1 export PYBUILD_NAME=inifile %: dh $@ --with python2,python3 --buildsystem=pybuild # If you need to rebuild the Sphinx documentation # Add spinxdoc to the dh --with line #override_dh_auto_build: # dh_auto_build # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bhtml docs/ build/html # HTML generator # PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs/ build/man # Manpage generator inifile-0.4/debian/source/000077500000000000000000000000001325671106500155245ustar00rootroot00000000000000inifile-0.4/debian/source/format000066400000000000000000000000141325671106500167320ustar00rootroot000000000000003.0 (quilt) inifile-0.4/debian/source/options000066400000000000000000000000521325671106500171370ustar00rootroot00000000000000extend-diff-ignore = "^[^/]*[.]egg-info/" inifile-0.4/debian/watch000066400000000000000000000001301325671106500152470ustar00rootroot00000000000000version=4 # PyPI https://pypi.python.org/inifile/inifile-(.+)\.(?:tar\.(?:gz|bz2|xz)) inifile-0.4/inifile.egg-info/000077500000000000000000000000001325671106500161135ustar00rootroot00000000000000inifile-0.4/inifile.egg-info/PKG-INFO000066400000000000000000000003421325671106500172070ustar00rootroot00000000000000Metadata-Version: 1.0 Name: inifile Version: 0.4 Summary: A small INI library for Python. Home-page: UNKNOWN Author: Armin Ronacher Author-email: armin.ronacher@active-4.com License: BSD Description: UNKNOWN Platform: UNKNOWN inifile-0.4/inifile.egg-info/SOURCES.txt000066400000000000000000000002311325671106500177730ustar00rootroot00000000000000inifile.py setup.cfg setup.py inifile.egg-info/PKG-INFO inifile.egg-info/SOURCES.txt inifile.egg-info/dependency_links.txt inifile.egg-info/top_level.txtinifile-0.4/inifile.egg-info/dependency_links.txt000066400000000000000000000000011325671106500221610ustar00rootroot00000000000000 inifile-0.4/inifile.egg-info/top_level.txt000066400000000000000000000000101325671106500206340ustar00rootroot00000000000000inifile inifile-0.4/inifile.py000066400000000000000000000474401325671106500150040ustar00rootroot00000000000000import os import sys import uuid import errno import tempfile from collections import MutableMapping, OrderedDict PY2 = sys.version_info[0] == 2 WIN = sys.platform.startswith('win') if PY2: text_type = unicode string_types = (str, unicode) integer_types = (int, long) iteritems = lambda x: x.iteritems() exec('def reraise(tp, value, tb=None):\n raise tp, value, tb') else: text_type = str string_types = (str,) integer_types = (int,) iteritems = lambda x: iter(x.items()) def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value def _posixify(name): return '-'.join(name.split()).lower() def iter_from_file(f, encoding=None): if encoding is None: encoding = 'utf-8-sig' return (x.decode(encoding, 'replace') for x in f) def get_app_dir(app_name, roaming=True, force_posix=False): r"""Returns the config folder for the application. The default behavior is to return whatever is most appropriate for the operating system. To give you an idea, for an app called ``"Foo Bar"``, something like the following folders could be returned: Mac OS X: ``~/Library/Application Support/Foo Bar`` Mac OS X (POSIX): ``~/.foo-bar`` Unix: ``~/.config/foo-bar`` Unix (POSIX): ``~/.foo-bar`` Win XP (roaming): ``C:\Documents and Settings\\Local Settings\Application Data\Foo Bar`` Win XP (not roaming): ``C:\Documents and Settings\\Application Data\Foo Bar`` Win 7 (roaming): ``C:\Users\\AppData\Roaming\Foo Bar`` Win 7 (not roaming): ``C:\Users\\AppData\Local\Foo Bar`` :param app_name: the application name. This should be properly capitalized and can contain whitespace. :param roaming: controls if the folder should be roaming or not on Windows. Has no affect otherwise. :param force_posix: if this is set to `True` then on any POSIX system the folder will be stored in the home folder with a leading dot instead of the XDG config home or darwin's application support folder. """ if WIN: key = roaming and 'APPDATA' or 'LOCALAPPDATA' folder = os.environ.get(key) if folder is None: folder = os.path.expanduser('~') return os.path.join(folder, app_name) if force_posix: return os.path.join(os.path.expanduser('~/.' + _posixify(app_name))) if sys.platform == 'darwin': return os.path.join(os.path.expanduser( '~/Library/Application Support'), app_name) return os.path.join( os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')), _posixify(app_name)) class Dialect(object): """This class allows customizing the dialect of the ini file. The default configuration is a compromise between the general Windows format and what's common on Unix systems. Example dialect config:: unix_dialect = Dialect( kv_sep=': ', quotes=("'",), comments=('#',), ) :param ns_sep: the namespace separator. This character is used to create hierarchical structures in sections and also placed between section and field. :param kv_sep: the separator to be placed between key and value. For parsing whitespace is automatically removed. :param quotes: a list of quote characters supported for strings. The leftmost one is automatically used for serialization, the others are supported for deserialization. :param true: strings that should be considered boolean true. :param false: strings that should be considered boolean false. :param comments: comment start markers. :param allow_escaping: enables or disables backslash escapes. :param linesep: a specific line separator to use other than the operating system's default. """ def __init__(self, ns_sep='.', kv_sep=' = ', quotes=('"', "'"), true=('true', 'yes', '1'), false=('false', 'no', '0'), comments=('#', ';'), allow_escaping=True, linesep=None): self.ns_sep = ns_sep self.kv_sep = kv_sep self.plain_kv_sep = kv_sep.strip() self.quotes = quotes self.true = true self.false = false self.comments = comments self.allow_escaping = allow_escaping self.linesep = linesep def get_actual_linesep(self): if self.linesep is None: return os.linesep return self.linesep def get_strippable_lineseps(self): if self.linesep is None or self.linesep in '\r\n': return '\r\n' return self.linesep def kv_serialize(self, key, val): if val is None: return None if self.quotes and val.split() != [val]: q = self.quotes[0] if self.allow_escaping: val = self.escape(val, q) val = '%s%s%s' % (q, val, q) return '%s%s%s' % (key, self.kv_sep, val) def escape(self, value, quote=None): value = value \ .replace('\\', '\\\\') \ .replace('\n', '\\n') \ .replace('\r', '\\r') \ .replace('\t', '\\t') for q in self.quotes: if q != quote: value = value.replace(q, '\\' + q) return value def unescape(self, value): value = value \ .replace('\\n', '\n') \ .replace('\\r', '\r') \ .replace('\\t', '\t') \ .replace('\\"', '"') for q in self.quotes: value = value.replace('\\' + q, q) return value def to_string(self, value): if value is True: return self.true[0] if value is False: return self.false[0] if isinstance(value, integer_types) or isinstance(value, float): return text_type(value) if not isinstance(value, string_types): raise TypeError('Cannot set value of this type') return text_type(value) def dict_from_iterable(self, iterable): """Builds a mapping of values out of an iterable of lines.""" mapping = OrderedDict() for token, _, data in self.tokenize(iterable): if token == 'KV': section, key, value = data mapping[self.ns_sep.join(section + (key,))] = value return mapping def tokenize(self, iterable): """Tokenizes an iterable of lines.""" section = () line_strip = self.get_strippable_lineseps() for line in iterable: line = line.rstrip(line_strip) if not line.strip(): yield 'EMPTY', line, None elif line.lstrip()[:1] in self.comments: yield 'COMMENT', line, None elif line[:1] == '[' and line[-1:] == ']': section = tuple(line[1:-1].strip().split(self.ns_sep)) yield 'SECTION', line, section elif self.plain_kv_sep in line: key, value = line.split(self.plain_kv_sep, 1) value = value.strip() if value[:1] in self.quotes and value[:1] == value[-1:]: value = value[1:-1] if self.allow_escaping: value = self.unescape(value) yield 'KV', line, (section, key.strip(), value) def update_tokens(self, old_tokens, changes): """Given the tokens returned from :meth:`tokenize` and a dictionary of new values (or `None` for values to be deleted) returns a new list of tokens that should be written back to a file. """ new_tokens = [] section_ends = {None: 0} pending_changes = dict(changes) for token, line, data in old_tokens: if token == 'KV': section, key, value = data k = self.ns_sep.join(section + (key,)) if k in pending_changes: value = pending_changes.pop(k) line = self.kv_serialize(key, value) data = (section, key, value) section_ends[self.ns_sep.join(section)] = len(new_tokens) elif token == 'SECTION': section_ends[self.ns_sep.join(data)] = len(new_tokens) new_tokens.append((token, line, data)) pending_by_sec = {} for key, value in sorted(pending_changes.items()): section, local_key = key.rsplit(self.ns_sep, 1) pending_by_sec.setdefault(section, []).append((local_key, value)) if pending_by_sec: section_ends_r = dict((v, k) for k, v in section_ends.items()) final_lines = [] for idx, (token, line, data) in enumerate(new_tokens): final_lines.append((token, line, data)) section = section_ends_r.get(idx) if section is not None and section in pending_by_sec: for local_key, value in pending_by_sec.pop(section): final_lines.append(( 'KV', self.kv_serialize(local_key, value), (section, local_key, value), )) for section, items in sorted(pending_by_sec.items()): if final_lines: final_lines.append(('EMPTY', u'', None)) final_lines.append(('SECTION', u'[%s]' % section, section)) for local_key, value in items: final_lines.append(( 'KV', self.kv_serialize(local_key, value), (section, local_key, value), )) new_tokens = final_lines return [x for x in new_tokens if x[1] is not None] default_dialect = Dialect() class IniData(MutableMapping): """This object behaves similar to a dictionary but it tracks modifications properly so that it can later write them back to an INI file with the help of the ini dialect, without destroying ordering or comments. This is rarely used directly, instead the :class:`IniFile` is normally used. This generally works similar to a dictionary and exposes the same basic API. """ def __init__(self, mapping=None, dialect=None): if dialect is None: dialect = default_dialect self.dialect = dialect if mapping is None: mapping = {} self._primary = mapping self._changes = {} @property def is_dirty(self): """This is true if the data was modified.""" return bool(self._changes) def get_updated_lines(self, line_iter=None): """Reconciles the updates in the ini data with the iterator of lines from the source file and returns a list of the new lines as they should be written into the file. """ return self.dialect.update_tokens(line_iter or (), self._changes) def discard(self): """Discards all local modifications in the ini data.""" self._changes.clear() def rollover(self): """Rolls all local modifications to the primary data. After this modifications are no longer tracked and `get_updated_lines` will not return them. """ self._primary = OrderedDict(self.iteritems()) self.discard() def to_dict(self): """Returns the current ini data as dictionary.""" return dict(self.iteritems()) def __len__(self): rv = len(self._primary) for key, value in iteritems(self._changes): if key in self._primary and value is not None: rv += 1 return rv def get(self, name, default=None): """Return a value for a key or return a default if the key does not exist. """ try: return self[name] except KeyError: return default def get_ascii(self, name, default=None): """This returns a value for a key for as long as the value fits into ASCII. Otherwise (or if the key does not exist) the default is returned. This is especially useful on Python 2 when working with some APIs that do not support unicode. """ try: rv = self[name] try: rv.encode('ascii') except UnicodeError: raise KeyError() if PY2: rv = str(rv) return rv except KeyError: return default def get_bool(self, name, default=False): """Returns a value as boolean. What constitutes as a valid boolean value depends on the dialect. """ try: rv = self[name].lower() if rv in self.dialect.true: return True if rv in self.dialect.false: return False raise KeyError() except KeyError: return default def get_int(self, name, default=None): """Returns a value as integer.""" try: return int(self[name]) except (ValueError, KeyError): return default def get_float(self, name, default=None): """Returns a value as float.""" try: return float(self[name]) except (ValueError, KeyError): return default def get_uuid(self, name, default=None): """Returns a value as uuid.""" try: return uuid.UUID(self[name]) except Exception: return default def itersections(self): """Iterates over the sections of the sections of the ini.""" seen = set() sep = self.dialect.ns_sep for key in self: if sep in key: section = key.rsplit(sep, 1)[0] if section not in seen: seen.add(section) yield section if PY2: def sections(self): """Returns a list of the sections in the ini file.""" return list(self.itersections()) else: sections = itersections def iteritems(self): for key in self._primary: try: yield key, self[key] except LookupError: pass for key in self._changes: if key not in self._primary: try: yield key, self[key] except LookupError: pass def iterkeys(self): for key, _ in self.iteritems(): yield key def itervalues(self): for _, value in self.iteritems(): yield value __iter__ = iterkeys if PY2: def keys(self): return list(self.iterkeys()) def values(self): return list(self.iterkeys()) def items(self): return list(self.iteritems()) else: keys = iterkeys values = itervalues items = iteritems def section_as_dict(self, section): rv = {} prefix = section + '.' for key, value in self.iteritems(): if key.startswith(prefix): rv[key[len(prefix):]] = value return rv def __getitem__(self, name): if name in self._changes: rv = self._changes[name] if rv is None: raise KeyError(name) return rv return self._primary[name] def __setitem__(self, name, value): self._changes[name] = self.dialect.to_string(value) def __delitem__(self, name): self._changes[name] = None class IniFile(IniData): """This class implements simplified read and write access to INI files in a way that preserves the original files as good as possible. Unlike a regular INI serializer it only overwrites the lines that were modified. Example usage:: ifile = IniFile('myfile.ini') ifile['ui.username'] = 'john_doe' ifile.save() The ini file exposes unicode strings but utility methods are provided for common type conversion. The default namespace separator is a dot (``.``). The format of the file can be configured by providing a custom :class:`Dialect` instance to the constructor. """ def __init__(self, filename, encoding=None, dialect=None): if dialect is None: dialect = default_dialect self.filename = os.path.abspath(filename) self.encoding = encoding try: with open(filename, 'rb') as f: mapping = dialect.dict_from_iterable( iter_from_file(f, self.encoding)) is_new = False except IOError as e: if e.errno != errno.ENOENT: raise is_new = True mapping = OrderedDict() IniData.__init__(self, mapping, dialect) #: If this is `true` the file did not exist yet (it is new). This #: can be used to fill it with some defaults. self.is_new = is_new def save(self, create_folder=False): """Saves all modifications back to the file. By default the folder in which the file is placed needs to exist. """ # No modifications means no write. if not self.is_dirty: return enc = self.encoding if enc is None: enc = 'utf-8' linesep = self.dialect.get_actual_linesep() if create_folder: folder = os.path.dirname(self.filename) try: os.makedirs(folder) except OSError: pass try: with open(self.filename, 'rb') as f: old_tokens = list(self.dialect.tokenize( iter_from_file(f, self.encoding))) except IOError: old_tokens = [] fd, tmp_filename = tempfile.mkstemp( dir=os.path.dirname(self.filename), prefix='.__atomic-write') try: with os.fdopen(fd, 'wb') as f: new_tokens = self.get_updated_lines(old_tokens) for _, line, _ in new_tokens: f.write((line + linesep).encode(enc)) except: exc_info = sys.exc_info() try: os.remove(tmp_filename) except OSError: pass reraise(*exc_info) if hasattr(os, 'replace'): os.replace(tmp_filename, self.filename) else: try: os.rename(tmp_filename, self.filename) except OSError: if os.name == 'nt': os.remove(self.filename) os.rename(tmp_filename, self.filename) else: raise self.rollover() self.is_new = False class AppIniFile(IniFile): """This works exactly the same as :class:`IniFile` but the ini files are placed by default in an application config directory. This uses the function :func:`get_app_dir` internally to calculate the path to it. Also by default the :meth:`~IniFile.save` method will create the folder if it did not exist yet. Example:: from inifile import AppIniFile config = AppIniFile('My App', 'my_config.ini') config['ui.user_colors'] = True config['ui.colorscheme'] = 'tango' config.save() """ def __init__(self, app_name, filename, roaming=True, force_posix=False, encoding=None, dialect=None): app_dir = get_app_dir(app_name, roaming=roaming, force_posix=force_posix) IniFile.__init__(self, os.path.join(app_dir, filename), encoding=encoding, dialect=dialect) def save(self, create_folder=True): return IniFile.save(self, create_folder=create_folder) inifile-0.4/setup.cfg000066400000000000000000000000461325671106500146230ustar00rootroot00000000000000[egg_info] tag_build = tag_date = 0 inifile-0.4/setup.py000066400000000000000000000003751325671106500145210ustar00rootroot00000000000000from setuptools import setup setup( name='inifile', license='BSD', author='Armin Ronacher', author_email='armin.ronacher@active-4.com', description='A small INI library for Python.', version='0.4', py_modules=['inifile'], )