~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/ui/__init__.py

  • Committer: John Arbash Meinel
  • Date: 2010-05-11 10:45:26 UTC
  • mto: This revision was merged to the branch mainline in revision 5225.
  • Revision ID: john@arbash-meinel.com-20100511104526-zxnstcxta22hzw2n
Implement a compiled extension for parsing the text key out of a CHKInventory value.

Related to bug #562666. This seems to shave 5-10% out of the time spent doing a complete
branch of bzr.dev/launchpad/etc.

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
22
22
Several levels are supported, and you can also register new factories such as
23
23
for a GUI.
24
24
 
25
 
UIFactory
 
25
bzrlib.ui.UIFactory
26
26
    Semi-abstract base class
27
27
 
28
 
SilentUIFactory
 
28
bzrlib.ui.SilentUIFactory
29
29
    Produces no output and cannot take any input; useful for programs using
30
30
    bzrlib in batch mode or for programs such as loggerhead.
31
31
 
32
 
CannedInputUIFactory
 
32
bzrlib.ui.CannedInputUIFactory
33
33
    For use in testing; the input values to be returned are provided 
34
34
    at construction.
35
35
 
36
 
TextUIFactory
 
36
bzrlib.ui.text.TextUIFactory
37
37
    Standard text command-line interface, with stdin, stdout, stderr.
38
38
    May make more or less advanced use of them, eg in drawing progress bars,
39
39
    depending on the detected capabilities of the terminal.
105
105
 
106
106
    This tells the library how to display things to the user.  Through this
107
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.
108
111
    """
109
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
 
110
121
    def __init__(self):
111
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
112
133
 
113
134
    def get_password(self, prompt='', **kwargs):
114
135
        """Prompt the user for a password.
125
146
        """
126
147
        raise NotImplementedError(self.get_password)
127
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
 
128
180
    def nested_progress_bar(self):
129
181
        """Return a nested progress bar.
130
182
 
143
195
        if not self._task_stack:
144
196
            warnings.warn("%r finished but nothing is active"
145
197
                % (task,))
146
 
        elif task != self._task_stack[-1]:
147
 
            warnings.warn("%r is not the active task %r"
148
 
                % (task, self._task_stack[-1]))
 
198
        if task in self._task_stack:
 
199
            self._task_stack.remove(task)
149
200
        else:
150
 
            del self._task_stack[-1]
 
201
            warnings.warn("%r is not in active stack %r"
 
202
                % (task, self._task_stack))
151
203
        if not self._task_stack:
152
204
            self._progress_all_finished()
153
205
 
170
222
        """
171
223
        pass
172
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
 
173
240
    def get_boolean(self, prompt):
174
241
        """Get a boolean question answered from the user.
175
242
 
179
246
        """
180
247
        raise NotImplementedError(self.get_boolean)
181
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
 
182
259
    def make_progress_view(self):
183
260
        """Construct a new ProgressView object for this UI.
184
261
 
190
267
    def recommend_upgrade(self,
191
268
        current_format_name,
192
269
        basedir):
193
 
        # 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
194
271
        # nothing
 
272
        #
 
273
        # XXX: Change to show_user_warning - that will accomplish the previous
 
274
        # xxx. -- mbp 2010-02-25
