~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/__init__.py

  • Committer: Ian Clatworthy
  • Date: 2010-05-26 04:26:59 UTC
  • mto: (5255.2.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5256.
  • Revision ID: ian.clatworthy@canonical.com-20100526042659-2e3p4qdjr0sby0bt
Fix PDF generation of User Reference

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""UI abstraction.
18
 
 
19
 
This tells the library how to display things to the user.  Through this
20
 
layer different applications can choose the style of UI.
21
 
 
22
 
At the moment this layer is almost trivial: the application can just
23
 
choose the style of progress bar.
24
 
 
25
 
Set the ui_factory member to define the behaviour.  The default
26
 
displays no output.
 
17
"""Abstraction for interacting with the user.
 
18
 
 
19
Applications can choose different types of UI, and they deal with displaying
 
20
messages or progress to the user, and with gathering different types of input.
 
21
 
 
22
Several levels are supported, and you can also register new factories such as
 
23
for a GUI.
 
24
 
 
25
bzrlib.ui.UIFactory
 
26
    Semi-abstract base class
 
27
 
 
28
bzrlib.ui.SilentUIFactory
 
29
    Produces no output and cannot take any input; useful for programs using
 
30
    bzrlib in batch mode or for programs such as loggerhead.
 
31
 
 
32
bzrlib.ui.CannedInputUIFactory
 
33
    For use in testing; the input values to be returned are provided 
 
34
    at construction.
 
35
 
 
36
bzrlib.ui.text.TextUIFactory
 
37
    Standard text command-line interface, with stdin, stdout, stderr.
 
38
    May make more or less advanced use of them, eg in drawing progress bars,
 
39
    depending on the detected capabilities of the terminal.
 
40
    GUIs may choose to subclass this so that unimplemented methods fall
 
41
    back to working through the terminal.
27
42
"""
28
43
 
 
44
 
29
45
import os
30
46
import sys
31
47
import warnings
41
57
    trace,
42
58
    )
43
59
""")
 
60
from bzrlib.symbol_versioning import (
 
61
    deprecated_function,
 
62
    deprecated_in,
 
63
    deprecated_method,
 
64
    )
 
65
 
 
66
 
 
67
_valid_boolean_strings = dict(yes=True, no=False,
 
68
                              y=True, n=False,
 
69
                              on=True, off=False,
 
70
                              true=True, false=False)
 
71
_valid_boolean_strings['1'] = True
 
72
_valid_boolean_strings['0'] = False
 
73
 
 
74
 
 
75
def bool_from_string(s, accepted_values=None):
 
76
    """Returns a boolean if the string can be interpreted as such.
 
77
 
 
78
    Interpret case insensitive strings as booleans. The default values
 
79
    includes: 'yes', 'no, 'y', 'n', 'true', 'false', '0', '1', 'on',
 
80
    'off'. Alternative values can be provided with the 'accepted_values'
 
81
    parameter.
 
82
 
 
83
    :param s: A string that should be interpreted as a boolean. It should be of
 
84
        type string or unicode.
 
85
 
 
86
    :param accepted_values: An optional dict with accepted strings as keys and
 
87
        True/False as values. The strings will be tested against a lowered
 
88
        version of 's'.
 
89
 
 
90
    :return: True or False for accepted strings, None otherwise.
 
91
    """
 
92
    if accepted_values is None:
 
93
        accepted_values = _valid_boolean_strings
 
94
    val = None
 
95
    if type(s) in (str, unicode):
 
96
        try:
 
97
            val = accepted_values[s.lower()]
 
98
        except KeyError:
 
99
            pass
 
100
    return val
44
101
 
45
102
 
46
103
class UIFactory(object):
48
105
 
49
106
    This tells the library how to display things to the user.  Through this
50
107
    layer different applications can choose the style of UI.
 
108
 
 
109
    :ivar suppressed_warnings: Identifiers for user warnings that should 
 
110
        no be emitted.
51
111
    """
