Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
language: python

sudo: false
cache:
directories:
- ~/.cache/pip
cache: pip

python:
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
- pypy
- pypy3

install:
- pip install coveralls
- pip install -e .\[dev\]
- pip install -e .\[all\]

script:
- py.test tests --cov click_plugins --cov-report term-missing
- pytest --cov click_plugins --cov-report term-missing

after_success:
- coveralls
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
New BSD License

Copyright (c) 2015-2016, Kevin D. Wurster, Sean C. Gillies
Copyright (c) 2015-2017, Kevin D. Wurster, Sean C. Gillies
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ Developing
$ git clone https://github.com/click-contrib/click-plugins.git
$ cd click-plugins
$ pip install -e .\[dev\]
$ py.test tests --cov click_plugins --cov-report term-missing
$ pytest tests --cov click_plugins --cov-report term-missing


Changelog
Expand Down
2 changes: 1 addition & 1 deletion click_plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def subcommand(arg):
__license__ = '''
New BSD License

Copyright (c) 2015-2016, Kevin D. Wurster, Sean C. Gillies
Copyright (c) 2015-2017, Kevin D. Wurster, Sean C. Gillies
All rights reserved.

Redistribution and use in source and binary forms, with or without
Expand Down
87 changes: 52 additions & 35 deletions click_plugins/core.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
"""
Core components for click_plugins
"""
"""Core components for click_plugins"""


import click

import os
import sys
import traceback

import click

def with_plugins(plugins):

"""
A decorator to register external CLI commands to an instance of
`click.Group()`.
def with_plugins(entry_points):

"""A decorator to register external CLI commands to an instance of
``click.Group()``.

Parameters
----------
plugins : iter
An iterable producing one `pkg_resources.EntryPoint()` per iteration.
attrs : **kwargs, optional
Additional keyword arguments for instantiating `click.Group()`.
entry_points : iter
An iterable producing one ``pkg_resources.EntryPoint()`` per
iteration.

Returns
-------
Expand All @@ -30,16 +26,18 @@ def with_plugins(plugins):

def decorator(group):
if not isinstance(group, click.Group):
raise TypeError("Plugins can only be attached to an instance of click.Group()")
raise TypeError(
"Plugins can only be attached to an instance of "
"'click.Group()'.")

for entry_point in plugins or ():
for ep in entry_points:
try:
group.add_command(entry_point.load())
group.add_command(ep.load())
except Exception:
# Catch this so a busted plugin doesn't take down the CLI.
# Handled by registering a dummy command that does nothing
# other than explain the error.
group.add_command(BrokenCommand(entry_point.name))
group.add_command(BrokenCommand(ep.name))

return group

Expand All @@ -48,41 +46,60 @@ def decorator(group):

class BrokenCommand(click.Command):

"""
Rather than completely crash the CLI when a broken plugin is loaded, this
class provides a modified help message informing the user that the plugin is
broken and they should contact the owner. If the user executes the plugin
or specifies `--help` a traceback is reported showing the exception the
plugin loader encountered.
"""Rather than completely crash the CLI when a broken plugin is
loaded, this class provides a modified help message informing the
user that the plugin is broken and they should contact the owner.
If the user executes the plugin or specifies ``--help`` a traceback
is reported showing the exception the plugin loader encountered.
"""

def __init__(self, name):

"""
Define the special help messages after instantiating a `click.Command()`.
"""Define the special help messages after instantiating a
``click.Command()``.

Parameters
----------
name : str
For ``click.Command()``.
"""

click.Command.__init__(self, name)

util_name = os.path.basename(sys.argv and sys.argv[0] or __file__)

if os.environ.get('CLICK_PLUGINS_HONESTLY'): # pragma no cover
if os.environ.get('CLICK_PLUGINS_HONESTLY') == 'TRUE':
icon = u'\U0001F4A9'
else:
icon = u'\u2020'

# Override the command's short help with a warning message about how
# the command is not functioning properly
prog_name = os.path.basename(sys.argv and sys.argv[0] or __file__)
self.short_help = (
u"{icon} Warning: could not load plugin. See: "
"'$ {prog_name} {name} --help'.".format(
icon=icon, prog_name=prog_name, name=name))

# Override the command's long help with the exception traceback.
# The call to 'traceback.format_exec()' function attempts to
# access 'Exception.__context__' which doesn't exist on Python
# 3.3 and 3.4, but appears to exist on 2.7 and >= 3.5.
if sys.version_info >= (3, 5) or sys.version[:2] == (2, 7):
tb = traceback.format_exc(chain=True)
else:
tb = traceback.format_exc()
self.help = (
"\nWarning: entry point could not be loaded. Contact "
"its author for help.\n\n\b\n"
+ traceback.format_exc())
self.short_help = (
icon + " Warning: could not load plugin. See `%s %s --help`."
% (util_name, self.name))
"its author for help.\n\n\b\n".format(os.linesep)
+ tb)

def invoke(self, ctx):

"""
Print the traceback instead of doing nothing.
"""Print the traceback instead of doing nothing.

Parameters
----------
ctx : click.Context
CLI context.
"""

click.echo(self.help, color=ctx.color)
Expand Down
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[bdist_wheel]
universal = 1
universal = 1

[tool:pytest]
testpaths = tests
73 changes: 45 additions & 28 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#!/usr/bin/env python


"""
Setup script for click-plugins
"""
"""Setup script for click-plugins"""


import codecs
import itertools as it
import os

from setuptools import find_packages
Expand All @@ -17,22 +16,41 @@
long_desc = f.read().strip()


version = None
author = None
email = None
source = None
def parse_dunder_line(string):

"""Take a line like:

"__version__ = '0.0.8'"

and turn it into a tuple:

('__version__', '0.0.8')

Not very fault tolerant.
"""

# Split the line and remove outside quotes
variable, value = (s.strip() for s in string.split('=')[:2])
value = value[1:-1].strip()
return variable, value


with open(os.path.join('click_plugins', '__init__.py')) as f:
for line in f:
if line.strip().startswith('__version__'):
version = line.split('=')[1].strip().replace('"', '').replace("'", '')
elif line.strip().startswith('__author__'):
author = line.split('=')[1].strip().replace('"', '').replace("'", '')
elif line.strip().startswith('__email__'):
email = line.split('=')[1].strip().replace('"', '').replace("'", '')
elif line.strip().startswith('__source__'):
source = line.split('=')[1].strip().replace('"', '').replace("'", '')
elif None not in (version, author, email, source):
break
dunders = dict(map(
parse_dunder_line, filter(lambda l: l.strip().startswith('__'), f)))
version = dunders['__version__']
author = dunders['__author__']
email = dunders['__email__']
source = dunders['__source__']


extras_require = {
'test': [
'pytest>=3.0',
'pytest-cov',
]
}
extras_require['all'] = list(it.chain(*extras_require.values()))


setup(
Expand All @@ -45,18 +63,17 @@
'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
],
description="An extension module for click to enable registering CLI commands "
"via setuptools entry-points.",
extras_require={
'dev': [
'pytest',
'pytest-cov',
'wheel',
'coveralls'
],
},
description="An extension module for click to enable registering CLI "
"commands via setuptools entry-points.",
extras_require=extras_require,
include_package_data=True,
install_requires=['click>=3.0'],
keywords='click plugin setuptools entry-point',
Expand Down
2 changes: 1 addition & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# This file is required for some of the tests of Python 2
"""This file is required for some tests on Python 2."""
5 changes: 4 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""``pytest`` fixtures."""


from click.testing import CliRunner

import pytest


@pytest.fixture(scope='function')
def runner(request):
def runner():
return CliRunner()
Loading