diff options
| author | Tarek Ziade <tarek@ziade.org> | 2010-09-19 10:15:10 +0200 |
|---|---|---|
| committer | Tarek Ziade <tarek@ziade.org> | 2010-09-19 10:15:10 +0200 |
| commit | 5afe5844d0b204e55801b6a02fab6b2e18254448 (patch) | |
| tree | a65ade5921380d541e2cc30ad307a30917b54d27 /src/distutils2/mkpkg.py | |
| parent | addfd74a36216143ed49f25da1cd6030902dad91 (diff) | |
| download | disutils2-5afe5844d0b204e55801b6a02fab6b2e18254448.tar.gz | |
moved everything in the same dir - we want to include docs/ in the release
Diffstat (limited to 'src/distutils2/mkpkg.py')
| -rw-r--r-- | src/distutils2/mkpkg.py | 417 |
1 files changed, 0 insertions, 417 deletions
diff --git a/src/distutils2/mkpkg.py b/src/distutils2/mkpkg.py deleted file mode 100644 index 438fb87..0000000 --- a/src/distutils2/mkpkg.py +++ /dev/null @@ -1,417 +0,0 @@ -#!/usr/bin/env python -# -# Helper for automating the creation of a package by looking at you -# current directory and asking the user questions. -# -# Available as either a stand-alone file or callable from the distutils2 -# package: -# -# python -m distutils2.mkpkg -# or: -# python mkpkg.py -# -# Written by Sean Reifschneider <jafo@tummy.com> -# -# Original TODO list: -# Look for a license file and automatically add the category. -# When a .c file is found during the walk, can we add it as an extension? -# Ask if there is a maintainer different that the author -# Ask for the platform (can we detect this via "import win32" or something?) -# Ask for the dependencies. -# Ask for the Requires-Dist -# Ask for the Provides-Dist -# Detect scripts (not sure how. #! outside of package?) - -import os -import sys -import re -import shutil -from ConfigParser import RawConfigParser -from textwrap import dedent -# importing this with an underscore as it should be replaced by the -# dict form or another structures for all purposes -from distutils2._trove import all_classifiers as _CLASSIFIERS_LIST - - -_helptext = { - 'name': ''' -The name of the program to be packaged, usually a single word composed -of lower-case characters such as "python", "sqlalchemy", or "CherryPy". -''', - 'version': ''' -Version number of the software, typically 2 or 3 numbers separated by dots -such as "1.00", "0.6", or "3.02.01". "0.1.0" is recommended for initial -development. -''', - 'description': ''' -A short summary of what this package is or does, typically a sentence 80 -characters or less in length. -''', - 'author': ''' -The full name of the author (typically you). -''', - 'author_email': ''' -E-mail address of the package author (typically you). -''', - 'do_classifier': ''' -Trove classifiers are optional identifiers that allow you to specify the -intended audience by saying things like "Beta software with a text UI -for Linux under the PSF license. However, this can be a somewhat involved -process. -''', - 'url': ''' -The home page for the package, typically starting with "http://". -''', - 'trove_license': ''' -Optionally you can specify a license. Type a string that identifies a common -license, and then you can select a list of license specifiers. -''', - 'trove_generic': ''' -Optionally, you can set other trove identifiers for things such as the -human language, programming language, user interface, etc... -''', -} - -# XXX everything needs docstrings and tests (both low-level tests of various -# methods and functional tests of running the script) - - -def ask_yn(question, default=None, helptext=None): - while True: - answer = ask(question, default, helptext, required=True) - if answer and answer[0].lower() in 'yn': - return answer[0].lower() - - print '\nERROR: You must select "Y" or "N".\n' - - -def ask(question, default=None, helptext=None, required=True, - lengthy=False, multiline=False): - prompt = '%s: ' % (question,) - if default: - prompt = '%s [%s]: ' % (question, default) - if default and len(question) + len(default) > 70: - prompt = '%s\n [%s]: ' % (question, default) - if lengthy or multiline: - prompt += '\n >' - - if not helptext: - helptext = 'No additional help available.' - - helptext = helptext.strip("\n") - - while True: - sys.stdout.write(prompt) - sys.stdout.flush() - - line = sys.stdin.readline().strip() - if line == '?': - print '=' * 70 - print helptext - print '=' * 70 - continue - if default and not line: - return default - if not line and required: - print '*' * 70 - print 'This value cannot be empty.' - print '===========================' - if helptext: - print helptext - print '*' * 70 - continue - return line - - -def _build_classifiers_dict(classifiers): - d = {} - for key in classifiers: - subDict = d - for subkey in key.split(' :: '): - if not subkey in subDict: - subDict[subkey] = {} - subDict = subDict[subkey] - return d - -CLASSIFIERS = _build_classifiers_dict(_CLASSIFIERS_LIST) - - -class MainProgram(object): - def __init__(self): - self.configparser = None - self.classifiers = {} - self.data = {} - self.data['classifier'] = self.classifiers - self.data['packages'] = {} - self.load_config_file() - - def lookup_option(self, key): - if not self.configparser.has_option('DEFAULT', key): - return None - return self.configparser.get('DEFAULT', key) - - def load_config_file(self): - self.configparser = RawConfigParser() - # TODO replace with section in distutils config file - self.configparser.read(os.path.expanduser('~/.mkpkgpy')) - self.data['author'] = self.lookup_option('author') - self.data['author_email'] = self.lookup_option('author_email') - - def update_config_file(self): - valuesDifferent = False - # FIXME looking only for those two fields seems wrong - for compareKey in ('author', 'author_email'): - if self.lookup_option(compareKey) != self.data[compareKey]: - valuesDifferent = True - self.configparser.set('DEFAULT', compareKey, - self.data[compareKey]) - - if not valuesDifferent: - return - - fp = open(os.path.expanduser('~/.mkpkgpy'), 'w') - try: - self.configparser.write(fp) - finally: - fp.close() - - def load_existing_setup_script(self): - raise NotImplementedError - # Ideas: - # - define a mock module to assign to sys.modules['distutils'] before - # importing the setup script as a module (or executing it); it would - # provide setup (a function that just returns its args as a dict), - # Extension (ditto), find_packages (the real function) - # - we could even mock Distribution and commands to handle more setup - # scripts - # - we could use a sandbox (http://bugs.python.org/issue8680) - # - the cleanest way is to parse the file, not import it, but there is - # no way to do that across versions (the compiler package is - # deprecated or removed in recent Pythons, the ast module is not - # present before 2.6) - - def inspect_file(self, path): - fp = open(path, 'r') - try: - for line in [fp.readline() for _ in range(10)]: - m = re.match(r'^#!.*python((?P<major>\d)(\.\d+)?)?$', line) - if m: - if m.group('major') == '3': - self.classifiers['Programming Language :: Python :: 3'] = 1 - else: - self.classifiers['Programming Language :: Python :: 2'] = 1 - finally: - fp.close() - - def inspect_directory(self): - dirName = os.path.basename(os.getcwd()) - self.data['name'] = dirName - m = re.match(r'(.*)-(\d.+)', dirName) - if m: - self.data['name'] = m.group(1) - self.data['version'] = m.group(2) - - for root, dirs, files in os.walk(os.curdir): - for filename in files: - if root == os.curdir and filename == 'setup.py': - continue - self.inspect_file(os.path.join(root, filename)) - - if filename == '__init__.py': - trySrc = os.path.join(os.curdir, 'src') - tmpRoot = root - if tmpRoot.startswith(trySrc): - tmpRoot = tmpRoot[len(trySrc):] - if tmpRoot.startswith(os.path.sep): - tmpRoot = tmpRoot[len(os.path.sep):] - - self.data['packages'][tmpRoot] = root[1 + len(os.path.sep):] - - def query_user(self): - self.data['name'] = ask('Package name', self.data['name'], - _helptext['name']) - self.data['version'] = ask('Current version number', - self.data.get('version'), _helptext['version']) - self.data['description'] = ask('Package description', - self.data.get('description'), _helptext['description'], - lengthy=True) - self.data['author'] = ask('Author name', - self.data.get('author'), _helptext['author']) - self.data['author_email'] = ask('Author e-mail address', - self.data.get('author_email'), _helptext['author_email']) - self.data['url'] = ask('Project URL', - self.data.get('url'), _helptext['url'], required=False) - - if ask_yn('Do you want to set Trove classifiers?', - helptext=_helptext['do_classifier']) == 'y': - self.set_classifier() - - def set_classifier(self): - self.set_devel_status(self.classifiers) - self.set_license(self.classifiers) - self.set_other_classifier(self.classifiers) - - def set_other_classifier(self, classifiers): - if ask_yn('Do you want to set other trove identifiers', 'n', - _helptext['trove_generic']) != 'y': - return - self.walk_classifiers(classifiers, [CLASSIFIERS], '') - - def walk_classifiers(self, classifiers, trovepath, desc): - trove = trovepath[-1] - - if not trove: - return - - for key in sorted(trove.keys()): - if len(trove[key]) == 0: - if ask_yn('Add "%s"' % desc[4:] + ' :: ' + key, 'n') == 'y': - classifiers[desc[4:] + ' :: ' + key] = 1 - continue - - if ask_yn('Do you want to set items under\n "%s" (%d sub-items)' - % (key, len(trove[key])), 'n', - _helptext['trove_generic']) == 'y': - self.walk_classifiers(classifiers, trovepath + [trove[key]], - desc + ' :: ' + key) - - def set_license(self, classifiers): - while True: - license = ask('What license do you use', - helptext=_helptext['trove_license'], required=False) - if not license: - return - - licenseWords = license.lower().split(' ') - - foundList = [] - # TODO use enumerate - for index in range(len(_CLASSIFIERS_LIST)): - troveItem = _CLASSIFIERS_LIST[index] - if not troveItem.startswith('License :: '): - continue - troveItem = troveItem[11:].lower() - - allMatch = True - for word in licenseWords: - if not word in troveItem: - allMatch = False - break - if allMatch: - foundList.append(index) - - question = 'Matching licenses:\n\n' - # TODO use enumerate? - for i in xrange(1, len(foundList) + 1): - question += ' %s) %s\n' % (i, _CLASSIFIERS_LIST[foundList[i - 1]]) - question += ('\nType the number of the license you wish to use or ' - '? to try again:') - troveLicense = ask(question, required=False) - - if troveLicense == '?': - continue - if troveLicense == '': - return - # FIXME the int conversion can fail - foundIndex = foundList[int(troveLicense) - 1] - classifiers[_CLASSIFIERS_LIST[foundIndex]] = 1 - try: - return - except IndexError: - print ("ERROR: Invalid selection, type a number from the list " - "above.") - - def set_devel_status(self, classifiers): - while True: - choice = ask(dedent('''\ - Please select the project status: - - 1 - Planning - 2 - Pre-Alpha - 3 - Alpha - 4 - Beta - 5 - Production/Stable - 6 - Mature - 7 - Inactive - - Status'''), required=False) - if choice: - try: - choice = int(choice) - 1 - key = ['Development Status :: 1 - Planning', - 'Development Status :: 2 - Pre-Alpha', - 'Development Status :: 3 - Alpha', - 'Development Status :: 4 - Beta', - 'Development Status :: 5 - Production/Stable', - 'Development Status :: 6 - Mature', - 'Development Status :: 7 - Inactive'][choice] - classifiers[key] = 1 - return - except (IndexError, ValueError): - print ("ERROR: Invalid selection, type a single digit " - "number.") - - def _dotted_packages(self, data): - packages = sorted(data.keys()) - modified_pkgs = [] - for pkg in packages: - pkg = pkg.lstrip('./') - pkg = pkg.replace('/', '.') - modified_pkgs.append(pkg) - return modified_pkgs - - def write_setup_script(self): - if os.path.exists('setup.py'): - if os.path.exists('setup.py.old'): - print ("ERROR: setup.py.old backup exists, please check that " - "current setup.py is correct and remove setup.py.old") - return - shutil.move('setup.py', 'setup.py.old') - - fp = open('setup.py', 'w') - try: - # XXX do LFs work on all platforms? - fp.write('#!/usr/bin/env python\n\n') - fp.write('from distutils2.core import setup\n\n') - fp.write('setup(name=%s,\n' % repr(self.data['name'])) - fp.write(' version=%s,\n' % repr(self.data['version'])) - fp.write(' description=%s,\n' - % repr(self.data['description'])) - fp.write(' author=%s,\n' % repr(self.data['author'])) - fp.write(' author_email=%s,\n' - % repr(self.data['author_email'])) - if self.data['url']: - fp.write(' url=%s,\n' % repr(self.data['url'])) - if self.data['classifier']: - fp.write(' classifier=[\n') - for classifier in sorted(self.data['classifier'].keys()): - fp.write(' %s,\n' % repr(classifier)) - fp.write(' ],\n') - if self.data['packages']: - fp.write(' packages=%s,\n' - % repr(self._dotted_packages(self.data['packages']))) - fp.write(' package_dir=%s,\n' - % repr(self.data['packages'])) - fp.write(' #scripts=[\'path/to/script\']\n') - - fp.write(' )\n') - finally: - fp.close() - os.chmod('setup.py', 0755) - - print 'Wrote "setup.py".' - - -def main(): - """Main entry point.""" - program = MainProgram() - # uncomment when implemented - #program.load_existing_setup_script() - program.inspect_directory() - program.query_user() - program.update_config_file() - program.write_setup() - - -if __name__ == '__main__': - main() |