52
112
 
 
113
    _user_warning_templates = dict(
 
114
        cross_format_fetch=("Doing on-the-fly conversion from "
 
115
            "%(from_format)s to %(to_format)s.\n"
 
116
            "This may take some time. Upgrade the repositories to the "
 
117
            "same format for better performance."
 
118
            )
 
119
        )
 
120
 
53
121
    def __init__(self):
54
122
        self._task_stack = []
 
123
        self.suppressed_warnings = set()
 
124
        self._quiet = False
 
125
 
 
126
    def be_quiet(self, state):
 
127
        """Tell the UI to be more quiet, or not.
 
128
 
 
129
        Typically this suppresses progress bars; the application may also look
 
130
        at ui_factory.is_quiet().
 
131
        """
 
132
        self._quiet = state
55
133
 
56
134
    def get_password(self, prompt='', **kwargs):
57
135
        """Prompt the user for a password.
68
146
        """
69
147
        raise NotImplementedError(self.get_password)
70
148
 
 
149
    def is_quiet(self):
 
150
        return self._quiet
 
151
 
 
152
    def make_output_stream(self, encoding=None, encoding_type=None):
 
153
        """Get a stream for sending out bulk text data.
 
154
 
 
155
        This is used for commands that produce bulk text, such as log or diff
 
156
        output, as opposed to user interaction.  This should work even for
 
157
        non-interactive user interfaces.  Typically this goes to a decorated
 
158
        version of stdout, but in a GUI it might be appropriate to send it to a 
 
159
        window displaying the text.
 
160
     
 
161
        :param encoding: Unicode encoding for output; default is the 
 
162
            terminal encoding, which may be different from the user encoding.
 
163
            (See get_terminal_encoding.)
 
164
 
 
165
        :param encoding_type: How to handle encoding errors:
 
166
            replace/strict/escape/exact.  Default is replace.
 
167
        """
 
168
        # XXX: is the caller supposed to close the resulting object?
 
169
        if encoding is None:
 
170
            encoding = osutils.get_terminal_encoding()
 
171
        if encoding_type is None:
 
172
            encoding_type = 'replace'
 
173
        out_stream = self._make_output_stream_explicit(encoding, encoding_type)
 
174
        return out_stream
 
175
 
 
176
    def _make_output_stream_explicit(self, encoding, encoding_type):
 
177
        raise NotImplementedError("%s doesn't support make_output_stream"
 
178
            % (self.__class__.__name__))
 
179
 
71
180
    def nested_progress_bar(self):
72
181
        """Return a nested progress bar.
73
182
 
86
195
        if not self._task_stack:
87
196
            warnings.warn("%r finished but nothing is active"
88
197
                % (task,))
89
 
        elif task != self._task_stack[-1]:
90
 
            warnings.warn("%r is not the active task %r"
91
 
                % (task, self._task_stack[-1]))
 
198
        if task in self._task_stack:
 
199
            self._task_stack.remove(task)
92
200
        else:
93
 
            del self._task_stack[-1]
 
201
            warnings.warn("%r is not in active stack %r"
 
202
                % (task, self._task_stack))
94
203
        if not self._task_stack:
95
204
            self._progress_all_finished()
96
205
 
113
222
        """
114
223
        pass
115
224
 
 
225
    def format_user_warning(self, warning_id, message_args):
 
226
        try:
 
227
            template = self._user_warning_templates[warning_id]
 
228
        except KeyError:
 
229
            fail = "failed to format warning %r, %r" % (warning_id, message_args)
 
230
            warnings.warn(fail)   # so tests will fail etc
 
231
            return fail
 
232
        try:
 
233
            return template % message_args
 
234
        except ValueError, e:
 
235
            fail = "failed to format warning %r, %r: %s" % (
 
236
                warning_id, message_args, e)
 
237
            warnings.warn(fail)   # so tests will fail etc
 
238
            return fail
 
