[PATCH stable] color/progress: subclass ui instead of using wrapfunction (issue2096)

Augie Fackler lists at durin42.com
Wed Jul 7 15:10:06 CDT 2010


On Wed, Jul 7, 2010 at 2:48 PM, Steve Borho <steve at borho.org> wrote:
> On Thu, Jul 1, 2010 at 9:46 PM, Brodie Rao <brodie at bitheap.org> wrote:
>> # HG changeset patch
>> # User Brodie Rao <brodie at bitheap.org>
>> # Date 1278030206 18000
>> # Branch stable
>> # Node ID 8e1d1b67fb3f61e1932330b182a8af98c4fb59c4
>> # Parent  239f3210c970615dc1d5f861a92b61b4662a71a5
>> color/progress: subclass ui instead of using wrapfunction (issue2096)
>>
>> This resolves the issue of hg cmd --mq not being colorized. This was due
>> to color wrapping only the instance of ui passed to dispatch._runcommand(),
>> which isn't the same ui object that mq.mqcommand() receives. After dispatch
>> calls extensions.loadall(), it makes sure any changes to ui.__class__ in
>> uisetup are propagated.
>>
>> progress is updated to wrap ui in the same manner because wrapfunction
>> doesn't play well when ui.__class__ has been replaced by another extension
>> (orig will point to the old class method instead of color's).
>
> +1 from me.  Anyone have any complaints?

Progress changes look reasonable. Sounds like we should start some
documentation for extension authors detailing when to subclass or use
wrapfunction. Currently we're at subclass ui and repo objects but use
wrapfunction on everything else, is that right?

>
>> diff --git a/hgext/color.py b/hgext/color.py
>> --- a/hgext/color.py
>> +++ b/hgext/color.py
>> @@ -74,7 +74,7 @@ Any value other than 'ansi', 'win32', or
>>
>>  import os, sys
>>
>> -from mercurial import commands, dispatch, extensions
>> +from mercurial import commands, dispatch, extensions, ui as uimod
>>  from mercurial.i18n import _
>>
>>  # start and stop parameters for effects
>> @@ -140,49 +140,50 @@ def configstyles(ui):
>>                             % (e, status))
>>             _styles[status] = ' '.join(good)
>>
>> -_buffers = None
>> -def style(msg, label):
>> -    effects = []
>> -    for l in label.split():
>> -        s = _styles.get(l, '')
>> -        if s:
>> -            effects.append(s)
>> -    effects = ''.join(effects)
>> -    if effects:
>> -        return '\n'.join([render_effects(s, effects)
>> -                          for s in msg.split('\n')])
>> -    return msg
>> +class colorui(uimod.ui):
>> +    def popbuffer(self, labeled=False):
>> +        if labeled:
>> +            return ''.join(self.label(a, label) for a, label
>> +                           in self._buffers.pop())
>> +        return ''.join(a for a, label in self._buffers.pop())
>>
>> -def popbuffer(orig, labeled=False):
>> -    global _buffers
>> -    if labeled:
>> -        return ''.join(style(a, label) for a, label in _buffers.pop())
>> -    return ''.join(a for a, label in _buffers.pop())
>> +    _colormode = 'ansi'
>> +    def write(self, *args, **opts):
>> +        label = opts.get('label', '')
>> +        if self._buffers:
>> +            self._buffers[-1].extend([(str(a), label) for a in args])
>> +        elif self._colormode == 'win32':
>> +            for a in args:
>> +                win32print(a, orig, **opts)
>> +        else:
>> +            return super(colorui, self).write(
>> +                *[self.label(str(a), label) for a in args], **opts)
>>
>> -mode = 'ansi'
>> -def write(orig, *args, **opts):
>> -    label = opts.get('label', '')
>> -    global _buffers
>> -    if _buffers:
>> -        _buffers[-1].extend([(str(a), label) for a in args])
>> -    elif mode == 'win32':
>> -        for a in args:
>> -            win32print(a, orig, **opts)
>> -    else:
>> -        return orig(*[style(str(a), label) for a in args], **opts)
>> +    def write_err(self, *args, **opts):
>> +        label = opts.get('label', '')
>> +        if self._colormode == 'win32':
>> +            for a in args:
>> +                win32print(a, orig, **opts)
>> +        else:
>> +            return super(colorui, self).write(
>> +                *[self.label(str(a), label) for a in args], **opts)
>>
>> -def write_err(orig, *args, **opts):
>> -    label = opts.get('label', '')
>> -    if mode == 'win32':
>> -        for a in args:
>> -            win32print(a, orig, **opts)
>> -    else:
>> -        return orig(*[style(str(a), label) for a in args], **opts)
>> +    def label(self, msg, label):
>> +        effects = []
>> +        for l in label.split():
>> +            s = _styles.get(l, '')
>> +            if s:
>> +                effects.append(s)
>> +        effects = ''.join(effects)
>> +        if effects:
>> +            return '\n'.join([render_effects(s, effects)
>> +                              for s in msg.split('\n')])
>> +        return msg
>> +
>>
>>  def uisetup(ui):
>>     if ui.plain():
>>         return
>> -    global mode
>>     mode = ui.config('color', 'mode', 'auto')
>>     if mode == 'auto':
>>         if os.name == 'nt' and 'TERM' not in os.environ:
>> @@ -202,14 +203,11 @@ def uisetup(ui):
>>         if (opts['color'] == 'always' or
>>             (opts['color'] == 'auto' and (os.environ.get('TERM') != 'dumb'
>>                                           and ui_.formatted()))):
>> -            global _buffers
>> -            _buffers = ui_._buffers
>> -            extensions.wrapfunction(ui_, 'popbuffer', popbuffer)
>> -            extensions.wrapfunction(ui_, 'write', write)
>> -            extensions.wrapfunction(ui_, 'write_err', write_err)
>> -            ui_.label = style
>> +            colorui._colormode = mode
>> +            colorui.__bases__ = (ui_.__class__,)
>> +            ui_.__class__ = colorui
>>             extstyles()
>> -            configstyles(ui)
>> +            configstyles(ui_)
>>         return orig(ui_, opts, cmd, cmdfunc)
>>     extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
>>
>> diff --git a/hgext/progress.py b/hgext/progress.py
>> --- a/hgext/progress.py
>> +++ b/hgext/progress.py
>> @@ -45,7 +45,6 @@ num characters, or ``+<num>`` for the fi
>>  import sys
>>  import time
>>
>> -from mercurial import extensions
>>  from mercurial import util
>>
>>  def spacejoin(*args):
>> @@ -159,7 +158,7 @@ class progbar(object):
>>         tw = util.termwidth()
>>         return min(int(self.ui.config('progress', 'width', default=tw)), tw)
>>
>> -    def progress(self, orig, topic, pos, item='', unit='', total=None):
>> +    def progress(self, topic, pos, item='', unit='', total=None):
>>         if pos is None:
>>             if self.topics and self.topics[-1] == topic and self.printed:
>>                 self.complete()
>> @@ -172,29 +171,35 @@ class progbar(object):
>>                 and topic == self.topics[-1]):
>>                 self.lastprint = now
>>                 self.show(topic, pos, item, unit, total)
>> -        return orig(topic, pos, item=item, unit=unit, total=total)
>> -
>> -    def write(self, orig, *args, **opts):
>> -        if self.printed:
>> -            self.clear()
>> -        return orig(*args, **opts)
>> -
>> -sharedprog = None
>>
>>  def uisetup(ui):
>> +    class progressui(ui.__class__):
>> +        _progbar = None
>> +
>> +        def progress(self, *args, **opts):
>> +            self._progbar.progress(*args, **opts)
>> +            return super(progressui, self).progress(*args, **opts)
>> +
>> +        def write(self, *args, **opts):
>> +            if self._progbar.printed:
>> +                self._progbar.clear()
>> +            return super(progressui, self).write(*args, **opts)
>> +
>> +        def write_err(self, *args, **opts):
>> +            if self._progbar.printed:
>> +                self._progbar.clear()
>> +            return super(progressui, self).write_err(*args, **opts)
>> +
>>     # Apps that derive a class from ui.ui() can use
>>     # setconfig('progress', 'disable', 'True') to disable this extension
>>     if ui.configbool('progress', 'disable'):
>>         return
>>     if shouldprint(ui) and not ui.debugflag and not ui.quiet:
>> +        ui.__class__ = progressui
>>         # we instantiate one globally shared progress bar to avoid
>>         # competing progress bars when multiple UI objects get created
>> -        global sharedprog
>> -        if not sharedprog:
>> -            sharedprog = progbar(ui)
>> -        extensions.wrapfunction(ui, 'progress', sharedprog.progress)
>> -        extensions.wrapfunction(ui, 'write', sharedprog.write)
>> -        extensions.wrapfunction(ui, 'write_err', sharedprog.write)
>> +        if not progressui._progbar:
>> +            progressui._progbar = progbar(ui)
>>
>>  def reposetup(ui, repo):
>>     uisetup(repo.ui)
>> diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
>> --- a/mercurial/dispatch.py
>> +++ b/mercurial/dispatch.py
>> @@ -388,6 +388,8 @@ def _dispatch(ui, args):
>>     # times so we keep track of configured extensions in _loaded.
>>     extensions.loadall(lui)
>>     exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
>> +    # Propagate any changes to lui.__class__ by extensions
>> +    ui.__class__ = lui.__class__
>>
>>     # (uisetup and extsetup are handled in extensions.loadall)
>>
>> diff --git a/tests/test-mq b/tests/test-mq
>> --- a/tests/test-mq
>> +++ b/tests/test-mq
>> @@ -80,6 +80,9 @@ echo '  .hgignore:'
>>  cat .hg/patches/.hgignore
>>  echo '  series:'
>>  cat .hg/patches/series
>> +
>> +echo '% status --mq with color (issue2096)'
>> +hg status --mq --config extensions.color= --color=always
>>  cd ..
>>
>>  echo '% init --mq without repo'
>> diff --git a/tests/test-mq.out b/tests/test-mq.out
>> --- a/tests/test-mq.out
>> +++ b/tests/test-mq.out
>> @@ -93,6 +93,11 @@ bleh
>>   series:
>>  A
>>  B
>> +% status --mq with color (issue2096)
>> + [0;32;1mA .hgignore [0m
>> + [0;32;1mA A [0m
>> + [0;32;1mA B [0m
>> + [0;32;1mA series [0m
>>  % init --mq without repo
>>  abort: There is no Mercurial repository here (.hg not found)
>>  % init --mq with repo path
>>
>
>
>
> --
> Steve Borho
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at selenic.com
> http://selenic.com/mailman/listinfo/mercurial-devel
>


More information about the Mercurial-devel mailing list