forked from saippuakauppias/django-maven
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored command line argument parsing (fixes saippuakauppias#7)
- Loading branch information
Showing
21 changed files
with
345 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import argparse | ||
|
||
from django.core.management.base import BaseCommand | ||
|
||
|
||
class MavenBaseCommand(BaseCommand): | ||
"""The *argparse* compatible command""" | ||
|
||
def add_arguments(self, parser): | ||
""" | ||
:type parser: argparse.ArgumentParser | ||
""" | ||
parser.add_argument('args', nargs=argparse.REMAINDER) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,84 @@ | ||
import sys | ||
from optparse import OptionParser | ||
|
||
from django.conf import settings | ||
from django.core.management import get_commands, load_command_class | ||
from django.core.management.base import (BaseCommand, handle_default_options, | ||
CommandError) | ||
|
||
from django.core.management.base import CommandError, handle_default_options | ||
from django_maven.compat import MavenBaseCommand, OutputWrapper | ||
from raven import Client | ||
|
||
from django_maven.compat import OutputWrapper | ||
|
||
VERBOSE_OUTPUT = 2 | ||
|
||
class Command(BaseCommand): | ||
|
||
class Command(MavenBaseCommand): | ||
help = 'Capture exceptions and send in Sentry' | ||
args = '<command>' | ||
|
||
def _get_subcommand_class(self, command): | ||
commands = get_commands() | ||
app_name = commands[command] | ||
return load_command_class(app_name, command) | ||
def __init__(self, *args, **kw): | ||
super(MavenBaseCommand, self).__init__(*args, **kw) | ||
self._argv = None | ||
|
||
def _write_error_in_stderr(self, exc): | ||
stderr = getattr(self, 'stderr', OutputWrapper(sys.stderr, | ||
self.style.ERROR)) | ||
stderr.write('%s: %s' % (exc.__class__.__name__, exc)) | ||
sys.exit(1) | ||
def run_from_argv(self, argv): | ||
self._argv = argv | ||
return super(Command, self).run_from_argv(argv) | ||
|
||
def usage(self, subcommand): | ||
usage = 'Usage: %s %s [command options]' % (subcommand, self.args) | ||
if self.help: | ||
return '%s\n\n%s' % (usage, self.help) | ||
else: | ||
return usage | ||
def execute(self, *args, **options): | ||
if not args: | ||
self.print_help(self._argv[0], self._argv[1]) | ||
return | ||
|
||
subcommand = self._get_subcommand_class(args[0]) | ||
subcommand_argv = [self._argv[0]] + list(args) | ||
|
||
# this is a lightweight version of the BaseCommand.run_from_argv | ||
# it should be compatible with Django-1.5..1.9 | ||
subcommand_args, subcommand_options = self._handle_argv( | ||
subcommand, subcommand_argv) | ||
try: | ||
subcommand.execute(*subcommand_args, **subcommand_options) | ||
except Exception as e: | ||
if not isinstance(e, CommandError): | ||
if int(options['verbosity']) >= VERBOSE_OUTPUT: | ||
# self.stderr is not guaranteed to be set here | ||
stderr = getattr(self, 'stderr', OutputWrapper( | ||
sys.stderr, self.style.ERROR)) | ||
stderr.write('Beautiful is better than %s. ' | ||
'Errors should never pass silently.' % | ||
(e.__class__.__name__)) | ||
|
||
def create_parser(self, prog_name, subcommand, subcommand_class): | ||
if hasattr(self, 'use_argparse') and self.use_argparse: | ||
return super(Command, self).create_parser(prog_name, subcommand) | ||
sentry = self._get_sentry() | ||
if sentry: | ||
sentry.captureException() | ||
# use default 'maven' options to deal with traceback | ||
raise | ||
|
||
def _get_sentry(self): | ||
if hasattr(settings, 'SENTRY_DSN'): | ||
dsn = settings.SENTRY_DSN | ||
elif hasattr(settings, 'RAVEN_CONFIG'): | ||
dsn = settings.RAVEN_CONFIG.get('dsn') | ||
else: | ||
return OptionParser(prog=prog_name, | ||
usage=subcommand_class.usage(subcommand), | ||
version=subcommand_class.get_version(), | ||
option_list=subcommand_class.option_list) | ||
return None | ||
|
||
def run_from_argv(self, argv): | ||
if len(argv) <= 2 or argv[2] in ['-h', '--help']: | ||
stdout = OutputWrapper(sys.stdout) | ||
stdout.write(self.usage(argv[1])) | ||
sys.exit(1) | ||
sentry = Client(dsn) | ||
if not sentry.is_enabled(): | ||
return None | ||
return sentry | ||
|
||
def _get_subcommand_class(self, command): | ||
commands = get_commands() | ||
app_name = commands[command] | ||
return load_command_class(app_name, command) | ||
|
||
subcommand_class = self._get_subcommand_class(argv[2]) | ||
parser = self.create_parser(argv[0], argv[2], subcommand_class) | ||
if hasattr(self, 'use_argparse') and self.use_argparse: | ||
subcommand_class.add_arguments(parser) | ||
options = parser.parse_args(argv[3:]) | ||
def _handle_argv(self, subcommand, argv): | ||
"""The universal Django command arguments parser.""" | ||
parser = subcommand.create_parser(argv[0], argv[1]) | ||
|
||
if hasattr(subcommand, 'use_argparse') and subcommand.use_argparse: | ||
options = parser.parse_args(argv[2:]) | ||
cmd_options = vars(options) | ||
# Move positional args out of options to mimic legacy optparse | ||
args = cmd_options.pop('args', ()) | ||
else: | ||
options, args = parser.parse_args(argv[3:]) | ||
options, args = parser.parse_args(argv[2:]) | ||
cmd_options = vars(options) | ||
handle_default_options(options) | ||
try: | ||
subcommand_class.execute(*args, **options.__dict__) | ||
except Exception as e: | ||
if not isinstance(e, CommandError): | ||
if hasattr(settings, 'SENTRY_DSN'): | ||
dsn = settings.SENTRY_DSN | ||
elif hasattr(settings, 'RAVEN_CONFIG'): | ||
dsn = settings.RAVEN_CONFIG.get('dsn') | ||
else: | ||
raise | ||
sentry = Client(dsn) | ||
if not sentry.is_enabled(): | ||
raise | ||
sentry.get_ident(sentry.captureException()) | ||
|
||
self._write_error_in_stderr(e) | ||
return args, cmd_options |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import types | ||
from optparse import AmbiguousOptionError, BadOptionError | ||
|
||
from django.core.management.base import BaseCommand | ||
|
||
|
||
class MavenBaseCommand(BaseCommand): | ||
"""The *optparse* compatible command | ||
manage.py maven [maven options] subcommand [subcommand options] | ||
""" | ||
|
||
@property | ||
def use_argparse(self): | ||
return False | ||
|
||
def create_parser(self, prog_name, subcommand): | ||
parser = super(MavenBaseCommand, self).create_parser( | ||
prog_name, subcommand) | ||
parser._process_args = types.MethodType(_process_args, parser) | ||
return parser | ||
|
||
|
||
def _process_args(self, largs, rargs, values): | ||
max_own_positional_args = 1 | ||
|
||
while rargs: | ||
try: | ||
arg = rargs[0] | ||
# XXX: e.generalov@ added condition to skip processing | ||
# arguments after subcommand name | ||
if len(largs) >= max_own_positional_args: | ||
return # stop now, leave this arg in rargs | ||
# We handle bare "--" explicitly, and bare "-" is handled by the | ||
# standard arg handler since the short arg case ensures that the | ||
# len of the opt string is greater than 1. | ||
elif arg == '--': | ||
del rargs[0] | ||
return | ||
elif arg[0:2] == '--': | ||
# process a single long option (possibly with value(s)) | ||
self._process_long_opt(rargs, values) | ||
elif arg[:1] == '-' and len(arg) > 1: | ||
# process a cluster of short options (possibly with | ||
# value(s) for the last one only) | ||
self._process_short_opts(rargs, values) | ||
elif self.allow_interspersed_args: | ||
largs.append(arg) | ||
del rargs[0] | ||
else: | ||
return # stop now, leave this arg in rargs | ||
|
||
except (BadOptionError, AmbiguousOptionError) as e: | ||
largs.append(e.opt_str) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
try: | ||
from unittest.case import skipIf | ||
except ImportError: | ||
from unittest2.case import skipIf | ||
|
||
from django.core.management.base import BaseCommand, handle_default_options | ||
from django.test import TestCase | ||
from django_maven.management.argparse_command import \ | ||
MavenBaseCommand as ArgparseMavenBaseCommand | ||
from django_maven.management.optparse_command import \ | ||
MavenBaseCommand as OptparseMavenBaseCommand | ||
|
||
|
||
class TestCommandParserMixin(object): | ||
|
||
def test_parse_maven_default_options(self): | ||
args, options = self._handle_argv(['--verbosity', '2']) | ||
self.assertEqual(args, []) | ||
self.assertEqual(int(options['verbosity']), 2) | ||
|
||
def test_parse_subcommand(self): | ||
args, options = self._handle_argv(['subcommand']) | ||
self.assertEqual(args, ['subcommand']) | ||
|
||
def test_should_keep_subcommand_default_options(self): | ||
args, options = self._handle_argv(['subcommand', '--help']) | ||
self.assertEqual(args, ['subcommand', '--help']) | ||
|
||
def test_should_keep_subcommand_custom_options(self): | ||
args, options = self._handle_argv(['subcommand', '--foo']) | ||
self.assertEqual(args, ['subcommand', '--foo']) | ||
|
||
def test_mavens_and_subcommands_default_options_should_not_conflict(self): | ||
args, options = self._handle_argv( | ||
['--verbosity', '2', 'subcommand', '--verbosity', '3']) | ||
self.assertEqual(int(options['verbosity']), 2) | ||
self.assertEqual(args, ['subcommand', '--verbosity', '3']) | ||
|
||
def _handle_argv(self, argv): | ||
raise NotImplementedError() | ||
|
||
|
||
@skipIf(not hasattr(BaseCommand, 'use_argparse'), | ||
'This Django doesn\'t use argparse') | ||
class ArgparseCommandParserTest(TestCommandParserMixin, TestCase): | ||
""" | ||
:type parser: django.core.management.base.CommandParser""" | ||
|
||
def setUp(self): | ||
self.cmd = ArgparseMavenBaseCommand() | ||
self.parser = self.cmd.create_parser('manage.py', 'maven') | ||
|
||
def _handle_argv(self, argv): | ||
options = self.parser.parse_args(argv) | ||
cmd_options = vars(options) | ||
args = cmd_options.pop('args', ()) | ||
handle_default_options(options) | ||
return args, cmd_options | ||
|
||
|
||
class OptparseCommandParserTest(TestCommandParserMixin, TestCase): | ||
""":type parser: django.core.management.base.OptionParser""" | ||
|
||
def setUp(self): | ||
self.cmd = OptparseMavenBaseCommand() | ||
self.parser = self.cmd.create_parser('manage.py', 'maven') | ||
|
||
def _handle_argv(self, argv): | ||
options, args = self.parser.parse_args(argv) | ||
cmd_options = vars(options) | ||
handle_default_options(options) | ||
return args, cmd_options |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from optparse import make_option | ||
|
||
from django.core.management.base import BaseCommand | ||
|
||
|
||
class IgnoramusException(Exception): | ||
pass | ||
|
||
|
||
class Command(BaseCommand): | ||
help = 'Test command' | ||
|
||
option_list = BaseCommand.option_list + ( | ||
make_option('--fail', | ||
action='store_true', | ||
dest='fail', | ||
default=False, | ||
help='Raise exception'), | ||
) | ||
|
||
def handle(self, *args, **options): | ||
if options['fail']: | ||
raise IgnoramusException('use jQuery') | ||
print ('OK') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# naked file |
Oops, something went wrong.