~james-w/pkgme/trunk

30.1.1 by Barry Warsaw
Basic Python packaging.
1
# setup_helper.py - Some utility functions for setup.py authors.
2
#
3
# Copyright (C) 2009, 2010 by Barry A. Warsaw
4
#
5
# This program is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU Lesser General Public License as published by the
7
# Free Software Foundation, version 3 of the License.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
12
# for more details.
13
#
14
# You should have received a copy of the GNU Lesser General Public License
30.1.2 by Barry Warsaw
Fix some package references.
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
30.1.1 by Barry Warsaw
Basic Python packaging.
16
17
"""setup.py helper functions."""
18
19
from __future__ import absolute_import, unicode_literals
20
from __future__ import print_function
21
22
23
__metaclass__ = type
24
__all__ = [
25
    'description',
26
    'find_doctests',
27
    'get_version',
28
    'long_description',
29
    'require_python',
30
    ]
31
32
33
import os
34
import re
35
import sys
36
37
38
DEFAULT_VERSION_RE = re.compile(r'(?P<version>\d+\.\d(?:\.\d+)?)')
39
NL = '\n'
40
41
42

43
def require_python(minimum):
44
    """Require at least a minimum Python version.
45
46
    The version number is expressed in terms of `sys.hexversion`.  E.g. to
47
    require a minimum of Python 2.6, use::
48
49
    >>> require_python(0x206000f0)
50
51
    :param minimum: Minimum Python version supported.
52
    :type minimum: integer
53
    """
54
    if sys.hexversion < minimum:
55
        hversion = hex(minimum)[2:]
56
        if len(hversion) % 2 != 0:
57
            hversion = '0' + hversion
58
        split = list(hversion)
59
        parts = []
60
        while split:
61
            parts.append(int(''.join((split.pop(0), split.pop(0))), 16))
62
        major, minor, micro, release = parts
63
        if release == 0xf0:
64
            print('Python {0}.{1}.{2} or better is required'.format(
65
                major, minor, micro))
66
        else:
67
            print('Python {0}.{1}.{2} ({3}) or better is required'.format(
68
                major, minor, micro, hex(release)[2:]))
69
        sys.exit(1)
70
71
72

73
def get_version(filename, pattern=None):
74
    """Extract the __version__ from a file without importing it.
75
76
    While you could get the __version__ by importing the module, the very act
77
    of importing can cause unintended consequences.  For example, Distribute's
78
    automatic 2to3 support will break.  Instead, this searches the file for a
79
    line that starts with __version__, and extract the version number by
80
    regular expression matching.
81
82
    By default, two or three dot-separated digits are recognized, but by
83
    passing a pattern parameter, you can recognize just about anything.  Use
84
    the `version` group name to specify the match group.
85
86
    :param filename: The name of the file to search.
87
    :type filename: string
88
    :param pattern: Optional alternative regular expression pattern to use.
89
    :type pattern: string
90
    :return: The version that was extracted.
91
    :rtype: string
92
    """
93
    if pattern is None:
94
        cre = DEFAULT_VERSION_RE
95
    else:
96
        cre = re.compile(pattern)
97
    with open(filename) as fp:
98
        for line in fp:
99
            if line.startswith('__version__'):
100
                mo = cre.search(line)
101
                assert mo, 'No valid __version__ string found'
102
                return mo.group('version')
103
    raise AssertionError('No __version__ assignment found')
104
105
106

107
def find_doctests(start='.', extension='.txt'):
108
    """Find separate-file doctests in the package.
109
110
    This is useful for Distribute's automatic 2to3 conversion support.  The
111
    `setup()` keyword argument `convert_2to3_doctests` requires file names,
112
    which may be difficult to track automatically as you add new doctests.
113
114
    :param start: Directory to start searching in (default is cwd)
115
    :type start: string
116
    :param extension: Doctest file extension (default is .txt)
117
    :type extension: string
118
    :return: The doctest files found.
119
    :rtype: list
120
    """
121
    doctests = []
122
    for dirpath, dirnames, filenames in os.walk(start):
123
        doctests.extend(os.path.join(dirpath, filename)
124
                        for filename in filenames
125
                        if filename.endswith(extension))
126
    return doctests
127
128
129

130
def long_description(*filenames):
131
    """Provide a long description."""
132
    res = []
133
    for value in filenames:
134
        if value.endswith('.txt'):
135
            with open(value) as fp:
136
                value = fp.read()
137
        res.append(value)
138
        if not value.endswith(NL):
139
            res.append('')
140
    return NL.join(res)
141
142
143
def description(filename):
144
    """Provide a short description."""
145
    with open(filename) as fp:
146
        for line in fp:
147
            return line.strip()