Skip to content

Commit

Permalink
Added config module.
Browse files Browse the repository at this point in the history
  • Loading branch information
idlesign committed Nov 26, 2016
1 parent e8d53c0 commit 7df349d
Showing 1 changed file with 367 additions and 0 deletions.
367 changes: 367 additions & 0 deletions setuptools/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,367 @@
import io
import os
import sys
from functools import partial

from distutils.errors import DistutilsOptionError
from setuptools.py26compat import import_module
from setuptools.extern.six import string_types


class ConfigHandler(object):
"""Handles metadata supplied in configuration files."""

section_prefix = None

def __init__(self, target_obj, options):
sections = {}

section_prefix = self.section_prefix
for section_name, section_options in options.items():
if not section_name.startswith(section_prefix):
continue

section_name = section_name.replace(section_prefix, '').strip(':')
sections[section_name] = section_options

self.target_obj = target_obj
self.sections = sections

@property
def parsers(self):
"""Metadata item name to parser function mapping."""
raise NotImplementedError(
'%s must provide .parsers property' % self.__class__.__name__)

def __setitem__(self, option_name, value):
unknown = tuple()
target_obj = self.target_obj

current_value = getattr(target_obj, option_name, unknown)

if current_value is unknown:
raise KeyError(option_name)

if current_value:
# Already inhabited. Skipping.
return

parser = self.parsers.get(option_name)
if parser:
value = parser(value)

setter = getattr(target_obj, 'set_%s' % option_name, None)
if setter is None:
setattr(target_obj, option_name, value)
else:
setter(value)

@classmethod
def _parse_list(cls, value, separator=','):
"""Represents value as a list.
Value is split either by comma or by lines.
:param value:
:param separator: List items separator character.
:rtype: list
"""
if isinstance(value, list): # _parse_complex case
return value

if '\n' in value:
value = value.splitlines()
else:
value = value.split(separator)

return [chunk.strip() for chunk in value]

@classmethod
def _parse_dict(cls, value):
"""Represents value as a dict.
:param value:
:rtype: dict
"""
separator = '='
result = {}
for line in cls._parse_list(value):
key, sep, val = line.partition(separator)
if sep != separator:
raise DistutilsOptionError(
'Unable to parse option value to dict: %s' % value)
result[key.strip()] = val.strip()

return result

@classmethod
def _parse_bool(cls, value):
"""Represents value as boolean.
:param value:
:rtype: bool
"""
value = value.lower()
return value in ('1', 'true', 'yes')

@classmethod
def _parse_file(cls, value):
"""Represents value as a string, allowing including text
from nearest files using include().
Examples:
include: LICENSE
include: src/file.txt
:param str value:
:rtype: str
"""
if not isinstance(value, string_types):
return value

include_directive = 'file:'
if not value.startswith(include_directive):
return value

filepath = value.replace(include_directive, '').strip()

if os.path.isfile(filepath):
with io.open(filepath, encoding='utf-8') as f:
value = f.read()

return value

@classmethod
def _get_parser_compound(cls, *parse_methods):
"""Returns parser function to represents value as a list.
Parses a value applying given methods one after another.
:param parse_methods:
:rtype: callable
"""
def parse(value):
parsed = value

for method in parse_methods:
parsed = method(parsed)

return parsed

return parse

@classmethod
def _parse_section_to_dict(cls, section_options, values_parser=None):
"""Parses section options into a dictionary.
Optionally applies a given parser to values.
:param dict section_options:
:param callable values_parser:
:rtype: dict
"""
value = {}
values_parser = values_parser or (lambda val: val)
for key, (_, val) in section_options.items():
value[key] = values_parser(val)
return value

def parse_section(self, section_options):
"""Parses configuration file section.
:param dict section_options:
"""
for (name, (_, value)) in section_options.items():
try:
self[name] = value
except KeyError:
raise DistutilsOptionError(
'Unknown distribution option: %s' % name)

def parse(self):
"""Parses configuration file items from one
or more related sections.
"""
for section_name, section_options in self.sections.items():

method_postfix = ''
if section_name: # [section:option] variant
method_postfix = '_%s' % section_name

section_parser_method = getattr(
self, 'parse_section%s' % method_postfix, None)

