Run one or more commands in succession.

        For example, assume the commands ``local`` and ``remote`` have been
        defined; the following will run ``ls`` first on the local host and
        then on the remote host::

            runcommands local ls remote <host> ls

        When a command name is encountered in ``argv``, it will be considered
        the starting point of the next command *unless* the previous item in
        ``argv`` was an option like ``--xyz`` that expects a value (i.e.,
        it's not a flag).

        To avoid ambiguity when an option value matches a command name, the
        value can be prepended with a colon to force it to be considered
        a value and not a command name.

def implementation(self,
                       commands_module: arg(short_option='-m') = DEFAULT_COMMANDS_MODULE,
                       config_file: arg(short_option='-f') = None,
                       # Globals
                       globals_: arg(
                           container=dict,
                           type=json_value,
                           help='Global variables & default args for *all* commands; will be '
                                'injected into itself, default args, and environment variables '
                                '(higher precedence than keyword args)'
                       ) = None,
                       # Special globals (for command line convenience)
                       env: arg(help='env will be added to globals if specified') = None,
                       version: arg(help='version will be added to globals if specified') = None,
                       echo: arg(
                           type=bool,
                           help='echo=True will be added to globals',
                           inverse_help='echo=False will be added to globals'
                       ) = None,
                       # Environment variables
                       environ: arg(
                           container=dict,
                           help='Additional environment variables; '
                                'added just before commands are run'
                       ) = None,
                       # Meta
                       info: arg(help='Show info and exit') = False,
                       list_commands: arg(help='Show info & commands and exit') = False,
                       debug: arg(
                           type=bool,
                           help='Print debugging info & re-raise exceptions; also added to globals'
                       ) = None,
                       *,
                       all_argv=(),
                       run_argv=(),
                       command_argv=(),
                       cli_args=()):
        """Run one or more commands in succession.

        For example, assume the commands ``local`` and ``remote`` have been
        defined; the following will run ``ls`` first on the local host and
        then on the remote host::

            runcommands local ls remote <host> ls

        When a command name is encountered in ``argv``, it will be considered
        the starting point of the next command *unless* the previous item in
        ``argv`` was an option like ``--xyz`` that expects a value (i.e.,
        it's not a flag).

        To avoid ambiguity when an option value matches a command name, the
        value can be prepended with a colon to force it to be considered
        a value and not a command name.

        """
        collection = Collection.load_from_module(commands_module)
        config_file = self.find_config_file(config_file)
        cli_globals = globals_ or {}

        if env:
            cli_globals['env'] = env
        if version:
            cli_globals['version'] = version
        if echo is not None:
            cli_globals['echo'] = echo
        if debug is not None:
            cli_globals['debug'] = debug

        if config_file:
            args_from_file = self.read_config_file(config_file, collection)
            args = merge_dicts(args_from_file, {'environ': environ or {}})

            config_file_globals = args['globals']

            env = cli_globals.get('env') or config_file_globals.get('env')
            if env:
                envs = args['envs']
                try:
                    env_globals = envs[env]
                except KeyError:
                    raise RunnerError('Unknown env: {env}'.format_map(locals()))
                globals_ = merge_dicts(config_file_globals, env_globals, cli_globals)
                globals_['envs'] = envs
            else:
                globals_ = merge_dicts(config_file_globals, cli_globals)

            default_args = {name: {} for name in collection}
            default_args = merge_dicts(default_args, args.get('args') or {})

            for command_name, command_default_args in default_args.items():
                command = collection[command_name]

                # Normalize arg names from default args section.
                for name in tuple(command_default_args):
                    param = command.find_parameter(name)
                    if param is None:
                        raise RunnerError(
                            'Unknown arg for command {command_name} in default args section of '
                            '{config_file}: {name}'
                            .format_map(locals()))
                    if param is not None and name != param.name:
                        command_default_args[param.name] = command_default_args.pop(name)

                # Add globals that correspond to this command (that
                # aren't present in default args section).
                for name, value in globals_.items():
                    param = command.find_parameter(name)
                    if param is not None:
                        if param.name not in command_default_args:
                            command_default_args[param.name] = value
                    elif command.has_kwargs:
                        name = name.replace('-', '_')
                        command_default_args[name] = value

                # Convert lists to tuples for the command's args that are
                # specified as being tuples.
                for name, value in command_default_args.items():
                    command_arg = command.find_arg(name)
                    if command_arg.container and isinstance(value, list):
                        command_default_args[name] = command_arg.container(value)

            default_args = {name: args for name, args in default_args.items() if args}

            environ = args['environ']
        else:
            globals_ = cli_globals
            default_args = {}
            environ = environ or {}

        debug = globals_.get('debug', False)
        show_info = info or list_commands or not command_argv or debug
        print_and_exit = info or list_commands

        globals_, default_args, environ = self.interpolate(globals_, default_args, environ)

        if show_info:
            print('RunCommands', __version__)

        if debug:
            print()
            printer.debug('Commands module:', commands_module)
            printer.debug('Config file:', config_file)
            printer.debug('All args:', all_argv)
            printer.debug('Run args:', run_argv)
            printer.debug('Command args:', command_argv)
            items = (
                ('Globals:', globals_),
                ('Default args:', default_args),
                ('Environment variables:', environ),
            )
            for label, data in items:
                if data:
                    printer.debug(label)
                    for k in sorted(data):
                        v = data[k]
                        printer.debug('  - {k} = {v!r}'.format_map(locals()))

        if environ:
            os.environ.update(environ)

        collection.set_attrs(debug=debug)
        collection.set_default_args(default_args)
        runner = CommandRunner(collection, debug)

        if print_and_exit:
            if list_commands:
                runner.print_usage()
        elif not command_argv:
            printer.warning('\nNo command(s) specified')
            runner.print_usage()
        else:
            runner.run(command_argv)