nicoulaj/rainbow

View on GitHub
rainbow/cli.py

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
# ----------------------------------------------------------------------
# rainbow, a terminal colorizer - https://github.com/nicoulaj/rainbow
# copyright (c) 2010-2018 rainbow contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# ----------------------------------------------------------------------

import sys
from optparse import OptionParser, OptionGroup, BadOptionError, AmbiguousOptionError

from . import __version__, __description__, LOGGER, DEFAULT_PATH
from .command.execute import ExecuteCommand
from .command.noop import NoOpCommand
from .command.print_config_names import PrintConfigNamesCommand
from .command.print_path import PrintPathCommand
from .command.stdin import STDINCommand
from .config.loader import ConfigLoader
from .filter import FILTER_GROUPS, FILTERS_BY_LONG_OPTION
from .transformer import TransformerBuilder, IdentityTransformer


class CommandLineParser(OptionParser):
    def __init__(self,
                 paths=None,
                 stdout_builder=None,
                 stderr_builder=None,
                 error_handler=lambda error: None):
        OptionParser.__init__(self,
                              usage='%prog [options] -- command [args...] ',
                              version='%prog ' + __version__,
                              description=__description__)
        self.disable_interspersed_args()
        self.formatter.max_help_position = 50
        self.formatter.width = 150
        self.command = None
        self.stdout_builder = stdout_builder or TransformerBuilder()
        self.stderr_builder = stderr_builder or TransformerBuilder()
        self.error_handler = error_handler
        self.config_loader = ConfigLoader(self.stdout_builder, self.stderr_builder, paths, error_handler)
        self.add_option('-f',
                        '--config',
                        action='callback',
                        callback=self.handle_config_option,
                        type='string',
                        help='Load a config file defining patterns. '
                             'This option can be called several times.')
        self.add_option('-v',
                        '--verbose',
                        action='callback',
                        callback=self.handle_verbosity_option,
                        help='Turn on verbose mode. '
                             'This option can be called several times to increase the verbosity level.')
        self.add_option('--print-path',
                        action='callback',
                        callback=self.handle_print_path_option,
                        help='Print config paths.')
        self.add_option('--print-config-names',
                        action='callback',
                        callback=self.handle_print_config_names_option,
                        help='Print config names.')
        self.add_option('--disable-stderr-filtering',
                        action='store_false',
                        dest='enable_stderr_filtering',
                        default=True,
                        help='Disable STDERR filtering, which can have unexpected effects on command directly '
                             'using tty.')

        for group in FILTER_GROUPS:
            option_group = OptionGroup(self, group.name, group.help)
            for f in group.filters:
                if f.short_option:
                    option_group.add_option('-' + f.short_option,
                                            '--' + f.long_option,
                                            action='callback',
                                            callback=self.handle_pattern_option,
                                            type='string',
                                            help=f.help)
                else:
                    option_group.add_option('--' + f.long_option,
                                            action='callback',
                                            callback=self.handle_pattern_option,
                                            type='string',
                                            help=f.help)
            self.add_option_group(option_group)

    def parse_args(self, args=None, values=None):

        try:
            (values, remaining_args) = OptionParser.parse_args(self, args=args)
        except SystemExit:
            return self.command

        if remaining_args:

            if not self.stdout_builder.transformers:
                self.config_loader.load_config_from_command_line(remaining_args)

            stdout_transformer = self.stdout_builder.build()
            stderr_transformer = self.stderr_builder.build() if values.enable_stderr_filtering else IdentityTransformer

            return ExecuteCommand(remaining_args, stdout_transformer, stderr_transformer)

        return STDINCommand(self.stdout_builder.build())

    def _process_args(self, largs, rargs, values):
        while rargs:
            remaining = len(rargs)
            try:
                OptionParser._process_args(self, largs, rargs, values)
                if remaining == len(rargs):
                    break
            except (BadOptionError, AmbiguousOptionError) as e:
                largs.append(e.opt_str)

    def exit(self, status=0, msg=None):
        if msg:
            self.error_handler(msg)
        sys.exit(status)

    def error(self, msg):
        self.exit(1, msg)

    def handle_config_option(self, option, opt, value, parser):
        self.config_loader.load_config_by_name(value)

    def handle_pattern_option(self, option, opt, value, parser):
        filter_name = option.get_opt_string()[2:]
        filter = FILTERS_BY_LONG_OPTION[filter_name]
        self.stdout_builder.add_mapping(value, filter)
        self.stderr_builder.add_mapping(value, filter)

    @staticmethod
    def handle_verbosity_option(option, opt, value, parser):
        LOGGER.setLevel(LOGGER.level - 10)

    def handle_print_path_option(self, option, opt, value, parser):
        self.command = PrintPathCommand(DEFAULT_PATH)
        parser.exit(0)

    def handle_print_config_names_option(self, option, opt, value, parser):
        self.command = PrintConfigNamesCommand(DEFAULT_PATH)
        parser.exit(0)

    def print_help(self, file=None):
        self.command = NoOpCommand()
        return OptionParser.print_help(self, file)

    def print_version(self, file=None):
        self.command = NoOpCommand()
        return OptionParser.print_version(self, file)