コード例 #1
0
ファイル: cli.py プロジェクト: OddBloke/swiftly
class CLI(object):

    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """

    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '--conf', dest='conf', metavar='PATH',
            help='Path to the configuration file to use. Default: '
                 '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url', metavar='URL',
            help='URL to auth system, example: '
                 'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user', metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key', metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant', metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
                 'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods', metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 The '
                 'best order will try to be determined for you; but if you '
                 'notice it keeps making useless auth attempts and that '
                 'drives you crazy, you can override that here. All the '
                 'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region', metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
                 'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct', metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L', '--local', dest='local', metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
                 'The PATH is the path on the local file system where the '
                 'fake Swift stores its data.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy', metavar='URL',
            help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet', dest='no_snet', action='store_true',
            help='Disables the above snet value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries', metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth', dest='no_cache_auth', action='store_true',
            help='Disables the above cache-auth value if it had been set '
                 'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn', dest='no_cdn', action='store_true',
            help='Disables the above cdn value if it had been set true in the '
                 'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency', metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1 Note that some nested actions may '
                 'amplify the number of concurrent actions. For instance, a '
                 'put of an entire directory will use up to this number of '
                 'concurrent actions. A put of a segmented object will use up '
                 'to this number of concurrent actions. But, if a directory '
                 'structure put is uploading segmented objects, this nesting '
                 'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'the command started.')
        self.option_parser.add_option(
            '--no-verbose', dest='no_verbose', action='store_true',
            help='Disables the above verbose value if it had been set true in '
                 'the environment or configuration file.')
        self.option_parser.add_option(
            '-O', '--direct-object-ring',
            dest='direct_object_ring', metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
                 'will enable direct client to use this ring for all the '
                 'queries. Use of this also requires the main Swift code  '
                 'is installed and importable.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[
            :-len(args)]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get('SWIFTLY_CONF', '~/.swiftly.conf')
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in (
                'auth_url', 'auth_user', 'auth_key', 'auth_tenant',
                'auth_methods', 'region', 'direct', 'local', 'proxy', 'snet',
                'no_snet', 'retries', 'cache_auth', 'no_cache_auth', 'cdn',
                'no_cdn', 'concurrency', 'eventlet', 'no_eventlet', 'verbose',
                'no_verbose', 'direct_object_ring'):
            self._resolve_option(options, option_name, 'swiftly')
        for option_name in (
                'snet', 'no_snet', 'cache_auth', 'no_cache_auth', 'cdn',
                'no_cdn', 'eventlet', 'no_eventlet', 'verbose', 'no_verbose'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(
                    options, option_name,
                    getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ('retries', 'concurrency'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(
                    options, option_name, int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == 'help':
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(
                LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient, swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1, eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write('No Auth URL has been given.\n')
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient, auth_methods=options.auth_methods,
                auth_url=options.auth_url, auth_tenant=options.auth_tenant,
                auth_user=options.auth_user, auth_key=options.auth_key,
                auth_cache_path=auth_cache_path, region=options.region,
                snet=options.snet, attempts=options.retries + 1,
                eventlet=self.context.eventlet, verbose=self._verbose,
                http_proxy=options.proxy)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + '_'):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1:]
        else:
            environ_name = (section_name + '_' + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name,
            os.environ.get(
                environ_name,
                (self.context.conf.get(section_name, {})).get(conf_name)))

    def _perform_command(self, args):
        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write(
                        'VERBOSE %.02f ' %
                        (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()
コード例 #2
0
ファイル: cli.py プロジェクト: krvax/swiftly
class CLI(object):

    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """

    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit(".", 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
            io_manager=self.context.io_manager,
        )
        self.option_parser.add_option("-h", dest="help", action="store_true", help="Shows this help text.")
        self.option_parser.add_option(
            "--conf",
            dest="conf",
            metavar="PATH",
            help="Path to the configuration file to use. Default: " "~/.swiftly.conf",
        )
        self.option_parser.add_option(
            "-A",
            "--auth-url",
            dest="auth_url",
            metavar="URL",
            help="URL to auth system, example: " "https://identity.api.rackspacecloud.com/v2.0",
        )
        self.option_parser.add_option(
            "-U",
            "--auth-user",
            dest="auth_user",
            metavar="USER",
            help="User name for auth system, example: test:tester",
        )
        self.option_parser.add_option(
            "-K", "--auth-key", dest="auth_key", metavar="KEY", help="Key for auth system, example: testing"
        )
        self.option_parser.add_option(
            "-T",
            "--auth-tenant",
            dest="auth_tenant",
            metavar="TENANT",
            help="Tenant name for auth system, example: test If not "
            "specified and needed, the auth user will be used.",
        )
        self.option_parser.add_option(
            "--auth-methods",
            dest="auth_methods",
            metavar="name[,name[...]]",
            help="Auth methods to use with the auth system, example: "
            "auth2key,auth2password,auth2password_force_tenant,auth1 The "
            "best order will try to be determined for you; but if you "
            "notice it keeps making useless auth attempts and that "
            "drives you crazy, you can override that here. All the "
            "available auth methods are listed in the example.",
        )
        self.option_parser.add_option(
            "--region",
            dest="region",
            metavar="VALUE",
            help="Region to use, if supported by auth, example: DFW Default: "
            "default region specified by the auth response.",
        )
        self.option_parser.add_option(
            "-D",
            "--direct",
            dest="direct",
            metavar="PATH",
            help="Uses direct connect method to access Swift. Requires access "
            "to rings and backend servers. The PATH is the account "
            "path, example: /v1/AUTH_test",
        )
        self.option_parser.add_option(
            "-L",
            "--local",
            dest="local",
            metavar="PATH",
            help="Uses the local file system method to access a fake Swift. "
            "The PATH is the path on the local file system where the "
            "fake Swift stores its data.",
        )
        self.option_parser.add_option(
            "-P", "--proxy", dest="proxy", metavar="URL", help="Uses the given HTTP proxy URL."
        )
        self.option_parser.add_option(
            "-S",
            "--snet",
            dest="snet",
            action="store_true",
            help='Prepends the storage URL host name with "snet-". Mostly '
            "only useful with Rackspace Cloud Files and Rackspace "
            "ServiceNet.",
        )
        self.option_parser.add_option(
            "--no-snet",
            dest="no_snet",
            action="store_true",
            help="Disables the above snet value if it had been set true in " "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-R",
            "--retries",
            dest="retries",
            metavar="INTEGER",
            help="Indicates how many times to retry the request on a server " "error. Default: 4",
        )
        self.option_parser.add_option(
            "-C",
            "--cache-auth",
            dest="cache_auth",
            action="store_true",
            help="If set true, the storage URL and auth token are cached in "
            "your OS temporary directory as <user>.swiftly for reuse. If "
            "there are already cached values, they are used without "
            "authenticating first.",
        )
        self.option_parser.add_option(
            "--no-cache-auth",
            dest="no_cache_auth",
            action="store_true",
            help="Disables the above cache-auth value if it had been set "
            "true in the environment or configuration file.",
        )
        self.option_parser.add_option(
            "--cdn", dest="cdn", action="store_true", help="Directs requests to the CDN management interface."
        )
        self.option_parser.add_option(
            "--no-cdn",
            dest="no_cdn",
            action="store_true",
            help="Disables the above cdn value if it had been set true in the " "environment or configuration file.",
        )
        self.option_parser.add_option(
            "--concurrency",
            dest="concurrency",
            metavar="INTEGER",
            help="Sets the the number of actions that can be done "
            "simultaneously when possible (currently requires using "
            "Eventlet too). Default: 1 Note that some nested actions may "
            "amplify the number of concurrent actions. For instance, a "
            "put of an entire directory will use up to this number of "
            "concurrent actions. A put of a segmented object will use up "
            "to this number of concurrent actions. But, if a directory "
            "structure put is uploading segmented objects, this nesting "
            "could cause up to INTEGER * INTEGER concurrent actions.",
        )
        self.option_parser.add_option(
            "--eventlet",
            dest="eventlet",
            action="store_true",
            help="Enables Eventlet, if installed. This is disabled by default "
            "if Eventlet is not installed or is less than version 0.11.0 "
            "(because older Swiftly+Eventlet tends to use excessive CPU.",
        )
        self.option_parser.add_option(
            "--no-eventlet",
            dest="no_eventlet",
            action="store_true",
            help="Disables Eventlet, even if installed and version 0.11.0 or " "greater.",
        )
        self.option_parser.add_option(
            "-v",
            "--verbose",
            dest="verbose",
            action="store_true",
            help="Causes output to standard error indicating actions being "
            "taken. These output lines will be prefixed with VERBOSE and "
            "will also include the number of seconds elapsed since "
            "the command started.",
        )
        self.option_parser.add_option(
            "--no-verbose",
            dest="no_verbose",
            action="store_true",
            help="Disables the above verbose value if it had been set true in "
            "the environment or configuration file.",
        )
        self.option_parser.add_option(
            "-O",
            "--direct-object-ring",
            dest="direct_object_ring",
            metavar="PATH",
            help="The current object ring of the cluster being pinged. This "
            "will enable direct client to use this ring for all the "
            "queries. Use of this also requires the main Swift code  "
            "is installed and importable.",
        )

        self.option_parser.raw_epilog = "Commands:\n"
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split("\n")
            main_line = "  " + lines[0].split("]", 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + " " * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + "\n"
                initial_indent = " " * 24
            self.option_parser.raw_epilog += (
                textwrap.fill(" ".join(lines), width=79, initial_indent=initial_indent, subsequent_indent=" " * 24)
                + "\n"
            )

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[: -len(args)]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get("SWIFTLY_CONF", "~/.swiftly.conf")
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in (
            "auth_url",
            "auth_user",
            "auth_key",
            "auth_tenant",
            "auth_methods",
            "region",
            "direct",
            "local",
            "proxy",
            "snet",
            "no_snet",
            "retries",
            "cache_auth",
            "no_cache_auth",
            "cdn",
            "no_cdn",
            "concurrency",
            "eventlet",
            "no_eventlet",
            "verbose",
            "no_verbose",
            "direct_object_ring",
        ):
            self._resolve_option(options, option_name, "swiftly")
        for option_name in (
            "snet",
            "no_snet",
            "cache_auth",
            "no_cache_auth",
            "cdn",
            "no_cdn",
            "eventlet",
            "no_eventlet",
            "verbose",
            "no_verbose",
        ):
            if isinstance(getattr(options, option_name), basestring):
                setattr(options, option_name, getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ("retries", "concurrency"):
            if isinstance(getattr(options, option_name), basestring):
                setattr(options, option_name, int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet

                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= "0.11.0":
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess

                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess

            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == "help":
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient,
                swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring,
            )
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(tempfile.gettempdir(), "%s.swiftly" % os.environ.get("USER", "user"))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write("No Auth URL has been given.\n")
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient,
                auth_methods=options.auth_methods,
                auth_url=options.auth_url,
                auth_tenant=options.auth_tenant,
                auth_user=options.auth_user,
                auth_key=options.auth_key,
                auth_cache_path=auth_cache_path,
                region=options.region,
                snet=options.snet,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                http_proxy=options.proxy,
            )

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + "_"):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1 :]
        else:
            environ_name = (section_name + "_" + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name, os.environ.get(environ_name, (self.context.conf.get(section_name, {})).get(conf_name))
        )

    def _perform_command(self, args):
        command = args[0]
        if command == "for":
            command = "fordo"
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write("ERROR unknown command %r\n" % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, "text"):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write("ERROR ")
                        fp.write(err.text)
                        fp.write("\n")
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write("\n")
                    fp.flush()
            return getattr(err, "code", 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get("skip_sub_command", False)
            with self.context.io_manager.with_debug(skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write("VERBOSE %.02f " % (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError("%s: %r %r" % (err, msg, args))
                fp.write("\n")
                fp.flush()
コード例 #3
0
class CLI(object):
    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.

    :param commands: The list of commands available; if None
        :py:attr:`COMMANDS` will be used.
    """
    def __init__(self, commands=None):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        if commands is None:
            commands = COMMANDS
        for command in commands:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(version=VERSION,
                                          usage="""
Usage: %prog [options] <command> [command_options] [args]

With all these main Swiftly options, you can also specify them in a
configuration file or in an environment variable.

The configuration file option name is the long name of the command line option
with any dashes replaced with underscores, for example:
--auth-url becomes auth_url

The environment variable name is the long name of the command line option in
uppercase prefixed with SWIFTLY_, for example:
--auth-url becomes SWIFTLY_AUTH_URL

Command line options override environment variables which override
configuration file variables.
            """.strip(),
                                          io_manager=self.context.io_manager)
        self.option_parser.add_option('-h',
                                      dest='help',
                                      action='store_true',
                                      help='Shows this help text.')
        self.option_parser.add_option(
            '--conf',
            dest='conf',
            metavar='PATH',
            help='Path to the configuration file to use. Default: '
            '~/.swiftly.conf')
        self.option_parser.add_option(
            '-A',
            '--auth-url',
            dest='auth_url',
            metavar='URL',
            help='URL to auth system, example: '
            'https://identity.api.rackspacecloud.com/v2.0')
        self.option_parser.add_option(
            '-U',
            '--auth-user',
            dest='auth_user',
            metavar='USER',
            help='User name for auth system, example: test:tester')
        self.option_parser.add_option(
            '-K',
            '--auth-key',
            dest='auth_key',
            metavar='KEY',
            help='Key for auth system, example: testing')
        self.option_parser.add_option(
            '-T',
            '--auth-tenant',
            dest='auth_tenant',
            metavar='TENANT',
            help='Tenant name for auth system, example: test If not '
            'specified and needed, the auth user will be used.')
        self.option_parser.add_option(
            '--auth-methods',
            dest='auth_methods',
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
            'auth2key,auth2password,auth2password_force_tenant,auth1 The '
            'best order will try to be determined for you; but if you '
            'notice it keeps making useless auth attempts and that '
            'drives you crazy, you can override that here. All the '
            'available auth methods are listed in the example.')
        self.option_parser.add_option(
            '--region',
            dest='region',
            metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW Default: '
            'default region specified by the auth response.')
        self.option_parser.add_option(
            '-D',
            '--direct',
            dest='direct',
            metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
            'to rings and backend servers. The PATH is the account '
            'path, example: /v1/AUTH_test')
        self.option_parser.add_option(
            '-L',
            '--local',
            dest='local',
            metavar='PATH',
            help='Uses the local file system method to access a fake Swift. '
            'The PATH is the path on the local file system where the '
            'fake Swift stores its data.')
        self.option_parser.add_option('-P',
                                      '--proxy',
                                      dest='proxy',
                                      metavar='URL',
                                      help='Uses the given HTTP proxy URL.')
        self.option_parser.add_option(
            '-B',
            '--bypass-url',
            dest='bypass_url',
            metavar='URL',
            help='Override Swift endpoint URL provided during authentication.')
        self.option_parser.add_option(
            '-S',
            '--snet',
            dest='snet',
            action='store_true',
            help='Prepends the storage URL host name with "snet-". Mostly '
            'only useful with Rackspace Cloud Files and Rackspace '
            'ServiceNet.')
        self.option_parser.add_option(
            '--no-snet',
            dest='no_snet',
            action='store_true',
            help='Disables the above snet value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-R',
            '--retries',
            dest='retries',
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
            'error. Default: 4')
        self.option_parser.add_option(
            '-C',
            '--cache-auth',
            dest='cache_auth',
            action='store_true',
            help='If set true, the storage URL and auth token are cached in '
            'your OS temporary directory as <user>.swiftly for reuse. If '
            'there are already cached values, they are used without '
            'authenticating first.')
        self.option_parser.add_option(
            '--no-cache-auth',
            dest='no_cache_auth',
            action='store_true',
            help='Disables the above cache-auth value if it had been set '
            'true in the environment or configuration file.')
        self.option_parser.add_option(
            '--cdn',
            dest='cdn',
            action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--no-cdn',
            dest='no_cdn',
            action='store_true',
            help='Disables the above cdn value if it had been set true in the '
            'environment or configuration file.')
        self.option_parser.add_option(
            '--concurrency',
            dest='concurrency',
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
            'simultaneously when possible (currently requires using '
            'Eventlet too). Default: 1 Note that some nested actions may '
            'amplify the number of concurrent actions. For instance, a '
            'put of an entire directory will use up to this number of '
            'concurrent actions. A put of a segmented object will use up '
            'to this number of concurrent actions. But, if a directory '
            'structure put is uploading segmented objects, this nesting '
            'could cause up to INTEGER * INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet',
            dest='eventlet',
            action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
            'if Eventlet is not installed or is less than version 0.11.0 '
            '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet',
            dest='no_eventlet',
            action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
            'greater.')
        self.option_parser.add_option(
            '-v',
            '--verbose',
            dest='verbose',
            action='store_true',
            help='Causes output to standard error indicating actions being '
            'taken. These output lines will be prefixed with VERBOSE and '
            'will also include the number of seconds elapsed since '
            'the command started.')
        self.option_parser.add_option(
            '--no-verbose',
            dest='no_verbose',
            action='store_true',
            help='Disables the above verbose value if it had been set true in '
            'the environment or configuration file.')
        self.option_parser.add_option(
            '-O',
            '--direct-object-ring',
            dest='direct_object_ring',
            metavar='PATH',
            help='The current object ring of the cluster being pinged. This '
            'will enable direct client to use this ring for all the '
            'queries. Use of this also requires the main Swift code  '
            'is installed and importable.')
        self.option_parser.add_option(
            '-k',
            '--insecure',
            dest='insecure',
            action='store_true',
            help='Allows "insecure" SSL connections for python >= 2.7.9')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in moves.range(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines),
                width=79,
                initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        options, args = self._parse_args(args)
        if not options:
            return 1
        return self._perform_command(args)

    def _parse_args(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return None, None
        if options.version:
            self.option_parser.print_version()
            return None, None
        if not args or options.help:
            self.option_parser.print_help()
            return None, None
        self.context.original_main_args = self.context.original_args[:-len(args
                                                                           )]

        self.context.conf = {}
        if options.conf is None:
            options.conf = os.environ.get('SWIFTLY_CONF', '~/.swiftly.conf')
        try:
            conf_parser = SafeConfigParser()
            conf_parser.read(os.path.expanduser(options.conf))
            for section in conf_parser.sections():
                self.context.conf[section] = dict(conf_parser.items(section))
        except ConfigParserError:
            pass

        for option_name in ('auth_url', 'auth_user', 'auth_key', 'auth_tenant',
                            'auth_methods', 'region', 'direct', 'local',
                            'proxy', 'snet', 'no_snet', 'retries',
                            'cache_auth', 'no_cache_auth', 'cdn', 'no_cdn',
                            'concurrency', 'eventlet', 'no_eventlet',
                            'verbose', 'no_verbose', 'direct_object_ring',
                            'insecure', 'bypass_url'):
            self._resolve_option(options, option_name, 'swiftly')
        for option_name in ('snet', 'no_snet', 'cache_auth', 'no_cache_auth',
                            'cdn', 'no_cdn', 'eventlet', 'no_eventlet',
                            'verbose', 'no_verbose', 'insecure'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(options, option_name,
                        getattr(options, option_name).lower() in TRUE_VALUES)
        for option_name in ('retries', 'concurrency'):
            if isinstance(getattr(options, option_name), six.string_types):
                setattr(options, option_name,
                        int(getattr(options, option_name)))
        if options.snet is None:
            options.snet = False
        if options.no_snet is None:
            options.no_snet = False
        if options.retries is None:
            options.retries = 4
        if options.cache_auth is None:
            options.cache_auth = False
        if options.no_cache_auth is None:
            options.no_cache_auth = False
        if options.cdn is None:
            options.cdn = False
        if options.no_cdn is None:
            options.no_cdn = False
        if options.concurrency is None:
            options.concurrency = 1
        if options.eventlet is None:
            options.eventlet = False
        if options.no_eventlet is None:
            options.no_eventlet = False
        if options.verbose is None:
            options.verbose = False
        if options.no_verbose is None:
            options.no_verbose = False
        if options.insecure is None:
            options.insecure = False

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if args and args[0] == 'help':
            return options, args
        elif options.local:
            self.context.client_manager = ClientManager(
                LocalClient, local_path=options.local, verbose=self._verbose)
        elif options.direct:
            self.context.client_manager = ClientManager(
                DirectClient,
                swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                direct_object_ring=options.direct_object_ring)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            if not options.auth_url:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write('No Auth URL has been given.\n')
                    fp.flush()
                return None, None
            self.context.client_manager = ClientManager(
                StandardClient,
                auth_methods=options.auth_methods,
                auth_url=options.auth_url,
                auth_tenant=options.auth_tenant,
                auth_user=options.auth_user,
                auth_key=options.auth_key,
                auth_cache_path=auth_cache_path,
                region=options.region,
                snet=options.snet,
                attempts=options.retries + 1,
                eventlet=self.context.eventlet,
                verbose=self._verbose,
                http_proxy=options.proxy,
                insecure=options.insecure,
                bypass_url=options.bypass_url)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        return options, args

    def _resolve_option(self, options, option_name, section_name):
        """Resolves an option value into options.

        Sets options.<option_name> to a resolved value. Any value
        already in options overrides a value in os.environ which
        overrides self.context.conf.

        :param options: The options instance as returned by optparse.
        :param option_name: The name of the option, such as
            ``auth_url``.
        :param section_name: The name of the section, such as
            ``swiftly``.
        """
        if getattr(options, option_name, None) is not None:
            return
        if option_name.startswith(section_name + '_'):
            environ_name = option_name.upper()
            conf_name = option_name[len(section_name) + 1:]
        else:
            environ_name = (section_name + '_' + option_name).upper()
            conf_name = option_name
        setattr(
            options, option_name,
            os.environ.get(environ_name,
                           (self.context.conf.get(section_name,
                                                  {})).get(conf_name)))

    def _perform_command(self, args):
        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write('VERBOSE %.02f ' %
                             (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()
コード例 #4
0
ファイル: cli.py プロジェクト: BlueSkyChina/swiftly
class CLI(object):
    """
    Handles the original command line.

    An example script is `swiftly` itself::

        #!/usr/bin/env python
        import sys
        import swiftly.cli
        sys.exit(swiftly.cli.CLI()())

    See the output of ``swiftly help`` for more information.
    """

    def __init__(self):
        #: The overall CLIContext containing attributes generated by the
        #: initial main_options parsing.
        #:
        #: The available attributes are:
        #:
        #: ==============  ====================================================
        #: cdn             True if the CDN URL should be used instead of the
        #:                 default Storage URL.
        #: client_manager  The :py:class:`swiftly.client.manager.ClientManager`
        #:                 to use for obtaining clients.
        #: concurrency     Number of concurrent actions to allow.
        #: io_manager      The :py:class:`swiftly.cli.iomanager.IOManager` to
        #:                 use for input and output.
        #: eventlet        True if Eventlet is in use.
        #: original_args   The original args used by the CLI.
        #: original_begin  The original time.time() when the CLI was called.
        #: verbose         Function to call when you want to (optionally) emit
        #:                 verbose output. ``verbose(msg, *args)`` where the
        #:                 output will be constructed with ``msg % args``.
        #: verbosity       Level of verbosity. Just None or 1 right now.
        #: ==============  ====================================================
        self.context = CLIContext()
        self.context.verbose = None
        self.context.io_manager = IOManager()

        #: A dictionary of the available commands and their CLICommand
        #: instances.
        self.commands = {}
        for command in COMMANDS:
            mod, cls = command.rsplit('.', 1)
            cls = getattr(__import__(mod, fromlist=[cls]), cls)
            inst = cls(self)
            self.commands[inst.name] = inst

        #: The main :py:class:`OptionParser`.
        self.option_parser = OptionParser(
            version=VERSION,
            usage='Usage: %prog [options] <command> [command_options] [args]',
            io_manager=self.context.io_manager)
        self.option_parser.add_option(
            '-h', dest='help', action='store_true',
            help='Shows this help text.')
        self.option_parser.add_option(
            '-A', '--auth-url', dest='auth_url',
            default=os.environ.get('SWIFTLY_AUTH_URL', ''), metavar='URL',
            help='URL to auth system, example: '
                 'http://127.0.0.1:8080/auth/v1.0 You can also set this with '
                 'the environment variable SWIFTLY_AUTH_URL.')
        self.option_parser.add_option(
            '-U', '--auth-user', dest='auth_user',
            default=os.environ.get('SWIFTLY_AUTH_USER', ''), metavar='USER',
            help='User name for auth system, example: test:tester You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_USER.')
        self.option_parser.add_option(
            '-K', '--auth-key', dest='auth_key',
            default=os.environ.get('SWIFTLY_AUTH_KEY', ''), metavar='KEY',
            help='Key for auth system, example: testing You can also set this '
                 'with the environment variable SWIFTLY_AUTH_KEY.')
        self.option_parser.add_option(
            '-T', '--auth-tenant', dest='auth_tenant',
            default=os.environ.get('SWIFTLY_AUTH_TENANT', ''),
            metavar='TENANT',
            help='Tenant name for auth system, example: test You can '
                 'also set this with the environment variable '
                 'SWIFTLY_AUTH_TENANT. If not specified and needed, the auth '
                 'user will be used.')
        self.option_parser.add_option(
            '--auth-methods', dest='auth_methods',
            default=os.environ.get('SWIFTLY_AUTH_METHODS', ''),
            metavar='name[,name[...]]',
            help='Auth methods to use with the auth system, example: '
                 'auth2key,auth2password,auth2password_force_tenant,auth1 You '
                 'can also set this with the environment variable '
                 'SWIFTLY_AUTH_METHODS. Swiftly will try to determine the '
                 'best order for you; but if you notice it keeps making '
                 'useless auth attempts and that drives you crazy, you can '
                 'override that here. All the available auth methods are '
                 'listed in the example.')
        self.option_parser.add_option(
            '--region', dest='region',
            default=os.environ.get('SWIFTLY_REGION', ''), metavar='VALUE',
            help='Region to use, if supported by auth, example: DFW You can '
                 'also set this with the environment variable SWIFTLY_REGION. '
                 'Default: default region specified by the auth response.')
        self.option_parser.add_option(
            '-D', '--direct', dest='direct',
            default=os.environ.get('SWIFTLY_DIRECT', ''), metavar='PATH',
            help='Uses direct connect method to access Swift. Requires access '
                 'to rings and backend servers. The PATH is the account '
                 'path, example: /v1/AUTH_test You can also set this with the '
                 'environment variable SWIFTLY_DIRECT.')
        self.option_parser.add_option(
            '-P', '--proxy', dest='proxy',
            default=os.environ.get('SWIFTLY_PROXY', ''), metavar='URL',
            help='Uses the given proxy URL. You can also set this with the '
                 'environment variable SWIFTLY_PROXY.')
        self.option_parser.add_option(
            '-S', '--snet', dest='snet', action='store_true',
            default=os.environ.get('SWIFTLY_SNET', 'false').lower() == 'true',
            help='Prepends the storage URL host name with "snet-". Mostly '
                 'only useful with Rackspace Cloud Files and Rackspace '
                 'ServiceNet. You can also set this with the environment '
                 'variable SWIFTLY_SNET (set to "true" or "false").')
        self.option_parser.add_option(
            '-R', '--retries', dest='retries',
            default=int(os.environ.get('SWIFTLY_RETRIES', 4)),
            metavar='INTEGER',
            help='Indicates how many times to retry the request on a server '
                 'error. Default: 4. You can also set this with the '
                 'environment variable SWIFTLY_RETRIES.')
        self.option_parser.add_option(
            '-C', '--cache-auth', dest='cache_auth', action='store_true',
            default=(os.environ.get(
                'SWIFTLY_CACHE_AUTH', 'false').lower() == 'true'),
            help='If set true, the storage URL and auth token are cached in '
                 'your OS temporary directory as <user>.swiftly for reuse. If '
                 'there are already cached values, they are used without '
                 'authenticating first. You can also set this with the '
                 'environment variable SWIFTLY_CACHE_AUTH (set to "true" or '
                 '"false").')
        self.option_parser.add_option(
            '--cdn', dest='cdn', action='store_true',
            help='Directs requests to the CDN management interface.')
        self.option_parser.add_option(
            '--concurrency', dest='concurrency',
            default=int(os.environ.get('SWIFTLY_CONCURRENCY', 1)),
            metavar='INTEGER',
            help='Sets the the number of actions that can be done '
                 'simultaneously when possible (currently requires using '
                 'Eventlet too). Default: 1. You can also set this with the '
                 'environment variable SWIFTLY_CONCURRENCY. Note that some '
                 'nested actions may amplify the number of concurrent '
                 'actions. For instance, a put of an entire directory will '
                 'use up to this number of concurrent actions. A put of a '
                 'segmented object will use up to this number of concurrent '
                 'actions. But, if a directory structure put is uploading '
                 'segmented objects, this nesting could cause up to INTEGER * '
                 'INTEGER concurrent actions.')
        self.option_parser.add_option(
            '--eventlet', dest='eventlet', action='store_true',
            help='Enables Eventlet, if installed. This is disabled by default '
                 'if Eventlet is not installed or is less than version 0.11.0 '
                 '(because older Swiftly+Eventlet tends to use excessive CPU.')
        self.option_parser.add_option(
            '--no-eventlet', dest='no_eventlet', action='store_true',
            help='Disables Eventlet, even if installed and version 0.11.0 or '
                 'greater.')
        self.option_parser.add_option(
            '-v', '--verbose', dest='verbose', action='store_true',
            help='Causes output to standard error indicating actions being '
                 'taken. These output lines will be prefixed with VERBOSE and '
                 'will also include the number of seconds elapsed since '
                 'Swiftly started.')

        self.option_parser.raw_epilog = 'Commands:\n'
        for name in sorted(self.commands):
            command = self.commands[name]
            lines = command.option_parser.get_usage().split('\n')
            main_line = '  ' + lines[0].split(']', 1)[1].strip()
            for x in xrange(4):
                lines.pop(0)
            for x, line in enumerate(lines):
                if not line:
                    lines = lines[:x]
                    break
            if len(main_line) < 24:
                initial_indent = main_line + ' ' * (24 - len(main_line))
            else:
                self.option_parser.raw_epilog += main_line + '\n'
                initial_indent = ' ' * 24
            self.option_parser.raw_epilog += textwrap.fill(
                ' '.join(lines), width=79, initial_indent=initial_indent,
                subsequent_indent=' ' * 24) + '\n'

    def __call__(self, args=None):
        self.context.original_begin = time.time()
        self.context.original_args = args if args is not None else sys.argv[1:]
        self.option_parser.disable_interspersed_args()
        try:
            options, args = self.option_parser.parse_args(
                self.context.original_args)
        except UnboundLocalError:
            # Happens sometimes with an error handler that doesn't raise its
            # own exception. We'll catch the error below with
            # error_encountered.
            pass
        self.option_parser.enable_interspersed_args()
        if self.option_parser.error_encountered:
            return 1
        if options.version:
            self.option_parser.print_version()
            return 1
        if not args or options.help:
            self.option_parser.print_help()
            return 1
        self.context.original_main_args = self.context.original_args[
            :-len(args)]

        self.context.eventlet = None
        if options.eventlet:
            self.context.eventlet = True
        if options.no_eventlet:
            self.context.eventlet = False
        if self.context.eventlet is None:
            self.context.eventlet = False
            try:
                import eventlet
                # Eventlet 0.11.0 fixed the CPU bug
                if eventlet.__version__ >= '0.11.0':
                    self.context.eventlet = True
            except ImportError:
                pass

        subprocess_module = None
        if self.context.eventlet:
            try:
                import eventlet.green.subprocess
                subprocess_module = eventlet.green.subprocess
            except ImportError:
                pass
        if subprocess_module is None:
            import subprocess
            subprocess_module = subprocess
        self.context.io_manager.subprocess_module = subprocess_module

        if options.verbose:
            self.context.verbosity = 1
            self.context.verbose = self._verbose
            self.context.io_manager.verbose = functools.partial(
                self._verbose, skip_sub_command=True)

        options.retries = int(options.retries)
        if options.direct:
            self.context.client_manager = ClientManager(
                DirectClient, swift_proxy_storage_path=options.direct,
                attempts=options.retries + 1, eventlet=self.context.eventlet,
                verbose=self._verbose)
        else:
            auth_cache_path = None
            if options.cache_auth:
                auth_cache_path = os.path.join(
                    tempfile.gettempdir(),
                    '%s.swiftly' % os.environ.get('USER', 'user'))
            self.context.client_manager = ClientManager(
                StandardClient, auth_methods=options.auth_methods,
                auth_url=options.auth_url, auth_tenant=options.auth_tenant,
                auth_user=options.auth_user, auth_key=options.auth_key,
                auth_cache_path=auth_cache_path, region=options.region,
                snet=options.snet, attempts=options.retries + 1,
                eventlet=self.context.eventlet, verbose=self._verbose)

        self.context.cdn = options.cdn
        self.context.concurrency = int(options.concurrency)

        command = args[0]
        if command == 'for':
            command = 'fordo'
        if command not in self.commands:
            with self.context.io_manager.with_stderr() as fp:
                fp.write('ERROR unknown command %r\n' % args[0])
                fp.flush()
            return 1
        try:
            self.commands[command](args[1:])
        except Exception as err:
            if hasattr(err, 'text'):
                if err.text:
                    with self.context.io_manager.with_stderr() as fp:
                        fp.write('ERROR ')
                        fp.write(err.text)
                        fp.write('\n')
                        fp.flush()
            else:
                with self.context.io_manager.with_stderr() as fp:
                    fp.write(traceback.format_exc())
                    fp.write('\n')
                    fp.flush()
            return getattr(err, 'code', 1)
        return 0

    def _verbose(self, msg, *args, **kwargs):
        if self.context.verbosity:
            skip_sub_command = kwargs.get('skip_sub_command', False)
            with self.context.io_manager.with_debug(
                    skip_sub_command=skip_sub_command) as fp:
                try:
                    fp.write(
                        'VERBOSE %.02f ' %
                        (time.time() - self.context.original_begin))
                    fp.write(msg % args)
                except TypeError as err:
                    raise TypeError('%s: %r %r' % (err, msg, args))
                fp.write('\n')
                fp.flush()