Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
First commit
  • Loading branch information
GaretJax committed Sep 10, 2015
0 parents commit bd0b8a0
Show file tree
Hide file tree
Showing 16 changed files with 512 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .coveragerc
@@ -0,0 +1,16 @@
[run]
branch = True
omit = setup.py, */migrations/*, */conftest.py

[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
NOCOV

# Don't complain if non-runnable code isn't run:
if __name__ == .__main__.:

[html]
directory = .htmlcov
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
__pycache__
*.pyc
/.cache
/.coverage
/.tox
/build
/dist
/docs/_build
15 changes: 15 additions & 0 deletions .travis.yml
@@ -0,0 +1,15 @@
language: python

python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"

install:
- pip install -r requirements-test.txt
- pip install coveralls

script: py.test --flake8 --cov djclick

after_success: coveralls
5 changes: 5 additions & 0 deletions AUTHORS.rst
@@ -0,0 +1,5 @@
====================
Project contributors
====================

* Jonathan Stoppani <jonathan@stoppani.name>
41 changes: 41 additions & 0 deletions CONTRIBUTING.rst
@@ -0,0 +1,41 @@
=======================
Contribution guidelines
=======================


Running tests
=============

Use ``tox``::

pip install tox
tox


Creating a release
==================

* Checkout the ``master`` branch.
* Pull the latest changes from ``origin``.
* Make sure ``check-manifest`` is happy.
* Increment the version number.
* Set the correct title for the release in ``HISTORY.rst``.
* If needed update the ``AUTHORS.rst`` file with new contributors.
* Commit everything and make sure the working tree is clean.
* Push everything to github and make sure the tests pass on Travis::

git push origin master

* Build and upload the release::

./setup.py publish

* Tag the release::

git tag -a "v$(python setup.py --version)" -m "$(python setup.py --name) release version $(python setup.py --version)"

* Push everything to github::

git push --tags origin master

* Add the title for the next release to `HISTORY.rst`
9 changes: 9 additions & 0 deletions HISTORY.rst
@@ -0,0 +1,9 @@
=======
History
=======


0.1.0 – Unreleased
==================

* Initial release
19 changes: 19 additions & 0 deletions LICENSE
@@ -0,0 +1,19 @@
Copyright (c) 2015 Jonathan Stoppani

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
15 changes: 15 additions & 0 deletions MANIFEST.in
@@ -0,0 +1,15 @@
include AUTHORS.rst
include CONTRIBUTING.rst
include HISTORY.rst
include LICENSE
include README.rst

include requirements-dev.txt
include requirements-test.txt
include requirements.txt

exclude .coveragerc
exclude .travis.yml
exclude tox.ini

prune docs
55 changes: 55 additions & 0 deletions README.rst
@@ -0,0 +1,55 @@
============
Django Click
============

.. image:: https://img.shields.io/travis/GaretJax/django-click.svg
:target: https://travis-ci.org/GaretJax/django-click

.. image:: https://img.shields.io/pypi/v/django-click.svg
:target: https://pypi.python.org/pypi/django-click

.. image:: https://img.shields.io/pypi/dm/django-click.svg
:target: https://pypi.python.org/pypi/django-click

.. image:: https://img.shields.io/coveralls/GaretJax/django-click/master.svg
:target: https://coveralls.io/r/GaretJax/django-click?branch=master

.. image:: https://img.shields.io/badge/docs-latest-brightgreen.svg
:target: http://django-click.readthedocs.org/en/latest/

.. image:: https://img.shields.io/pypi/l/django-click.svg
:target: https://github.com/GaretJax/django-click/blob/develop/LICENSE

.. image:: https://img.shields.io/requires/github/GaretJax/django-click.svg
:target: https://requires.io/github/GaretJax/django-click/requirements/?branch=master

.. .. image:: https://img.shields.io/codeclimate/github/GaretJax/django-click.svg
.. :target: https://codeclimate.com/github/GaretJax/django-click
django-click is a library to easily write django management commands using the
click command line library.

* Free software: MIT license
* Documentation: http://django-click.rtfd.org


Installation
============

::

pip install django-click


Example
=======

Create a command module as you would usually do, but instead of creating a
class, just put a djclick command into it::

import djclick as click

@click.command()
@click.argument('name')
def command(name):
click.secho('Hello, {}'.format(name), fg='red')
14 changes: 14 additions & 0 deletions djclick/__init__.py
@@ -0,0 +1,14 @@
"""
Support click in Django management commands.
"""

import click
from click import * # NOQA
from .adapter import CommandRegistrator


__version__ = '0.1.0'
__url__ = 'https://github.com/GaretJax/django-click'
__all__ = click.__all__

command = CommandRegistrator
150 changes: 150 additions & 0 deletions djclick/adapter.py
@@ -0,0 +1,150 @@
import sys

import six

import click

from django import get_version


class ParserAdapter(object):
def parse_args(self, args):
return (self, None)


class CommandAdapter(click.Command):
use_argparse = False

def run_from_argv(self, argv):
"""
Called when run from the command line.
"""
return self.main(args=argv[2:])

def create_parser(self, progname, subcommand):
"""
Called when run through `call_command`.
"""
return ParserAdapter()

def map_names(self):
for param in self.params:
for opt in param.opts:
yield opt.lstrip('--').replace('-', '_'), param.name

def execute(self, *args, **kwargs):
"""
Called when run through `call_command`. `args` are passed through,
while `kwargs` is the __dict__ of the return value of
`self.create_parser('', name)` updated with the kwargs passed to
`call_command`.
"""
# Remove internal Django command handling machinery
kwargs.pop('skip_checks')

with self.make_context('', list(args)) as ctx:
# Rename kwargs to to the appropriate destination argument name
opt_mapping = dict(self.map_names())
arg_options = {opt_mapping.get(key, key): value
for key, value in six.iteritems(kwargs)}

# Update the context with the passed (renamed) kwargs
ctx.params.update(arg_options)

# Invoke the command
self.invoke(ctx)


def register_on_context(ctx, param, value):
setattr(ctx, param.name, value)
return value


def suppress_colors(ctx, param, value):
if value:
ctx.color = False
return value


class CommandRegistrator(object):
common_options = [
click.option(
'-v', '--verbosity',
expose_value=False,
callback=register_on_context,
type=click.Choice(str(s) for s in range(4)),
help=('Verbosity level; 0=minimal output, 1=normal ''output, '
'2=verbose output, 3=very verbose output.'),
),
click.option(
'--settings',
metavar='SETTINGS',
expose_value=False,
help=('The Python path to a settings module, e.g. '
'"myproject.settings.main". If this is not provided, the '
'DJANGO_SETTINGS_MODULE environment variable will be used.'),
),
click.option(
'--pythonpath',
metavar='PYTHONPATH',
expose_value=False,
help=('A directory to add to the Python path, e.g. '
'"/home/djangoprojects/myproject".'),
),
click.option(
'--traceback',
is_flag=True,
expose_value=False,
callback=register_on_context,
help='Raise on CommandError exceptions.',
),
click.option(
'--no-color',
is_flag=True,
expose_value=False,
callback=suppress_colors,
help='Do not colorize the command output.',
),
]

def __init__(self, **kwargs):
self.kwargs = kwargs
self.version = self.kwargs.pop('version', get_version())

context_settings = kwargs.setdefault('context_settings', {})
context_settings['help_option_names'] = ['-h', '--help']

def get_params(self, name):
def show_help(ctx, param, value):
if value and not ctx.resilient_parsing:
ctx.info_name += ' ' + name
click.echo(ctx.get_help(), color=ctx.color)
ctx.exit()

return [
click.version_option(version=self.version, message='%(version)s'),
click.option('-h', '--help', is_flag=True, is_eager=True,
expose_value=False, callback=show_help,
help='Show this message and exit.',),
] + self.common_options

def __call__(self, func):
module = sys.modules[func.__module__]

# Get the command name as Django expects it
self.name = func.__module__.rsplit('.', 1)[-1]

# Build the click command
decorators = [
click.command(name=self.name, cls=CommandAdapter, **self.kwargs),
] + self.get_params(self.name)

for decorator in reversed(decorators):
func = decorator(func)

# Django expects the command to be callable (it instantiates the class
# pointed at by the `Command` module-level property)...
# ...let's make it happy.
module.Command = lambda: func

return func
22 changes: 22 additions & 0 deletions requirements-dev.txt
@@ -0,0 +1,22 @@
-r requirements-test.txt

# Automation
Fabric
livereload

# Packaging
wheel
check-manifest

# Code linting
flake8
mccabe
pep8
flake8-todo
pep8-naming
pyflakes

# Documentation
Sphinx
sphinx-autobuild
sphinx_rtd_theme
5 changes: 5 additions & 0 deletions requirements-test.txt
@@ -0,0 +1,5 @@
-r requirements.txt

pytest
pytest-cov
pytest-flake8
1 change: 1 addition & 0 deletions requirements.txt
@@ -0,0 +1 @@
six>=1.9.0

0 comments on commit bd0b8a0

Please sign in to comment.