1# Copyright (c) 2010 Google Inc. All rights reserved.
2# Copyright (c) 2009 Apple Inc. All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are
6# met:
7#
8#     * Redistributions of source code must retain the above copyright
9# notice, this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above
11# copyright notice, this list of conditions and the following disclaimer
12# in the documentation and/or other materials provided with the
13# distribution.
14#     * Neither the name of Google Inc. nor the names of its
15# contributors may be used to endorse or promote products derived from
16# this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30"""blink_tool.py is a tool with multiple sub-commands with different purposes.
31
32It has commands for printing expectations, fetching new test baselines, etc.
33These commands don't necessarily have anything to do with each other.
34"""
35
36import logging
37import optparse
38import sys
39
40from blinkpy.common.host import Host
41from blinkpy.tool.commands.analyze_baselines import AnalyzeBaselines
42from blinkpy.tool.commands.command import HelpPrintingOptionParser
43from blinkpy.tool.commands.copy_existing_baselines import CopyExistingBaselines
44from blinkpy.tool.commands.flaky_tests import FlakyTests
45from blinkpy.tool.commands.help_command import HelpCommand
46from blinkpy.tool.commands.optimize_baselines import OptimizeBaselines
47from blinkpy.tool.commands.pretty_diff import PrettyDiff
48from blinkpy.tool.commands.queries import CrashLog
49from blinkpy.tool.commands.queries import PrintBaselines
50from blinkpy.tool.commands.queries import PrintExpectations
51from blinkpy.tool.commands.rebaseline import Rebaseline
52from blinkpy.tool.commands.rebaseline_cl import RebaselineCL
53from blinkpy.tool.commands.rebaseline_test import RebaselineTest
54
55
56_log = logging.getLogger(__name__)
57
58
59class BlinkTool(Host):
60    # FIXME: It might make more sense if this class had a Host attribute
61    # instead of being a Host subclass.
62
63    global_options = [
64        optparse.make_option(
65            '-v', '--verbose', action='store_true', dest='verbose', default=False,
66            help='enable all logging'),
67        optparse.make_option(
68            '-d', '--directory', action='append', default=[],
69            help='Directory to look at for changed files'),
70    ]
71
72    def __init__(self, path):
73        super(BlinkTool, self).__init__()
74        self._path = path
75        self.commands = [
76            AnalyzeBaselines(),
77            CopyExistingBaselines(),
78            CrashLog(),
79            FlakyTests(),
80            OptimizeBaselines(),
81            PrettyDiff(),
82            PrintBaselines(),
83            PrintExpectations(),
84            Rebaseline(),
85            RebaselineCL(),
86            RebaselineTest(),
87        ]
88        self.help_command = HelpCommand(tool=self)
89        self.commands.append(self.help_command)
90
91    def main(self, argv=None):
92        argv = argv or sys.argv
93        (command_name, args) = self._split_command_name_from_args(argv[1:])
94
95        option_parser = self._create_option_parser()
96        self._add_global_options(option_parser)
97
98        command = self.command_by_name(command_name) or self.help_command
99        if not command:
100            option_parser.error('%s is not a recognized command', command_name)
101
102        command.set_option_parser(option_parser)
103        (options, args) = command.parse_args(args)
104
105        result = command.check_arguments_and_execute(options, args, self)
106        return result
107
108    def path(self):
109        return self._path
110
111    @staticmethod
112    def _split_command_name_from_args(args):
113        # Assume the first argument which doesn't start with "-" is the command name.
114        command_index = 0
115        for arg in args:
116            if arg[0] != '-':
117                break
118            command_index += 1
119        else:
120            return (None, args[:])
121
122        command = args[command_index]
123        return (command, args[:command_index] + args[command_index + 1:])
124
125    def _create_option_parser(self):
126        usage = 'Usage: %prog [options] COMMAND [ARGS]'
127        name = optparse.OptionParser().get_prog_name()
128        return HelpPrintingOptionParser(epilog_method=self.help_command.help_epilog, prog=name, usage=usage)
129
130    def _add_global_options(self, option_parser):
131        global_options = self.global_options or []
132        for option in global_options:
133            option_parser.add_option(option)
134
135    def name(self):
136        return optparse.OptionParser().get_prog_name()
137
138    def should_show_in_main_help(self, command):
139        return command.show_in_main_help
140
141    def command_by_name(self, command_name):
142        for command in self.commands:
143            if command_name == command.name:
144                return command
145        return None
146