239
 
116
240
    def get_boolean(self, prompt):
117
241
        """Get a boolean question answered from the user.
118
242
 
122
246
        """
123
247
        raise NotImplementedError(self.get_boolean)
124
248
 
 
249
    def get_integer(self, prompt):
 
250
        """Get an integer from the user.
 
251
 
 
252
        :param prompt: a message to prompt the user with. Could be a multi-line
 
253
            prompt but without a terminating \n.
 
254
 
 
255
        :return: A signed integer.
 
256
        """
 
257
        raise NotImplementedError(self.get_integer)
 
258
 
 
259
    def make_progress_view(self):
 
260
        """Construct a new ProgressView object for this UI.
 
261
 
 
262
        Application code should normally not call this but instead
 
263
        nested_progress_bar().
 
264
        """
 
265
        return NullProgressView()
 
266
 
125
267
    def recommend_upgrade(self,
126
268
        current_format_name,
127
269
        basedir):
128
 
        # this should perhaps be in the TextUIFactory and the default can do
 
270
        # XXX: this should perhaps be in the TextUIFactory and the default can do
129
271
        # nothing
 
272
        #
 
273
        # XXX: Change to show_user_warning - that will accomplish the previous
 
274
        # xxx. -- mbp 2010-02-25
130
275
        trace.warning("%s is deprecated "
131
276
            "and a better format is available.\n"
132
277
            "It is recommended that you upgrade by "
143
288
        """
144
289
        pass
145
290
 
146
 
 
147
 
 
148
 
class CLIUIFactory(UIFactory):
149
 
    """Common behaviour for command line UI factories.
150
 
 
151
 
    This is suitable for dumb terminals that can't repaint existing text."""
152
 
 
153
 
    def __init__(self, stdin=None, stdout=None, stderr=None):
 
291
    def log_transport_activity(self, display=False):
 
292
        """Write out whatever transport activity has been measured.
 
293
 
 
294
        Implementations are allowed to do nothing, but it is useful if they can
 
295
        write a line to the log file.
 
296
 
 
297
        :param display: If False, only log to disk, if True also try to display
 
298
            a message to the user.
 
299
        :return: None
 
300
        """
 
301
        # Default implementation just does nothing
 
302
        pass
 
303
 
 
304
    def show_user_warning(self, warning_id, **message_args):
 
305
        """Show a warning to the user.
 
306
 
 
307
        This is specifically for things that are under the user's control (eg
 
308
        outdated formats), not for internal program warnings like deprecated
 
309
        APIs.
 
310
 
 
311
        This can be overridden by UIFactory subclasses to show it in some 
 
312
        appropriate way; the default UIFactory is noninteractive and does
 
313
        nothing.  format_user_warning maps it to a string, though other
 
314
        presentations can be used for particular UIs.
 
315
 
 
316
        :param warning_id: An identifier like 'cross_format_fetch' used to 
 
317
            check if the message is suppressed and to look up the string.
 
318
        :param message_args: Arguments to be interpolated into the message.
 
319
        """
 
320
        pass
 
321
 
 
322
    def show_error(self, msg):
 
323
        """Show an error message (not an exception) to the user.
 
324
        
 
325
        The message should not have an error prefix or trailing newline.  That
 
326
        will be added by the factory if appropriate.
 
327
        """
 
328
        raise NotImplementedError(self.show_error)
 
329
 
 
330
    def show_message(self, msg):
 
331
        """Show a message to the user."""
 
332
        raise NotImplementedError(self.show_message)
 
333
 
 
334
    def show_warning(self, msg):
 
335
        """Show a warning to the user."""
 
336
        raise NotImplementedError(self.show_warning)
 
337
 
 
338
    def warn_cross_format_fetch(self, from_format, to_format):
 
339
        """Warn about a potentially slow cross-format transfer.
 
340
        
 
341
        This is deprecated in favor of show_user_warning, but retained for api
 
342
        compatibility in 2.0 and 2.1.
 
343
        """
 
344
        self.show_user_warning('cross_format_fetch', from_format=from_format,
 
345
            to_format=to_format)
 
346
 
 
347
    def warn_experimental_format_fetch(self, inter):
 
348
        """Warn about fetching into experimental repository formats."""
 
349
        if inter.target._format.experimental:
 
350
            trace.warning("Fetching into experimental format %s.\n"
 
351
                "This format may be unreliable or change in the future "
 
352
                "without an upgrade path.\n" % (inter.target._format,))
 
353
 
 
354
 
 
355
 
 
356
class SilentUIFactory(UIFactory):
 
357
    """A UI Factory which never prints anything.
 
358
 
 
359
    This is the default UI, if another one is never registered by a program
 
360
    using bzrlib, and it's also active for example inside 'bzr serve'.
 
361
 
 
362
    Methods that try to read from the user raise an error; methods that do
 
363
    output do nothing.
 
364
    """
 
365
 
 
366
    def __init__(self):
154
367
        UIFactory.__init__(self)
155
 
        self.stdin = stdin or sys.stdin
156
 
        self.stdout = stdout or sys.stdout
157
 
        self.stderr = stderr or sys.stderr
158
 
 
159
 
    def get_boolean(self, prompt):
160
 
        self.clear_term()
161
 
        # FIXME: make a regexp and handle case variations as well.
162
 
        while True:
163
 
            self.prompt(prompt + "? [y/n]: ")
164
 
            line = self.stdin.readline()
165
 
            if line in ('y\n', 'yes\n'):
166
 
                return True
167
 
            if line in ('n\n', 'no\n'):
168
 
                return False
169
 
 
170
 
    def get_non_echoed_password(self, prompt):
171
 
        if not sys.stdin.isatty():
172
 
            raise errors.NotATerminal()
173
 
        encoding = osutils.get_terminal_encoding()
174
 
        return getpass.getpass(prompt.encode(encoding, 'replace'))
175
 
 
176
 
    def get_password(self, prompt='', **kwargs):
177
 
        """Prompt the user for a password.
178
 
 
179
 
        :param prompt: The prompt to present the user
180
 
        :param kwargs: Arguments which will be expanded into the prompt.
181
 
                       This lets front ends display different things if
182
 
                       they so choose.
183
 
        :return: The password string, return None if the user
184
 
                 canceled the request.
185
 
        """
186
 
        prompt += ': '
187
 
        prompt = (prompt % kwargs)
188
 
        # There's currently no way to say 'i decline to enter a password'
189
 
        # as opposed to 'my password is empty' -- does it matter?
190
 
        return self.get_non_echoed_password(prompt)
191
 
 
192
 
    def prompt(self, prompt):
193
 
        """Emit prompt on the CLI."""
194
 
        self.stdout.write(prompt)
195
368
 
196
369
    def note(self, msg):
197
 
        """Write an already-formatted message."""
198
 
        self.stdout.write(msg + '\n')
199
 
 
200
 
 
201
 
class SilentUIFactory(CLIUIFactory):
202
 
    """A UI Factory which never prints anything.
203
 
 
204
 
    This is the default UI, if another one is never registered.
205
 
    """
206
 
 
207
 
    def __init__(self):
208
 
        CLIUIFactory.__init__(self)
209
 
 
210
 
    def get_password(self, prompt='', **kwargs):
 
370
        pass
 
371
 
 
372
    def get_username(self, prompt, **kwargs):
211
373
        return None
212
374
 
213
 
    def prompt(self, prompt):
214
 
        pass
215
 
 
216
 
    def note(self, msg):
217
 
        pass
218
 
 
219
 
 
220
 
def clear_decorator(func, *args, **kwargs):
221
 
    """Decorator that clears the term"""
222
 
    ui_factory.clear_term()
223
 
    func(*args, **kwargs)
 
375
    def _make_output_stream_explicit(self, encoding, encoding_type):
 
376
        return NullOutputStream(encoding)
 
377
 
 
378
    def show_error(self, msg):
 
379
        pass
 
380
 
 
381
    def show_message(self, msg):
 
382
        pass
 
383
 
 
384
    def show_warning(self, msg):
 
385
        pass
 
386
 
 
387
 
 
388
class CannedInputUIFactory(SilentUIFactory):
 
389
    """A silent UI that return canned input."""
 
390
 
 
391
    def __init__(self, responses):
 
392
        self.responses = responses
 
393
 
 
394
    def __repr__(self):
 
395
        return "%s(%r)" % (self.__class__.__name__, self.responses)
 
396
 
 
397
    def get_boolean(self, prompt):
 
398
        return self.responses.pop(0)
 
399
 
 
400
    def get_integer(self, prompt):
 
401
        return self.responses.pop(0)
 
402
 
 
403
    def get_password(self, prompt='', **kwargs):
 
404
        return self.responses.pop(0)
 
405
 
 
406
    def get_username(self, prompt, **kwargs):
 
407
        return self.responses.pop(0)
 
408
 
 
409
    def assert_all_input_consumed(self):
 
410
        if self.responses:
 
411
            raise AssertionError("expected all input in %r to be consumed"
 
412
                % (self,))
224
413
 
225
414
 
226
415
ui_factory = SilentUIFactory()
227
 
"""IMPORTANT: never import this symbol directly. ONLY ever access it as
228
 
ui.ui_factory."""
 
416
# IMPORTANT: never import this symbol directly. ONLY ever access it as
 
417
# ui.ui_factory, so that you refer to the current value.
229
418
 
230
419
 
231
420
def make_ui_for_terminal(stdin, stdout, stderr):
232
421
    """Construct and return a suitable UIFactory for a text mode program.
233
 
 
234
 
    If stdout is a smart terminal, this gets a smart UIFactory with
235
 
    progress indicators, etc.  If it's a dumb terminal, just plain text output.
236
422
    """
237
 
    cls = None
238
 
    isatty = getattr(stdin, 'isatty', None)
239
 
    if isatty is None:
240
 
        cls = CLIUIFactory
241
 
    elif not isatty():
242
 
        cls = CLIUIFactory
243
 
    elif os.environ.get('TERM') in ('dumb', ''):
244
 
        # e.g. emacs compile window
245
 
        cls = CLIUIFactory
246
 
    # User may know better, otherwise default to TextUIFactory
247
 
    if (   os.environ.get('BZR_USE_TEXT_UI', None) is not None
248
 
        or cls is None):
249
 
        from bzrlib.ui.text import TextUIFactory
250
 
        cls = TextUIFactory
251
 
    return cls(stdin=stdin, stdout=stdout, stderr=stderr)
 
423
    # this is now always TextUIFactory, which in turn decides whether it
 
424
    # should display progress bars etc
 
425
    from bzrlib.ui.text import TextUIFactory
 
426
    return TextUIFactory(stdin, stdout, stderr)
 
427
 
 
428
 
 
429
class NullProgressView(object):
 
430
    """Soak up and ignore progress information."""
 
431
 
 
432
    def clear(self):
 
433
        pass
 
434
 
 
435
    def show_progress(self, task):
 
436
        pass
 
437
 
 
438
    def show_transport_activity(self, transport, direction, byte_count):
 
439
        pass
 
440
 
 
441
    def log_transport_activity(self, display=False):
 
442
        pass
 
443
 
 
444
 
 
445
class NullOutputStream(object):
 
446
    """Acts like a file, but discard all output."""
 
447
 
 
448
    def __init__(self, encoding):
 
449
        self.encoding = encoding
 
450
 
 
451
    def write(self, data):
 
452
        pass
 
453
 
 
454
    def writelines(self, data):
 
455
        pass
 
456
 
 
457
    def close(self):
 
458
        pass