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