2a4ae31deb01bf9a08a3591038957d371f96f1cc
[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 re
33 import shlex
34 import subprocess
35 import webbrowser
36
37 from .platforminfo import PlatformInfo
38
39
40 _log = logging.getLogger(__name__)
41
42
43 try:
44     import readline
45 except ImportError:
46     if not PlatformInfo().is_native_win():
47         # There is no readline module for win32, not much to do except cry.
48         _log.warn("Unable to import readline.")
49
50
51 class User(object):
52     DEFAULT_NO = 'n'
53     DEFAULT_YES = 'y'
54
55     def __init__(self, platforminfo=None):
56         # We cannot get the PlatformInfo object from a SystemHost because
57         # User is part of SystemHost itself.
58         self._platforminfo = platforminfo or PlatformInfo()
59
60     # FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance).
61     @classmethod
62     def prompt(cls, message, repeat=1, raw_input=raw_input):
63         response = None
64         while (repeat and not response):
65             repeat -= 1
66             response = raw_input(message)
67         return response
68
69     @classmethod
70     def prompt_password(cls, message, repeat=1):
71         return cls.prompt(message, repeat=repeat, raw_input=getpass.getpass)
72
73     @classmethod
74     def prompt_with_multiple_lists(cls, list_title, subtitles, lists, can_choose_multiple=False, raw_input=raw_input):
75         item_index = 0
76         cumulated_list = []
77         print(list_title)
78         for i in range(len(subtitles)):
79             print("\n" + subtitles[i])
80             for item in lists[i]:
81                 item_index += 1
82                 print("%2d. %s" % (item_index, item))
83             cumulated_list += lists[i]
84         return cls._wait_on_list_response(cumulated_list, can_choose_multiple, raw_input)
85
86     @classmethod
87     def _wait_on_list_response(cls, list_items, can_choose_multiple, raw_input):
88         while True:
89             if can_choose_multiple:
90                 response = cls.prompt("Enter one or more numbers (comma-separated) or ranges (e.g. 3-7), or \"all\": ", raw_input=raw_input)
91                 if not response.strip() or response == "all":
92                     return list_items
93
94                 try:
95                     indices = []
96                     for value in re.split("\s*,\s*", response):
97                         parts = value.split('-')
98                         if len(parts) == 2:
99                             indices += range(int(parts[0]) - 1, int(parts[1]))
100                         else:
101                             indices.append(int(value) - 1)
102                 except ValueError as err:
103                     continue
104
105                 return [list_items[i] for i in indices]
106             else:
107                 try:
108                     result = int(cls.prompt("Enter a number: ", raw_input=raw_input)) - 1
109                 except ValueError as err:
110                     continue
111                 return list_items[result]
112
113     @classmethod
114     def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input):
115         print(list_title)
116         i = 0
117         for item in list_items:
118             i += 1
119             print("%2d. %s" % (i, item))
120         return cls._wait_on_list_response(list_items, can_choose_multiple, raw_input)
121
122     def edit(self, files):
123         editor = os.environ.get("EDITOR") or "vi"
124         args = shlex.split(editor)
125         # Note: Not thread safe: http://bugs.python.org/issue2320
126         try:
127             subprocess.call(args + files)
128         except OSError as e:
129             _log.warn("There was a problem editing the ChangeLog using editor '%s': %s." % (editor, e))
130
131     def _warn_if_application_is_xcode(self, edit_application):
132         if "Xcode" in edit_application:
133             print("Instead of using Xcode.app, consider using EDITOR=\"xed --wait\".")
134
135     def edit_changelog(self, files):
136         edit_application = os.environ.get("CHANGE_LOG_EDIT_APPLICATION")
137         if edit_application and self._platforminfo.is_mac():
138             # On Mac we support editing ChangeLogs using an application.
139             args = shlex.split(edit_application)
140             print("Using editor in the CHANGE_LOG_EDIT_APPLICATION environment variable.")
141             print("Please quit the editor application when done editing.")
142             self._warn_if_application_is_xcode(edit_application)
143             subprocess.call(["open", "-W", "-n", "-a"] + args + files)
144             return
145         self.edit(files)
146
147     def page(self, message):
148         pager = os.environ.get("PAGER") or "less"
149         try:
150             # Note: Not thread safe: http://bugs.python.org/issue2320
151             child_process = subprocess.Popen([pager], stdin=subprocess.PIPE)
152             child_process.communicate(input=message)
153         except IOError as e:
154             pass
155
156     def confirm(self, message=None, default=DEFAULT_YES, raw_input=raw_input):
157         if not message:
158             message = "Continue?"
159         choice = {'y': 'Y/n', 'n': 'y/N'}[default]
160         response = raw_input("%s [%s]: " % (message, choice))
161         if not response:
162             response = default
163         return response.lower() == 'y'
164
165     def can_open_url(self):
166         try:
167             webbrowser.get()
168             return True
169         except webbrowser.Error as e:
170             return False
171
172     def open_url(self, url):
173         if not self.can_open_url():
174             _log.warn("Failed to open %s" % url)
175         webbrowser.open(url)