if section_parser_method is None:
raise DistutilsOptionError(
'Unsupported distribution option section: [%s:%s]' % (
self.section_prefix, section_name))

section_parser_method(section_options)


class ConfigMetadataHandler(ConfigHandler):

section_prefix = 'metadata'

@property
def parsers(self):
"""Metadata item name to parser function mapping."""
parse_list = self._parse_list
parse_file = self._parse_file

return {
'platforms': parse_list,
'keywords': parse_list,
'provides': parse_list,
'requires': parse_list,
'obsoletes': parse_list,
'classifiers': self._get_parser_compound(parse_file, parse_list),
'license': parse_file,
'description': parse_file,
'long_description': parse_file,
'version': self._parse_version,
}

def parse_section_classifiers(self, section_options):
"""Parses configuration file section.
:param dict section_options:
"""
classifiers = []
for begin, (_, rest) in section_options.items():
classifiers.append('%s :%s' % (begin.title(), rest))

self['classifiers'] = classifiers

def _parse_version(self, value):
"""Parses `version` option value.
:param value:
:rtype: str
"""
attr_directive = 'attr:'
if not value.startswith(attr_directive):
return value

attrs_path = value.replace(attr_directive, '').strip().split('.')
attr_name = attrs_path.pop()

module_name = '.'.join(attrs_path)
module_name = module_name or '__init__'

sys.path.insert(0, os.getcwd())
try:
module = import_module(module_name)
version = getattr(module, attr_name)

if callable(version):
version = version()

if not isinstance(version, string_types):
if hasattr(version, '__iter__'):
version = '.'.join(map(str, version))
else:
version = '%s' % version

finally:
sys.path = sys.path[1:]

return version


class ConfigOptionsHandler(ConfigHandler):

section_prefix = 'options'

@property
def parsers(self):
"""Metadata item name to parser function mapping."""
parse_list = self._parse_list
parse_list_semicolon = partial(self._parse_list, separator=';')
parse_bool = self._parse_bool
parse_dict = self._parse_dict

return {
'zip_safe': parse_bool,
'use_2to3': parse_bool,
'include_package_data': parse_bool,
'package_dir': parse_dict,
'use_2to3_fixers': parse_list,
'use_2to3_exclude_fixers': parse_list,
'convert_2to3_doctests': parse_list,
'scripts': parse_list,
'eager_resources': parse_list,
'dependency_links': parse_list,
'namespace_packages': parse_list,
'install_requires': parse_list_semicolon,
'setup_requires': parse_list_semicolon,
'tests_require': parse_list_semicolon,
'packages': self._parse_packages,
'entry_points': self._parse_file,
}

def _parse_packages(self, value):
"""Parses `packages` option value.
:param value:
:rtype: list
"""
find_directive = 'find:'

if not value.startswith(find_directive):
return self._parse_list(value)

from setuptools import find_packages
return find_packages()

def parse_section_dependency_links(self, section_options):
"""Parses `dependency_links` configuration file section.
:param dict section_options:
"""
parsed = self._parse_section_to_dict(section_options)
self['dependency_links'] = list(parsed.values())

def parse_section_entry_points(self, section_options):
"""Parses `entry_points` configuration file section.
:param dict section_options:
"""
parsed = self._parse_section_to_dict(section_options, self._parse_list)
self['entry_points'] = parsed

def _parse_package_data(self, section_options):
parsed = self._parse_section_to_dict(section_options, self._parse_list)

root = parsed.get('*')
if root:
parsed[''] = root
del parsed['*']

return parsed

def parse_section_package_data(self, section_options):
"""Parses `package_data` configuration file section.
:param dict section_options:
"""
self['package_data'] = self._parse_package_data(section_options)

def parse_section_exclude_package_data(self, section_options):
"""Parses `exclude_package_data` configuration file section.
:param dict section_options:
"""
self['exclude_package_data'] = self._parse_package_data(
section_options)

def parse_section_extras_require(self, section_options):
"""Parses `extras_require` configuration file section.
:param dict section_options:
"""
parse_list = partial(self._parse_list, separator=';')
self['extras_require'] = self._parse_section_to_dict(
section_options, parse_list)

0 comments on commit 7df349d

Please sign in to comment.