Skip to content

Rework command handling #463

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
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
95 changes: 67 additions & 28 deletions pip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import difflib

from pip.backwardcompat import walk_packages
from pip.basecommand import command_dict, load_command, load_all_commands, command_names
from pip.baseparser import parser
from pip.exceptions import InstallationError
from pip.log import logger
from pip.util import get_installed_distributions
from pip.baseparser import create_main_parser
from pip.commands import commands, get_similar_commands, get_summaries
from pip.exceptions import CommandError, PipError
from pip.commands import commands, get_similar_commands, get_summaries


def autocomplete():
Expand All @@ -30,8 +32,8 @@ def autocomplete():
current = cwords[cword-1]
except IndexError:
current = ''
load_all_commands()
subcommands = [cmd for cmd, cls in command_dict.items() if not cls.hidden]

subcommands = [cmd for cmd, cls in commands.items() if not cls.hidden]
options = []
# subcommand
try:
Expand All @@ -55,7 +57,7 @@ def autocomplete():
for dist in installed:
print(dist)
sys.exit(1)
subcommand = command_dict.get(subcommand_name)
subcommand = commands.get(subcommand_name)
options += [(opt.get_opt_string(), opt.nargs)
for opt in subcommand.parser.option_list
if opt.help != optparse.SUPPRESS_HELP]
Expand All @@ -71,11 +73,16 @@ def autocomplete():
opt_label += '='
print(opt_label)
else:
parser = create_main_parser()

# show options of main parser only when necessary
if current.startswith('-') or current.startswith('--'):
subcommands += [opt.get_opt_string()
for opt in parser.option_list
if opt.help != optparse.SUPPRESS_HELP]
opts = [i.option_list for i in parser.option_groups]
opts = (o for it in opts for o in it) #flatten without reduce

subcommands += [i.get_opt_string() for i in opts
if i.help != optparse.SUPPRESS_HELP]

print(' '.join([x for x in subcommands if x.startswith(current)]))
sys.exit(1)

Expand All @@ -88,31 +95,63 @@ def version_control():
__import__(modname)


def parseopts(args):
parser = create_main_parser()

# create command listing
command_summaries = get_summaries()

description = ['Commands:']
description.extend([' %-20s %s' % (i, j) for i,j in command_summaries])

parser.description = '\n'.join(description)

options, args = parser.parse_args(args)

if options.version:
sys.stdout.write(parser.version)
sys.stdout.write(os.linesep)
sys.exit()

# pip || pip help || pip --help -> print_help()
if options.help or not args or (args[0] == 'help' and len(args) == 1):
parser.print_help()
sys.exit()

if not args:
msg = 'You must give a command (use "pip --help" to see a list of commands)'
raise CommandError(msg)

command = args[0].lower()

if command not in commands:
guess = get_similar_commands(command)

msg = ['unknown command "%s"' % command]
if guess:
msg.append('maybe you meant "%s"' % guess)

raise CommandError(' - '.join(msg)) # TODO:

return command, options, args, parser


def main(initial_args=None):
if initial_args is None:
initial_args = sys.argv[1:]

autocomplete()
version_control()
options, args = parser.parse_args(initial_args)
if options.help and not args:
args = ['help']
if not args:
parser.error('You must give a command (use "pip help" to see a list of commands)')
command = args[0].lower()
load_command(command)
if command not in command_dict:
close_commands = difflib.get_close_matches(command, command_names())
if close_commands:
guess = close_commands[0]
if args[1:]:
guess = "%s %s" % (guess, " ".join(args[1:]))
else:
guess = 'install %s' % command
error_dict = {'arg': command, 'guess': guess,
'script': os.path.basename(sys.argv[0])}
parser.error('No command by the name %(script)s %(arg)s\n '
'(maybe you meant "%(script)s %(guess)s")' % error_dict)
command = command_dict[command]

try:
cmd_name, options, args, parser = parseopts(initial_args)
except PipError:
e = sys.exc_info()[1]
sys.stderr.write(str(e))
sys.stderr.write(os.linesep)
sys.exit(1)

command = commands[cmd_name](parser) #see baseparser.Command
return command.main(args[1:], options)


Expand Down
94 changes: 50 additions & 44 deletions pip/basecommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@
import sys
import traceback
import time
import optparse

from pip import commands
from pip.log import logger
from pip.baseparser import parser, ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.download import urlopen
from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
from pip.exceptions import (BadCommand, InstallationError, UninstallationError,
CommandError)
from pip.backwardcompat import StringIO, walk_packages
from pip.status_codes import SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND

__all__ = ['Command']

__all__ = ['command_dict', 'Command', 'load_all_commands',
'load_command', 'command_names']

command_dict = {}

# for backwards compatibiliy
get_proxy = urlopen.get_proxy
Expand All @@ -30,29 +27,59 @@ class Command(object):
usage = None
hidden = False

def __init__(self):
def __init__(self, main_parser):
assert self.name
self.parser = ConfigOptionParser(
usage=self.usage,
prog='%s %s' % (sys.argv[0], self.name),
version=parser.version,
formatter=UpdatingDefaultsHelpFormatter(),
name=self.name)
for option in parser.option_list:

prog = os.path.basename(sys.argv[0])

parser_kw = {
'usage' : self.usage,
'prog' : '%s %s' % (prog, self.name),
'add_help_option' : False,
'formatter' : UpdatingDefaultsHelpFormatter(),
'name' : self.name, }

self.main_parser = main_parser
self.parser = ConfigOptionParser(**parser_kw)

# Each command should populate this option group with the options
# that are specific to it.
self.command_group = optparse.OptionGroup(self.parser, 'Command options')

# Re-add all options and option groups. This is quite lame :\
# Copies all general options from the main parser.
for group in main_parser.option_groups:
self._copy_option_group(self.parser, group)

self._copy_options(self.parser, main_parser.option_list)

def _copy_options(self, parser, options):
"""Populate an option parser or group with options."""
for option in options:
if not option.dest or option.dest == 'help':
# -h, --version, etc
continue
self.parser.add_option(option)
command_dict[self.name] = self
parser.add_option(option)

def _copy_option_group(self, parser, group):
"""Copy option group (including options) to another parser."""
new_group = optparse.OptionGroup(parser, group.title)
self._copy_options(new_group, group.option_list)

parser.add_option_group(new_group)

def merge_options(self, initial_options, options):
# Make sure we have all global options carried over
for attr in ['log', 'proxy', 'require_venv',
'log_explicit_levels', 'log_file',
'timeout', 'default_vcs',
'skip_requirements_regex',
'no_input', 'exists_action']:
setattr(options, attr, getattr(initial_options, attr) or getattr(options, attr))

opts = ('log', 'proxy', 'require_venv',
'log_explicit_levels', 'log_file',
'timeout', 'default_vcs',
'skip_requirements_regex',
'no_input', 'exists_action')

for attr in opts:
val = getattr(initial_options, attr) or getattr(options, attr)
setattr(options, attr, val)

options.quiet += initial_options.quiet
options.verbose += initial_options.verbose

Expand Down Expand Up @@ -170,24 +197,3 @@ def open_logfile(filename, mode='a'):
log_fp.write('%s\n' % ('-'*60))
log_fp.write('%s run on %s\n' % (sys.argv[0], time.strftime('%c')))
return log_fp


def load_command(name):
full_name = 'pip.commands.%s' % name
if full_name in sys.modules:
return
try:
__import__(full_name)
except ImportError:
pass


def load_all_commands():
for name in command_names():
load_command(name)


def command_names():
names = set((pkg[1] for pkg in walk_packages(path=commands.__path__)))
return list(names)

Loading