# Copyright (C) 2009-2017 Barry Warsaw # # This file is part of setup_helpers.py # # setup_helpers.py is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, version 3 of the License. # # setup_helpers.py 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 Lesser General Public License # for more details. # # You should have received a copy of the GNU Lesser General Public License # along with setup_helpers.py. If not, see . """setup.py helper functions.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'description', 'find_doctests', 'get_version', 'long_description', 'require_python', ] import os import re import sys DEFAULT_VERSION_RE = re.compile( r'(?P\d+\.\d+(?:\.\d+)?(?:(?:a|b|rc)\d+)?)') EMPTYSTRING = '' __version__ = '3.0' def require_python(minimum): """Require at least a minimum Python version. The version number is expressed in terms of `sys.hexversion`. E.g. to require a minimum of Python 2.6, use:: >>> require_python(0x206000f0) :param minimum: Minimum Python version supported. :type minimum: integer """ if sys.hexversion < minimum: hversion = hex(minimum)[2:] if len(hversion) % 2 != 0: hversion = '0' + hversion split = list(hversion) parts = [] while split: parts.append(int(''.join((split.pop(0), split.pop(0))), 16)) major, minor, micro, release = parts if release == 0xf0: print('Python {0}.{1}.{2} or better is required'.format( major, minor, micro)) else: print('Python {0}.{1}.{2} ({3}) or better is required'.format( major, minor, micro, hex(release)[2:])) sys.exit(1) def get_version(filename, pattern=None): """Extract the __version__ from a file without importing it. While you could get the __version__ by importing the module, the very act of importing can cause unintended consequences. For example, Distribute's automatic 2to3 support will break. Instead, this searches the file for a line that starts with __version__, and extract the version number by regular expression matching. By default, two or three dot-separated digits are recognized, but by passing a pattern parameter, you can recognize just about anything. Use the `version` group name to specify the match group. :param filename: The name of the file to search. :type filename: string :param pattern: Optional alternative regular expression pattern to use. :type pattern: string :return: The version that was extracted. :rtype: string """ if pattern is None: cre = DEFAULT_VERSION_RE else: cre = re.compile(pattern) with open(filename) as fp: for line in fp: if line.startswith('__version__'): mo = cre.search(line) assert mo, 'No valid __version__ string found' return mo.group('version') raise AssertionError('No __version__ assignment found') def find_doctests(start='.', extension='.rst'): """Find separate-file doctests in the package. This is useful for Distribute's automatic 2to3 conversion support. The `setup()` keyword argument `convert_2to3_doctests` requires file names, which may be difficult to track automatically as you add new doctests. :param start: Directory to start searching in (default is cwd) :type start: string :param extension: Doctest file extension (default is .txt) :type extension: string :return: The doctest files found. :rtype: list """ doctests = [] for dirpath, dirnames, filenames in os.walk(start): doctests.extend(os.path.join(dirpath, filename) for filename in filenames if filename.endswith(extension)) return doctests def long_description(*filenames): """Provide a long description.""" res = [''] for filename in filenames: with open(filename) as fp: for line in fp: res.append(' ' + line) res.append('') res.append('\n') return EMPTYSTRING.join(res) def description(filename): """Provide a short description.""" # This ends up in the Summary header for PKG-INFO and it should be a # one-liner. It will get rendered on the package page just below the # package version header but above the long_description, which ironically # gets stuff into the Description header. It should not include reST, so # pick out the first single line after the double header. with open(filename) as fp: for lineno, line in enumerate(fp): if lineno < 3: continue line = line.strip() if len(line) > 0: return line