1231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# Copyright (c) 2009, Google Inc. All rights reserved. 2d0825bca7fe65beaee391d30da42e937db621564Steve Block# 3231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# Redistribution and use in source and binary forms, with or without 4231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# modification, are permitted provided that the following conditions are 5231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# met: 6231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# 7231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# * Redistributions of source code must retain the above copyright 8231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# notice, this list of conditions and the following disclaimer. 9231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# * Redistributions in binary form must reproduce the above 10231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# copyright notice, this list of conditions and the following disclaimer 11231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# in the documentation and/or other materials provided with the 12231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# distribution. 13231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# * Neither the name of Google Inc. nor the names of its 14231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# contributors may be used to endorse or promote products derived from 15231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# this software without specific prior written permission. 16231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# 17231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 292daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdochimport getpass 306c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsenimport logging 31d0825bca7fe65beaee391d30da42e937db621564Steve Blockimport os 32bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsenimport re 33d0825bca7fe65beaee391d30da42e937db621564Steve Blockimport shlex 34d0825bca7fe65beaee391d30da42e937db621564Steve Blockimport subprocess 35e78cbe89e6f337f2f1fe40315be88f742b547151Steve Blockimport sys 36d0825bca7fe65beaee391d30da42e937db621564Steve Blockimport webbrowser 37d0825bca7fe65beaee391d30da42e937db621564Steve Block 386c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 396c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen_log = logging.getLogger("webkitpy.common.system.user") 406c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 416c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen 42dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blocktry: 43dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block import readline 44dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockexcept ImportError: 456c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen if sys.platform != "win32": 466c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # There is no readline module for win32, not much to do except cry. 476c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen _log.warn("Unable to import readline.") 486c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # FIXME: We could give instructions for non-mac platforms. 496c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen # Lack of readline results in a very bad user experiance. 50f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch if sys.platform == "darwin": 516c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen _log.warn("If you're using MacPorts, try running:") 526c2af9490927c3c5959b5cb07461b646f8b32f6cKristian Monsen _log.warn(" sudo port install py25-readline") 53dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 54dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 55d0825bca7fe65beaee391d30da42e937db621564Steve Blockclass User(object): 56bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen DEFAULT_NO = 'n' 57bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen DEFAULT_YES = 'y' 58bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen 59e458d70a0d18538346f41b503114c9ebe6b2ce12Leon Clarke # FIXME: These are @classmethods because bugzilla.py doesn't have a Tool object (thus no User instance). 60dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block @classmethod 61dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def prompt(cls, message, repeat=1, raw_input=raw_input): 628a0914b749bbe7da7768e07a7db5c6d4bb09472bSteve Block response = None 638a0914b749bbe7da7768e07a7db5c6d4bb09472bSteve Block while (repeat and not response): 648a0914b749bbe7da7768e07a7db5c6d4bb09472bSteve Block repeat -= 1 658a0914b749bbe7da7768e07a7db5c6d4bb09472bSteve Block response = raw_input(message) 668a0914b749bbe7da7768e07a7db5c6d4bb09472bSteve Block return response 67d0825bca7fe65beaee391d30da42e937db621564Steve Block 68dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block @classmethod 692daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch def prompt_password(cls, message, repeat=1): 702daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch return cls.prompt(message, repeat=repeat, raw_input=getpass.getpass) 712daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch 722daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch @classmethod 73bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input): 74dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block print list_title 75dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block i = 0 76dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block for item in list_items: 77dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block i += 1 78dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block print "%2d. %s" % (i, item) 79bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen 80bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen # Loop until we get valid input 81bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen while True: 82bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen if can_choose_multiple: 83bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen response = cls.prompt("Enter one or more numbers (comma-separated), or \"all\": ", raw_input=raw_input) 84bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen if not response.strip() or response == "all": 85bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return list_items 86bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen try: 87bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen indices = [int(r) - 1 for r in re.split("\s*,\s*", response)] 88bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen except ValueError, err: 89bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen continue 90bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return [list_items[i] for i in indices] 91bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen else: 92bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen try: 93bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen result = int(cls.prompt("Enter a number: ", raw_input=raw_input)) - 1 94bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen except ValueError, err: 95bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen continue 96bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return list_items[result] 97dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 98d0825bca7fe65beaee391d30da42e937db621564Steve Block def edit(self, files): 99d0825bca7fe65beaee391d30da42e937db621564Steve Block editor = os.environ.get("EDITOR") or "vi" 100d0825bca7fe65beaee391d30da42e937db621564Steve Block args = shlex.split(editor) 10121939df44de1705786c545cd1bf519d47250322dBen Murdoch # Note: Not thread safe: http://bugs.python.org/issue2320 102d0825bca7fe65beaee391d30da42e937db621564Steve Block subprocess.call(args + files) 103d0825bca7fe65beaee391d30da42e937db621564Steve Block 104a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch def _warn_if_application_is_xcode(self, edit_application): 105a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if "Xcode" in edit_application: 106a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch print "Instead of using Xcode.app, consider using EDITOR=\"xed --wait\"." 107a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch 1085af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke def edit_changelog(self, files): 1095af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke edit_application = os.environ.get("CHANGE_LOG_EDIT_APPLICATION") 1105af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke if edit_application and sys.platform == "darwin": 1115af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke # On Mac we support editing ChangeLogs using an application. 1125af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke args = shlex.split(edit_application) 1135af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke print "Using editor in the CHANGE_LOG_EDIT_APPLICATION environment variable." 1145af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke print "Please quit the editor application when done editing." 115a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch self._warn_if_application_is_xcode(edit_application) 1165af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke subprocess.call(["open", "-W", "-n", "-a"] + args + files) 1175af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke return 1185af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke self.edit(files) 1195af96e2c7b73ebc627c6894727826a7576d31758Leon Clarke 120d0825bca7fe65beaee391d30da42e937db621564Steve Block def page(self, message): 121d0825bca7fe65beaee391d30da42e937db621564Steve Block pager = os.environ.get("PAGER") or "less" 122d0825bca7fe65beaee391d30da42e937db621564Steve Block try: 12321939df44de1705786c545cd1bf519d47250322dBen Murdoch # Note: Not thread safe: http://bugs.python.org/issue2320 124d0825bca7fe65beaee391d30da42e937db621564Steve Block child_process = subprocess.Popen([pager], stdin=subprocess.PIPE) 125d0825bca7fe65beaee391d30da42e937db621564Steve Block child_process.communicate(input=message) 126d0825bca7fe65beaee391d30da42e937db621564Steve Block except IOError, e: 127d0825bca7fe65beaee391d30da42e937db621564Steve Block pass 128231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 129bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def confirm(self, message=None, default=DEFAULT_YES, raw_input=raw_input): 130d0825bca7fe65beaee391d30da42e937db621564Steve Block if not message: 131d0825bca7fe65beaee391d30da42e937db621564Steve Block message = "Continue?" 132bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen choice = {'y': 'Y/n', 'n': 'y/N'}[default] 133bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen response = raw_input("%s [%s]: " % (message, choice)) 134bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen if not response: 135bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen response = default 136bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return response.lower() == 'y' 137231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block 138545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch def can_open_url(self): 139545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch try: 140545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch webbrowser.get() 141545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch return True 142545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch except webbrowser.Error, e: 143545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch return False 144545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch 145d0825bca7fe65beaee391d30da42e937db621564Steve Block def open_url(self, url): 146545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch if not self.can_open_url(): 147545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch _log.warn("Failed to open %s" % url) 148d0825bca7fe65beaee391d30da42e937db621564Steve Block webbrowser.open(url) 149