195
275
        trace.warning("%s is deprecated "
196
276
            "and a better format is available.\n"
197
277
            "It is recommended that you upgrade by "
208
288
        """
209
289
        pass
210
290
 
211
 
 
212
 
 
213
 
class CLIUIFactory(UIFactory):
214
 
    """Deprecated in favor of TextUIFactory."""
215
 
 
216
 
    @deprecated_method(deprecated_in((1, 18, 0)))
217
 
    def __init__(self, stdin=None, stdout=None, stderr=None):
218
 
        UIFactory.__init__(self)
219
 
        self.stdin = stdin or sys.stdin
220
 
        self.stdout = stdout or sys.stdout
221
 
        self.stderr = stderr or sys.stderr
222
 
 
223
 
    _accepted_boolean_strings = dict(y=True, n=False, yes=True, no=False)
224
 
 
225
 
    def get_boolean(self, prompt):
226
 
        while True:
227
 
            self.prompt(prompt + "? [y/n]: ")
228
 
            line = self.stdin.readline()
229
 
            line = line.rstrip('\n')
230
 
            val = bool_from_string(line, self._accepted_boolean_strings)
231
 
            if val is not None:
232
 
                return val
233
 
 
234
 
    def get_non_echoed_password(self):
235
 
        isatty = getattr(self.stdin, 'isatty', None)
236
 
        if isatty is not None and isatty():
237
 
            # getpass() ensure the password is not echoed and other
238
 
            # cross-platform niceties
239
 
            password = getpass.getpass('')
240
 
        else:
241
 
            # echo doesn't make sense without a terminal
242
 
            password = self.stdin.readline()
243
 
            if not password:
244
 
                password = None
245
 
            elif password[-1] == '\n':
246
 
                password = password[:-1]
247
 
        return password
248
 
 
249
 
    def get_password(self, prompt='', **kwargs):
250
 
        """Prompt the user for a password.
251
 
 
252
 
        :param prompt: The prompt to present the user
253
 
        :param kwargs: Arguments which will be expanded into the prompt.
254
 
                       This lets front ends display different things if
255
 
                       they so choose.
256
 
        :return: The password string, return None if the user
257
 
                 canceled the request.
258
 
        """
259
 
        prompt += ': '
260
 
        self.prompt(prompt, **kwargs)
261
 
        # There's currently no way to say 'i decline to enter a password'
262
 
        # as opposed to 'my password is empty' -- does it matter?
263
 
        return self.get_non_echoed_password()
264
 
 
265
 
    def get_username(self, prompt, **kwargs):
266
 
        """Prompt the user for a username.
267
 
 
268
 
        :param prompt: The prompt to present the user
269
 
        :param kwargs: Arguments which will be expanded into the prompt.
270
 
                       This lets front ends display different things if
271
 
                       they so choose.
272
 
        :return: The username string, return None if the user
273
 
                 canceled the request.
274
 
        """
275
 
        prompt += ': '
276
 
        self.prompt(prompt, **kwargs)
277
 
        username = self.stdin.readline()
278
 
        if not username:
279
 
            username = None
280
 
        elif username[-1] == '\n':
281
 
            username = username[:-1]
282
 
        return username
283
 
 
284
 
    def prompt(self, prompt, **kwargs):
285
 
        """Emit prompt on the CLI.
286
 
        
287
 
        :param kwargs: Dictionary of arguments to insert into the prompt,
288
 
            to allow UIs to reformat the prompt.
289
 
        """
290
 
        if kwargs:
291
 
            # See <https://launchpad.net/bugs/365891>
292
 
            prompt = prompt % kwargs
293
 
        prompt = prompt.encode(osutils.get_terminal_encoding(), 'replace')
294
 
        self.clear_term()
295
 
        self.stderr.write(prompt)
296
 
 
297
 
    def note(self, msg):
298
 
        """Write an already-formatted message."""
299
 
        self.stdout.write(msg + '\n')
 
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
 
300
354
 
301
355
 
302
356
class SilentUIFactory(UIFactory):
318
372
    def get_username(self, prompt, **kwargs):
319
373
        return None
320
374
 
 
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
 
321
387
 
322
388
class CannedInputUIFactory(SilentUIFactory):
323
389
    """A silent UI that return canned input."""
331
397
    def get_boolean(self, prompt):
332
398
        return self.responses.pop(0)
333
399
 
 
400
    def get_integer(self, prompt):
 
401
        return self.responses.pop(0)
 
402
 
334
403
    def get_password(self, prompt='', **kwargs):
335
404
        return self.responses.pop(0)
336
405
 
337
406
    def get_username(self, prompt, **kwargs):
338
407
        return self.responses.pop(0)
339
 
    
 
408
 
340
409
    def assert_all_input_consumed(self):
341
410
        if self.responses:
342
411
            raise AssertionError("expected all input in %r to be consumed"
343
412
                % (self,))
344
413
 
345
414
 
346
 
@deprecated_function(deprecated_in((1, 18, 0)))
347
 
def clear_decorator(func, *args, **kwargs):
348
 
    """Decorator that clears the term"""
349
 
    ui_factory.clear_term()
350
 
    func(*args, **kwargs)
351
 
 
352
 
 
353
415
ui_factory = SilentUIFactory()
354
416
# IMPORTANT: never import this symbol directly. ONLY ever access it as
355
417
# ui.ui_factory, so that you refer to the current value.
375
437
 
376
438
    def show_transport_activity(self, transport, direction, byte_count):
377
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