diff --git a/Makefile b/Makefile index aad3f28..b56e033 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,11 @@ test: tox-test: tox -release: - python scripts/make-release.py +release_test: + pyqi make-release --package-name=pyqi + +release_real: + pyqi make-release --package-name=pyqi --real-run clean-pyc: find . -name '*.pyc' -exec rm -f {} + diff --git a/pyqi/commands/make_package.py b/pyqi/commands/make_package.py new file mode 100644 index 0000000..aa46958 --- /dev/null +++ b/pyqi/commands/make_package.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python +from __future__ import division + +__author__ = "Daniel McDonald" +__copyright__ = "pyqi, copyright 2014" +__credits__ = ["Daniel McDonald"] +__license__ = "BSD" +__maintainer__ = "Daniel McDonald" +__email__ = "mcdonadt@colorado.edu" + +import os +from pyqi import __version__ # note, pyqi has not been vermanized +from pyqi.core.command import (Command, CommandIn, ParameterCollection) +from pyqi.util import pyqi_system_call +from sphinx.quickstart import generate + +PKG_INIT = """#!/usr/bin/env python + +#----------------------------------------------------------------------------- +# Copyright (c) 2014, The biocore Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +from verman import Version +%(pkg_name)s_version = Version("%(pkg_name)s", 0, 1, 0, releaselevel="dev", init_file=__file__) +__version__ = %(pkg_name)s_version.mmm +""" + + +COPYING = """============================= + The %(pkg_name)s licensing terms +============================= + +%(pkg_name)s is licensed under the terms of the Modified BSD License (also known as +New or Revised BSD), as follows: + +Copyright (c) 2014, biocore Development Team %(email)s + +All rights reserved. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name BiPy nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE BIPY DEVELOPMENT TEAM BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#----------------------------------------------------------------------------- +# Copyright (c) 2013, The biocore Development Team. +# +# Distributed under the terms of the Modified BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- +""" + +README = """%(pkg_name)s +=============== + +A simple package that does all the awesome +""" + +CHANGELOG = """%(pkg_name)s ChangeLog +=================== + +%(pkg_name)s 0.1.0-dev +-------------------- + +* initial creation of teh awesome +""" + +SETUP = """#!/usr/bin/env python + +from setuptools import setup +from glob import glob +from %(pkg_name)s import %(pkg_name)s_version +import sys + +__author__ = "%(author)s" +__copyright__ = "Copyright 2014" +__credits__ = ["%(author)s"] +__license__ = "BSD" +__version__ = %(pkg_name)s_version.mmm +__maintainer__ = "%(author)s" +__email__ = "%(email)s" + +# PyPI's list of classifiers can be found here by Googling: +# "pypi list of classifiers" +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: Implementation :: CPython", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X" +] + +# Verify Python version +ver = '.'.join(map(str, [sys.version_info.major, sys.version_info.minor])) +if ver not in ['2.7']: + sys.stderr.write("Only Python >=2.7 and <3.0 is supported.") + sys.exit(1) + +short_description = "Doing things" +long_description = "Committing acts of awesome" + +setup(name='%(pkg_name)s', + version=__version__, + license=__license__, + description=short_description, + long_description=long_description, + author=__maintainer__, + author_email=__email__, + maintainer=__maintainer__, + maintainer_email=__email__, + url='%(url)s', + packages=['%(pkg_name)s'], + scripts=glob('scripts/*'), + install_requires=[%(pyqi_pip_requires)s], + extras_require={'test': ['nose >= 0.10.1', + 'tox >= 1.6.1'] + }, + classifiers=classifiers + ) +""" + +MANIFEST = """include README.md +include COPYING.txt +include README.md +include ChangeLog.md + +graft %(pkg_name)s +graft scripts +graft doc + +global-exclude *.pyc +global-exclude *.pyo +global-exclude .git +""" + +MAKEFILE = """# +# Based on Makefile from https://github.com/mitsuhiko/flask/blob/master/Makefile +# at SHA-1: afd3c4532b8625729bed9ed37a3eddd0b7b3b5a9 +# +.PHONY: clean-pyc test upload-docs docs + +all: clean-pyc clean-docs test tox-test docs + +clean: clean-pyc clean-docs + +test: + nosetests + +tox-test: + tox + +release_test: + pyqi make-release --package-name=%(pkg_name)s + +release_real: + pyqi make-release --package-name=%(pkg_name)s --real-run + +clean-pyc: + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + +docs: + $(MAKE) -C doc html + +clean-docs: + $(MAKE) -C doc clean + +love: + @echo not war +""" + +PYQI_DRIVER = """#!/bin/sh + +exec pyqi --driver-name %(pkg_name)s --command-config-module %(pkg_name)s.interfaces.optparse.config -- "$@" +""" + +def touch(path): + f = open(path, 'w') + f.close() + +def write_file(fname, payload): + if os.path.exists(fname): + raise IOError("%s exists!" % fname) + + with open(fname, 'w') as f: + f.write(payload) + +class MakePackage(Command): + BriefDescription = "Stub out a package" + LongDescription = "Create the basic directory structure for a Python package" + CommandIns = ParameterCollection([ + CommandIn(Name='pkg_name', DataType=str, + Description='name of the package', Required=True), + CommandIn(Name='pkg_base', DataType=str, + Description='base path for package', Required=True), + CommandIn(Name='email', DataType=str, + Description='contact email', Required=True), + CommandIn(Name='author', DataType=str, + Description='package author', Required=True), + CommandIn(Name='url', DataType=str, + Description='package URL', Required=False, Default=''), + CommandIn(Name='pyqi_package', DataType=bool, + Description='Flag to indicate if this is a pyqi package', + Required=False, Default=False) + ]) + + CommandOuts = ParameterCollection([]) + + _formatted_base_writes = [('COPYING.txt', COPYING), + ('README.md', README), + ('setup.py', SETUP), + ('ChangeLog.md', CHANGELOG), + ('MANIFEST.in', MANIFEST), + ('Makefile', MAKEFILE)] + + def _create_directories_pyqi(self, pkg_name): + os.makedirs('%s/commands' % pkg_name) + os.makedirs('%s/interfaces/optparse/config' % pkg_name) + os.makedirs('doc') + os.makedirs('scripts') + os.makedirs('tests') + + def _create_directories_nonpyqi(self, pkg_name): + os.makedirs('%s' % pkg_name) + os.makedirs('doc') + os.makedirs('scripts') + os.makedirs('tests') + + def _create_empty_inits(self, pkg_name): + touch('%s/commands/__init__.py' % pkg_name) + touch('%s/interfaces/__init__.py' % pkg_name) + touch('%s/interfaces/optparse/__init__.py' % pkg_name) + touch('%s/interfaces/optparse/config/__init__.py' % pkg_name) + + def _create_pkg_init(self, pkg_name): + payload = PKG_INIT % {'pkg_name': pkg_name} + write_file('%s/__init__.py' % pkg_name, payload) + + def _create_pyqi_driver(self, pkg_name): + payload = PYQI_DRIVER % {'pkg_name': pkg_name} + write_file('scripts/%s' % pkg_name, payload) + os.chmod('scripts/%s' % pkg_name, 0755) + + def _create_sphinx(self, kwargs): + d = {'path': 'doc', + 'sep': False, + 'dot': '_', + 'project': kwargs['pkg_name'], + 'version': '0.1', + 'author': kwargs['author'], + 'release': '-dev', + 'suffix': '.rst', + 'master': 'index', + 'epub': False, + 'ext_autodoc': True, + 'ext_doctest': True, + 'ext_intersphinx': True, + 'ext_todo': False, + 'ext_viewcode': True, + 'ext_coverage': False, + 'ext_pngmath': False, + 'ext_mathjax': False, + 'ext_ifconfig': False, + 'makefile': True, + 'batchfile': False} + generate(d, silent=True) + + def _create_git(self): + init_cmd = ['git', 'init', '--quiet'] + add_cmd = ['git', 'add', '*'] + commit_cmd = ['git', 'commit', '-qm', '"initial creation"'] + + so, se, ret = pyqi_system_call(init_cmd, shell=False) + so, se, ret = pyqi_system_call(add_cmd, shell=False) + so, se, ret = pyqi_system_call(commit_cmd, shell=False) + + def run(self, **kwargs): + cwd = os.getcwd() + os.chdir(kwargs['pkg_base']) + os.mkdir(kwargs['pkg_name']) + os.chdir(kwargs['pkg_name']) + + if kwargs['pyqi_package']: + self._create_directories_pyqi(kwargs['pkg_name']) + self._create_empty_inits(kwargs['pkg_name']) + self._create_pyqi_driver(kwargs['pkg_name']) + kwargs['pyqi_pip_requires'] = '"pyqi == %s"' % __version__ + else: + self._create_directories_nonpyqi(kwargs['pkg_name']) + kwargs['pyqi_pip_requires'] = "" + + self._create_pkg_init(kwargs['pkg_name']) + + for fname, formatted_str in self._formatted_base_writes: + write_file(fname, formatted_str % kwargs) + + self._create_sphinx(kwargs) + self._create_git() + + os.chdir(cwd) + return {} + +CommandConstructor = MakePackage diff --git a/pyqi/interfaces/optparse/config/make_package.py b/pyqi/interfaces/optparse/config/make_package.py new file mode 100644 index 0000000..233eedc --- /dev/null +++ b/pyqi/interfaces/optparse/config/make_package.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +from __future__ import division + +__credits__ = [] + +from pyqi.core.interfaces.optparse import (OptparseUsageExample, + OptparseOption, OptparseResult) +from pyqi.core.command import (make_command_in_collection_lookup_f, + make_command_out_collection_lookup_f) +from pyqi.commands.make_package import CommandConstructor + +# If you need access to input or output handlers provided by pyqi, consider +# importing from the following modules: +# pyqi.core.interfaces.optparse.input_handler +# pyqi.core.interfaces.optparse.output_handler +# pyqi.interfaces.optparse.input_handler +# pyqi.interfaces.optparse.output_handler + +# Convenience function for looking up parameters by name. +cmd_in_lookup = make_command_in_collection_lookup_f(CommandConstructor) +cmd_out_lookup = make_command_out_collection_lookup_f(CommandConstructor) + +# Examples of how the command can be used from the command line using an +# optparse interface. +usage_examples = [ + OptparseUsageExample(ShortDesc="Stub out a pyqi package", + LongDesc="Construct the base structure for a pyqi package", + Ex="%prog --pkg-base=$HOME/gitables --pkg-name=FTW --author=foo --email=bar --bar some_file --pyqi-package"), + OptparseUsageExample(ShortDesc="Stub out a non-pyqi package", + LongDesc="Construct the base structure for a regular package", + Ex="%prog --pkg-base=$HOME/gitables --pkg-name=FTW --author=foo --email=bar --bar some_file"), +] + +# inputs map command line arguments and values onto Parameters. It is possible +# to define options here that do not exist as parameters, e.g., an output file. +inputs = [ + OptparseOption(Parameter=cmd_in_lookup('author'), + Type=str, + Action='store', # default is 'store', change if desired + Handler=None, # must be defined if desired + ShortName=None, # must be defined if desired + ), + OptparseOption(Parameter=cmd_in_lookup('email'), + Type=str, + Action='store', # default is 'store', change if desired + Handler=None, # must be defined if desired + ShortName=None, # must be defined if desired + ), + OptparseOption(Parameter=cmd_in_lookup('pkg_base'), + Type=str, + Action='store', # default is 'store', change if desired + Handler=None, # must be defined if desired + ShortName=None, # must be defined if desired + ), + OptparseOption(Parameter=cmd_in_lookup('pkg_name'), + Type=str, + Action='store', # default is 'store', change if desired + Handler=None, # must be defined if desired + ShortName=None, # must be defined if desired + ), + OptparseOption(Parameter=cmd_in_lookup('url'), + Type=str, + Action='store', # default is 'store', change if desired + Handler=None, # must be defined if desired + ShortName=None, # must be defined if desired + ), + OptparseOption(Parameter=cmd_in_lookup('pyqi_package'), + Type=None, + Action='store_true', + Handler=None, + ShortName=None) +] + +outputs = [] diff --git a/tests/test_core/test_interface.py b/tests/test_core/test_interface.py index 63e05c0..8550d0f 100644 --- a/tests/test_core/test_interface.py +++ b/tests/test_core/test_interface.py @@ -19,10 +19,10 @@ class TopLevelTests(TestCase): def test_get_command_names(self): """Test that command names are returned from a config directory.""" - exp = ['make-bash-completion', 'make-command', 'make-optparse', - 'make-release', 'serve-html-interface'] + exp = ['make-bash-completion', 'make-command', 'make-optparse', + 'make-release', 'make-package', 'serve-html-interface'] obs = get_command_names('pyqi.interfaces.optparse.config') - self.assertEqual(obs, exp) + self.assertEqual(sorted(obs), sorted(exp)) # Invalid config dir. with self.assertRaises(ImportError):