e513ecb0aaaabb805fe87fcd08c166dcc334eaef
[WebKit-https.git] / Tools / Scripts / webkitpy / common / system / user.py
1 # Copyright (c) 2009, Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import getpass
30 import logging
31 import os
32 import platform
33 import re
34 import shlex
35 import subprocess
36 import sys
37 import webbrowser
38
39 from webkitpy.common.system.executive import Executive
40 from webkitpy.common.system.platforminfo import PlatformInfo
41
42
43 _log = logging.getLogger(__name__)
44
45
46 try:
47     import readline
48 except ImportError:
49     if not sys.platform.startswith('win32'):
50         # There is no readline module for win32, not much to do except cry.
51         _log.warn("Unable to import readline.")
52
53
54 class User(object):
55     DEFAULT_NO = 'n'
56     DEFAULT_YES = 'y'
57
58     def __init__(self, platforminfo=None):
59         # We cannot get the PlatformInfo object from a SystemHost because
60         # User is part of SystemHost itself.
61         self._platforminfo = platforminfo or PlatformInfo(sys, platform, Executive())
62
63     # FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance).
64     @classmethod
65     def prompt(cls, message, repeat=1, raw_input=raw_input):
66         response = None
67         while (repeat and not response):
68             repeat -= 1
69             response = raw_input(message)
70         return response
71
72     @classmethod
73     def prompt_password(cls, message, repeat=1):
74         return cls.prompt(message, repeat=repeat, raw_input=getpass.getpass)
75
76     @classmethod
77     def prompt_with_multiple_lists(cls, list_title, subtitles, lists, can_choose_multiple=False, raw_input=raw_input):
78         item_index = 0
79         cumulated_list = []
80         print(list_title)
81         for i in range(len(subtitles)):
82             print("\n" + subtitles[i])
83             for item in lists[i]:
84                 item_index += 1
85                 print("%2d. %s" % (item_index, item))
86             cumulated_list += lists[i]
87         return cls._wait_on_list_response(cumulated_list, can_choose_multiple, raw_input)
88
89     @classmethod
90     def _wait_on_list_response(cls, list_items, can_choose_multiple, raw_input):
91         while True:
92             if can_choose_multiple:
93                 response = cls.prompt("Enter one or more numbers (comma-separated) or ranges (e.g. 3-7), or \"all\": ", raw_input=raw_input)
94                 if not response.strip() or response == "all":
95                     return list_items
96
97                 try:
98                     indices = []
99                     for value in re.split("\s*,\s*", response):
100                         parts = value.split('-')
101                         if len(parts) == 2:
102                             indices += range(int(parts[0]) - 1, int(parts[1]))
103                         else:
104                             indices.append(int(value) - 1)
105                 except ValueError as err:
106                     continue
107
108                 return [list_items[i] for i in indices]
109             else:
110                 try:
111                     result = int(cls.prompt("Enter a number: ", raw_input=raw_input)) - 1
112                 except ValueError as err:
113                     continue
114                 return list_items[result]
115
116     @classmethod
117     def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input):
118         print(list_title)
119         i = 0
120         for item in list_items:
121             i += 1
122             print("%2d. %s" % (i, item))
123         return cls._wait_on_list_response(list_items, can_choose_multiple, raw_input)
124
125     def edit(self, files):
126         editor = os.environ.get("EDITOR") or "vi"
127         args = shlex.split(editor)
128         # Note: Not thread safe: http://bugs.python.org/issue2320
129         try:
130             subprocess.call(args + files)
131         except OSError as e:
132             _log.warn("There was a problem editing the ChangeLog using editor '%s': %s." % (editor, e))
133
134     def _warn_if_application_is_xcode(self, edit_application):
135         if "Xcode" in edit_application:
136             print("Instead of using Xcode.app, consider using EDITOR=\"xed --wait\".")
137
138     def edit_changelog(self, files):
139         edit_application = os.environ.get("CHANGE_LOG_EDIT_APPLICATION")
140         if edit_application and self._platforminfo.is_mac():
141             # On Mac we support editing ChangeLogs using an application.
142             args = shlex.split(edit_application)
143             print("Using editor in the CHANGE_LOG_EDIT_APPLICATION environment variable.")
144             print("Please quit the editor application when done editing.")
145             self._warn_if_application_is_xcode(edit_application)
146             subprocess.call(["open", "-W", "-n", "-a"] + args + files)
147             return
148         self.edit(files)
149
150     def page(self, message):
151         pager = os.environ.get("PAGER") or "less"
152         try:
153             # Note: Not thread safe: http://bugs.python.org/issue2320
154             child_process = subprocess.Popen([pager], stdin=subprocess.PIPE)
155             child_process.communicate(input=message)
156         except IOError as e:
157             pass
158
159     def confirm(self, message=None, default=DEFAULT_YES, raw_input=raw_input):
160         if not message:
161             message = "Continue?"
162         choice = {'y': 'Y/n', 'n': 'y/N'}[default]
163         response = raw_input("%s [%s]: " % (message, choice))
164         if not response:
165             response = default
166         return response.lower() == 'y'
167
168     def can_open_url(self):
169         try:
170             webbrowser.get()
171             return True
172         except webbrowser.Error as e:
173             return False
174
175     def open_url(self, url):
176         if not self.can_open_url():
177             _log.warn("Failed to open %s" % url)
178         webbrowser.open(url)