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 sys.platform != "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, 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, 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 subprocess.call(args + files) 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, 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, 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) 176