Adding new stuff
This commit is contained in:
parent
3d621f7993
commit
91c18a10da
449 changed files with 2 additions and 1 deletions
|
|
@ -0,0 +1 @@
|
|||
""" Support libs. """
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,38 @@
|
|||
"""Backport of importlib.import_module from 3.x."""
|
||||
# While not critical (and in no way guaranteed!), it would be nice to keep this
|
||||
# code compatible with Python 2.3.
|
||||
import sys
|
||||
|
||||
def _resolve_name(name, package, level):
|
||||
"""Return the absolute name of the module to be imported."""
|
||||
if not hasattr(package, 'rindex'):
|
||||
raise ValueError("'package' not set to a string")
|
||||
dot = len(package)
|
||||
for x in xrange(level, 1, -1):
|
||||
try:
|
||||
dot = package.rindex('.', 0, dot)
|
||||
except ValueError:
|
||||
raise ValueError("attempted relative import beyond top-level "
|
||||
"package")
|
||||
return "%s.%s" % (package[:dot], name)
|
||||
|
||||
|
||||
def import_module(name, package=None):
|
||||
"""Import a module.
|
||||
|
||||
The 'package' argument is required when performing a relative import. It
|
||||
specifies the package to use as the anchor point from which to resolve the
|
||||
relative import to an absolute import.
|
||||
|
||||
"""
|
||||
if name.startswith('.'):
|
||||
if not package:
|
||||
raise TypeError("relative imports require the 'package' argument")
|
||||
level = 0
|
||||
for character in name:
|
||||
if character != '.':
|
||||
break
|
||||
level += 1
|
||||
name = _resolve_name(name[level:], package, level)
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
|
@ -0,0 +1,405 @@
|
|||
"""
|
||||
Inirama is a python module that parses INI files.
|
||||
|
||||
.. _badges:
|
||||
.. include:: ../README.rst
|
||||
:start-after: .. _badges:
|
||||
:end-before: .. _contents:
|
||||
|
||||
.. _description:
|
||||
.. include:: ../README.rst
|
||||
:start-after: .. _description:
|
||||
:end-before: .. _badges:
|
||||
|
||||
:copyright: 2013 by Kirill Klenov.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
|
||||
__version__ = "0.8.0"
|
||||
__project__ = "Inirama"
|
||||
__author__ = "Kirill Klenov <horneds@gmail.com>"
|
||||
__license__ = "BSD"
|
||||
|
||||
|
||||
import io
|
||||
import re
|
||||
import logging
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from UserDict import DictMixin
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
null = object()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.clear()
|
||||
self.update(*args, **kwargs)
|
||||
|
||||
def clear(self):
|
||||
self.__map = dict()
|
||||
self.__order = list()
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
self.__map[key] = len(self.__order)
|
||||
self.__order.append(key)
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
self.__map.pop(key)
|
||||
self.__order = self.null
|
||||
|
||||
def __iter__(self):
|
||||
for key in self.__order:
|
||||
if key is not self.null:
|
||||
yield key
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
|
||||
NS_LOGGER = logging.getLogger('inirama')
|
||||
|
||||
|
||||
class Scanner(object):
|
||||
|
||||
""" Split a code string on tokens. """
|
||||
|
||||
def __init__(self, source, ignore=None, patterns=None):
|
||||
""" Init Scanner instance.
|
||||
|
||||
:param patterns: List of token patterns [(token, regexp)]
|
||||
:param ignore: List of ignored tokens
|
||||
|
||||
"""
|
||||
self.reset(source)
|
||||
if patterns:
|
||||
self.patterns = []
|
||||
for k, r in patterns:
|
||||
self.patterns.append((k, re.compile(r)))
|
||||
|
||||
if ignore:
|
||||
self.ignore = ignore
|
||||
|
||||
def reset(self, source):
|
||||
""" Reset scanner's state.
|
||||
|
||||
:param source: Source for parsing
|
||||
|
||||
"""
|
||||
self.tokens = []
|
||||
self.source = source
|
||||
self.pos = 0
|
||||
|
||||
def scan(self):
|
||||
""" Scan source and grab tokens. """
|
||||
|
||||
self.pre_scan()
|
||||
|
||||
token = None
|
||||
end = len(self.source)
|
||||
|
||||
while self.pos < end:
|
||||
|
||||
best_pat = None
|
||||
best_pat_len = 0
|
||||
|
||||
# Check patterns
|
||||
for p, regexp in self.patterns:
|
||||
m = regexp.match(self.source, self.pos)
|
||||
if m:
|
||||
best_pat = p
|
||||
best_pat_len = len(m.group(0))
|
||||
break
|
||||
|
||||
if best_pat is None:
|
||||
raise SyntaxError(
|
||||
"SyntaxError[@char {0}: {1}]".format(
|
||||
self.pos, "Bad token."))
|
||||
|
||||
# Ignore patterns
|
||||
if best_pat in self.ignore:
|
||||
self.pos += best_pat_len
|
||||
continue
|
||||
|
||||
# Create token
|
||||
token = (
|
||||
best_pat,
|
||||
self.source[self.pos:self.pos + best_pat_len],
|
||||
self.pos,
|
||||
self.pos + best_pat_len,
|
||||
)
|
||||
|
||||
self.pos = token[-1]
|
||||
self.tokens.append(token)
|
||||
|
||||
def pre_scan(self):
|
||||
""" Prepare source. """
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
""" Print the last 5 tokens that have been scanned in.
|
||||
|
||||
:return str:
|
||||
|
||||
"""
|
||||
return '<Scanner: ' + ','.join(
|
||||
"{0}({2}:{3})".format(*t) for t in self.tokens[-5:]) + ">"
|
||||
|
||||
|
||||
class INIScanner(Scanner):
|
||||
|
||||
""" Get tokens for INI. """
|
||||
|
||||
patterns = [
|
||||
('SECTION', re.compile(r'\[[^]]+\]')),
|
||||
('IGNORE', re.compile(r'[ \r\t\n]+')),
|
||||
('COMMENT', re.compile(r'[;#].*')),
|
||||
('KEY_VALUE', re.compile(r'[^=\s]+\s*[:=].*')),
|
||||
('CONTINUATION', re.compile(r'.*'))
|
||||
]
|
||||
|
||||
ignore = ['IGNORE']
|
||||
|
||||
def pre_scan(self):
|
||||
""" Prepare string for scaning. """
|
||||
escape_re = re.compile(r'\\\n[\t ]+')
|
||||
self.source = escape_re.sub('', self.source)
|
||||
|
||||
|
||||
undefined = object()
|
||||
|
||||
|
||||
class Section(OrderedDict):
|
||||
|
||||
""" Representation of INI section. """
|
||||
|
||||
def __init__(self, namespace, *args, **kwargs):
|
||||
super(Section, self).__init__(*args, **kwargs)
|
||||
self.namespace = namespace
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
value = str(value)
|
||||
if value.isdigit():
|
||||
value = int(value)
|
||||
|
||||
super(Section, self).__setitem__(name, value)
|
||||
|
||||
|
||||
class InterpolationSection(Section):
|
||||
|
||||
""" INI section with interpolation support. """
|
||||
|
||||
var_re = re.compile('{([^}]+)}')
|
||||
|
||||
def get(self, name, default=None):
|
||||
""" Get item by name.
|
||||
|
||||
:return object: value or None if name not exists
|
||||
|
||||
"""
|
||||
|
||||
if name in self:
|
||||
return self[name]
|
||||
return default
|
||||
|
||||
def __interpolate__(self, math):
|
||||
try:
|
||||
key = math.group(1).strip()
|
||||
return self.namespace.default.get(key) or self[key]
|
||||
except KeyError:
|
||||
return ''
|
||||
|
||||
def __getitem__(self, name, raw=False):
|
||||
value = super(InterpolationSection, self).__getitem__(name)
|
||||
if not raw:
|
||||
sample = undefined
|
||||
while sample != value:
|
||||
try:
|
||||
sample, value = value, self.var_re.sub(
|
||||
self.__interpolate__, value)
|
||||
except RuntimeError:
|
||||
message = "Interpolation failed: {0}".format(name)
|
||||
NS_LOGGER.error(message)
|
||||
raise ValueError(message)
|
||||
return value
|
||||
|
||||
def iteritems(self, raw=False):
|
||||
""" Iterate self items. """
|
||||
|
||||
for key in self:
|
||||
yield key, self.__getitem__(key, raw=raw)
|
||||
|
||||
items = iteritems
|
||||
|
||||
|
||||
class Namespace(object):
|
||||
|
||||
""" Default class for parsing INI.
|
||||
|
||||
:param **default_items: Default items for default section.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
::
|
||||
|
||||
from inirama import Namespace
|
||||
|
||||
ns = Namespace()
|
||||
ns.read('config.ini')
|
||||
|
||||
print ns['section']['key']
|
||||
|
||||
ns['other']['new'] = 'value'
|
||||
ns.write('new_config.ini')
|
||||
|
||||
"""
|
||||
|
||||
#: Name of default section (:attr:`~inirama.Namespace.default`)
|
||||
default_section = 'DEFAULT'
|
||||
|
||||
#: Dont raise any exception on file reading erorrs
|
||||
silent_read = True
|
||||
|
||||
#: Class for generating sections
|
||||
section_type = Section
|
||||
|
||||
def __init__(self, **default_items):
|
||||
self.sections = OrderedDict()
|
||||
for k, v in default_items.items():
|
||||
self[self.default_section][k] = v
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
""" Return default section or empty dict.
|
||||
|
||||
:return :class:`inirama.Section`: section
|
||||
|
||||
"""
|
||||
return self.sections.get(self.default_section, dict())
|
||||
|
||||
def read(self, *files, **params):
|
||||
""" Read and parse INI files.
|
||||
|
||||
:param *files: Files for reading
|
||||
:param **params: Params for parsing
|
||||
|
||||
Set `update=False` for prevent values redefinition.
|
||||
|
||||
"""
|
||||
for f in files:
|
||||
try:
|
||||
with io.open(f, encoding='utf-8') as ff:
|
||||
NS_LOGGER.info('Read from `{0}`'.format(ff.name))
|
||||
self.parse(ff.read(), **params)
|
||||
except (IOError, TypeError, SyntaxError, io.UnsupportedOperation):
|
||||
if not self.silent_read:
|
||||
NS_LOGGER.error('Reading error `{0}`'.format(ff.name))
|
||||
raise
|
||||
|
||||
def write(self, f):
|
||||
""" Write namespace as INI file.
|
||||
|
||||
:param f: File object or path to file.
|
||||
|
||||
"""
|
||||
if isinstance(f, str):
|
||||
f = io.open(f, 'w', encoding='utf-8')
|
||||
|
||||
if not hasattr(f, 'read'):
|
||||
raise AttributeError("Wrong type of file: {0}".format(type(f)))
|
||||
|
||||
NS_LOGGER.info('Write to `{0}`'.format(f.name))
|
||||
for section in self.sections.keys():
|
||||
f.write('[{0}]\n'.format(section))
|
||||
for k, v in self[section].items():
|
||||
f.write('{0:15}= {1}\n'.format(k, v))
|
||||
f.write('\n')
|
||||
f.close()
|
||||
|
||||
def parse(self, source, update=True, **params):
|
||||
""" Parse INI source as string.
|
||||
|
||||
:param source: Source of INI
|
||||
:param update: Replace alredy defined items
|
||||
|
||||
"""
|
||||
scanner = INIScanner(source)
|
||||
scanner.scan()
|
||||
|
||||
section = self.default_section
|
||||
name = None
|
||||
|
||||
for token in scanner.tokens:
|
||||
if token[0] == 'KEY_VALUE':
|
||||
name, value = re.split('[=:]', token[1], 1)
|
||||
name, value = name.strip(), value.strip()
|
||||
if not update and name in self[section]:
|
||||
continue
|
||||
self[section][name] = value
|
||||
|
||||
elif token[0] == 'SECTION':
|
||||
section = token[1].strip('[]')
|
||||
|
||||
elif token[0] == 'CONTINUATION':
|
||||
if not name:
|
||||
raise SyntaxError(
|
||||
"SyntaxError[@char {0}: {1}]".format(
|
||||
token[2], "Bad continuation."))
|
||||
self[section][name] += '\n' + token[1].strip()
|
||||
|
||||
def __getitem__(self, name):
|
||||
""" Look name in self sections.
|
||||
|
||||
:return :class:`inirama.Section`: section
|
||||
|
||||
"""
|
||||
if name not in self.sections:
|
||||
self.sections[name] = self.section_type(self)
|
||||
return self.sections[name]
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.sections
|
||||
|
||||
def __repr__(self):
|
||||
return "<Namespace: {0}>".format(self.sections)
|
||||
|
||||
|
||||
class InterpolationNamespace(Namespace):
|
||||
|
||||
""" That implements the interpolation feature.
|
||||
|
||||
::
|
||||
|
||||
from inirama import InterpolationNamespace
|
||||
|
||||
ns = InterpolationNamespace()
|
||||
ns.parse('''
|
||||
[main]
|
||||
test = value
|
||||
foo = bar {test}
|
||||
more_deep = wow {foo}
|
||||
''')
|
||||
print ns['main']['more_deep'] # wow bar value
|
||||
|
||||
"""
|
||||
|
||||
section_type = InterpolationSection
|
||||
|
||||
# pylama:ignore=D,W02,E731,W0621
|
||||
Loading…
Add table
Add a link
Reference in a new issue