# -*- coding: utf-8 -*-
"""
Command-Line-Interface of PyScaffold
"""
from __future__ import absolute_import, print_function
import argparse
import os.path
import sys
from datetime import date
import pyscaffold
from . import info, repo, shell, structure, templates, utils
__author__ = "Florian Wilhelm"
__copyright__ = "Blue Yonder"
__license__ = "new BSD"
[docs]def parse_args(args):
"""
Parse command line parameters
:param args: command line parameters as list of strings
:return: command line parameters as dictionary
"""
parser = argparse.ArgumentParser(
description="PyScaffold is a tool for easily putting up the scaffold "
"of a Python project.")
parser.add_argument(
dest="project",
help="project name",
metavar="PROJECT")
parser.add_argument(
"-p",
"--package",
dest="package",
required=False,
help="package name (default: project name)",
metavar="NAME")
parser.add_argument(
"-d",
"--description",
dest="description",
required=False,
help="package description (default: '')",
metavar="TEXT")
parser.add_argument(
"-u",
"--url",
dest="url",
required=False,
help="package url (default: '')",
metavar="URL")
license_choices = templates.licenses.keys()
parser.add_argument(
"-l",
"--license",
dest="license",
choices=license_choices,
required=False,
default="none",
help="package license from {choices} (default: {default})".format(
choices=str(license_choices), default="No license"),
metavar="LICENSE")
parser.add_argument(
"-f",
"--force",
dest="force",
action="store_true",
default=False,
help="force overwriting an existing directory")
parser.add_argument(
"-U",
"--update",
dest="update",
action="store_true",
default=False,
help="update an existing project by replacing the most important files"
" like setup.py etc. Use additionally --force to "
"replace all scaffold files.")
parser.add_argument(
"--with-namespace",
dest="namespace",
default="",
help="put your project inside a namespace package",
metavar="NS1[.NS2]")
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--with-cookiecutter",
dest="cookiecutter_template",
default="",
metavar="TEMPLATE",
help="additionally apply a cookiecutter template")
group.add_argument(
"--with-django",
dest="django",
action="store_true",
default=False,
help="generate Django project files")
parser.add_argument(
"--with-travis",
dest="travis",
action="store_true",
default=False,
help="generate Travis configuration files")
parser.add_argument(
"--with-pre-commit",
dest="pre_commit",
action="store_true",
default=False,
help="generate pre-commit configuration file")
parser.add_argument(
"--with-tox",
dest="tox",
action="store_true",
default=False,
help="generate Tox configuration file")
version = pyscaffold.__version__
parser.add_argument('-v',
'--version',
action='version',
version='PyScaffold {ver}'.format(ver=version))
opts = vars(parser.parse_args(args))
# Strip (back)slash when added accidentally during update
opts['project'] = opts['project'].rstrip(os.sep)
return {k: v for k, v in opts.items() if v is not None}
[docs]def get_default_opts(project_name, **aux_opts):
"""
Creates default options using auxiliary options as keyword argument
Use this function if you want to use PyScaffold from another application
in order to generate an option dictionary that can than be passed to
:obj:`create_project`.
:param project_name: name of the project
:param aux_opts: auxiliary options as keyword parameters
:return: options with default values set as dictionary
"""
# Merge the default options generated by argparse
opts = parse_args([project_name])
# Remove inadvertent double definition of project_name
aux_opts.pop('project', None)
opts.update(aux_opts)
opts.setdefault('package', utils.make_valid_identifier(opts['project']))
opts.setdefault('author', info.username())
opts.setdefault('email', info.email())
opts.setdefault('release_date', date.today().strftime('%Y-%m-%d'))
opts.setdefault('year', date.today().year)
opts.setdefault('license', 'none')
opts.setdefault('description', 'Add a short description here!')
opts.setdefault('url', 'http://...')
opts.setdefault('version', pyscaffold.__version__)
opts.setdefault('title',
'='*len(opts['project']) + '\n' + opts['project'] + '\n' +
'='*len(opts['project']))
classifiers = ['Development Status :: 4 - Beta',
'Programming Language :: Python']
opts.setdefault('classifiers', utils.list2str(
classifiers, indent=14, brackets=False, quotes=False))
opts.setdefault('url', 'http://...')
# Initialize empty list of all requirements
opts.setdefault('requirements', list())
opts['namespace'] = utils.prepare_namespace(opts['namespace'])
if opts['namespace']:
opts['root_pkg'] = opts['namespace'][0]
opts['namespace_pkg'] = ".".join([opts['namespace'][-1],
opts['package']])
else:
opts['root_pkg'] = opts['package']
opts['namespace_pkg'] = opts['package']
if opts['update']:
if not os.path.exists(project_name):
raise RuntimeError(
"Project {project} does not exist and thus cannot be "
"updated!".format(project=project_name))
opts = info.project(opts)
# Reset project name since the one from setup.cfg might be different
opts['project'] = project_name
if opts['django']:
opts['force'] = True
opts['package'] = opts['project'] # since this is required by Django
opts['requirements'].append('django')
if opts['cookiecutter_template']:
opts['force'] = True
return opts
[docs]def create_project(opts):
"""
Create the project's directory structure
:param opts: options as dictionary
"""
if opts['django']:
structure.create_django_proj(opts)
if opts['cookiecutter_template']:
structure.create_cookiecutter(opts)
proj_struct = structure.make_structure(opts)
structure.create_structure(proj_struct,
update=opts['update'] or opts['force'])
if not opts['update'] and not repo.is_git_repo(opts['project']):
repo.init_commit_repo(opts['project'], proj_struct)
[docs]def make_sanity_checks(opts):
"""
Perform some sanity checks, e.g., if git is installed.
:param opts: options as dictionary
"""
if not info.is_git_installed():
raise RuntimeError("Make sure git is installed and working.")
if not info.is_git_configured():
raise RuntimeError(
'Make sure git is configured. Run:\n' +
' git config --global user.email "you@example.com"\n' +
' git config --global user.name "Your Name"\n' +
"to set your account's default identity.")
if os.path.exists(opts['project']):
if not opts['update'] and not opts['force']:
raise RuntimeError(
"Directory {dir} already exists! Use --update to update an "
"existing project or --force to overwrite an existing "
"directory.".format(dir=opts['project']))
[docs]def main(args):
"""
PyScaffold is a tool for putting up the scaffold of a Python project.
"""
utils.check_setuptools_version()
opts = parse_args(args)
make_sanity_checks(opts)
opts = get_default_opts(opts['project'], **opts)
create_project(opts)
if opts['update'] and not opts['force']:
note = "Update accomplished!\n" \
"Please check if your setup.cfg still complies with:\n" \
"http://pyscaffold.readthedocs.org/en/v{}/configuration.html"
print(note.format(pyscaffold.__version__))
@shell.called_process_error2exit_decorator
@utils.exceptions2exit([RuntimeError])
[docs]def run():
"""
Entry point for setup.py
"""
main(sys.argv[1:])
if __name__ == '__main__':
main(sys.argv[1:])