##// END OF EJS Templates
bookmarks: move color style to color
Matt Mackall -
r13361:5b425236 default
parent child Browse files
Show More
@@ -1,360 +1,358 b''
1 1 # Mercurial extension to provide the 'hg bookmark' command
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''track a line of development with movable markers
9 9
10 10 Bookmarks are local movable markers to changesets. Every bookmark
11 11 points to a changeset identified by its hash. If you commit a
12 12 changeset that is based on a changeset that has a bookmark on it, the
13 13 bookmark shifts to the new changeset.
14 14
15 15 It is possible to use bookmark names in every revision lookup (e.g.
16 16 :hg:`merge`, :hg:`update`).
17 17
18 18 By default, when several bookmarks point to the same changeset, they
19 19 will all move forward together. It is possible to obtain a more
20 20 git-like experience by adding the following configuration option to
21 21 your configuration file::
22 22
23 23 [bookmarks]
24 24 track.current = True
25 25
26 26 This will cause Mercurial to track the bookmark that you are currently
27 27 using, and only update it. This is similar to git's approach to
28 28 branching.
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import nullid, nullrev, bin, hex, short
33 33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 34 from mercurial import encoding
35 35 from mercurial import bookmarks
36 36 import os
37 37
38 38 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
39 39 '''track a line of development with movable markers
40 40
41 41 Bookmarks are pointers to certain commits that move when
42 42 committing. Bookmarks are local. They can be renamed, copied and
43 43 deleted. It is possible to use bookmark names in :hg:`merge` and
44 44 :hg:`update` to merge and update respectively to a given bookmark.
45 45
46 46 You can use :hg:`bookmark NAME` to set a bookmark on the working
47 47 directory's parent revision with the given name. If you specify
48 48 a revision using -r REV (where REV may be an existing bookmark),
49 49 the bookmark is assigned to that revision.
50 50
51 51 Bookmarks can be pushed and pulled between repositories (see :hg:`help
52 52 push` and :hg:`help pull`). This requires the bookmark extension to be
53 53 enabled for both the local and remote repositories.
54 54 '''
55 55 hexfn = ui.debugflag and hex or short
56 56 marks = repo._bookmarks
57 57 cur = repo.changectx('.').node()
58 58
59 59 if rename:
60 60 if rename not in marks:
61 61 raise util.Abort(_("a bookmark of this name does not exist"))
62 62 if mark in marks and not force:
63 63 raise util.Abort(_("a bookmark of the same name already exists"))
64 64 if mark is None:
65 65 raise util.Abort(_("new bookmark name required"))
66 66 marks[mark] = marks[rename]
67 67 del marks[rename]
68 68 if repo._bookmarkcurrent == rename:
69 69 bookmarks.setcurrent(repo, mark)
70 70 bookmarks.write(repo)
71 71 return
72 72
73 73 if delete:
74 74 if mark is None:
75 75 raise util.Abort(_("bookmark name required"))
76 76 if mark not in marks:
77 77 raise util.Abort(_("a bookmark of this name does not exist"))
78 78 if mark == repo._bookmarkcurrent:
79 79 bookmarks.setcurrent(repo, None)
80 80 del marks[mark]
81 81 bookmarks.write(repo)
82 82 return
83 83
84 84 if mark is not None:
85 85 if "\n" in mark:
86 86 raise util.Abort(_("bookmark name cannot contain newlines"))
87 87 mark = mark.strip()
88 88 if not mark:
89 89 raise util.Abort(_("bookmark names cannot consist entirely of "
90 90 "whitespace"))
91 91 if mark in marks and not force:
92 92 raise util.Abort(_("a bookmark of the same name already exists"))
93 93 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
94 94 and not force):
95 95 raise util.Abort(
96 96 _("a bookmark cannot have the name of an existing branch"))
97 97 if rev:
98 98 marks[mark] = repo.lookup(rev)
99 99 else:
100 100 marks[mark] = repo.changectx('.').node()
101 101 bookmarks.setcurrent(repo, mark)
102 102 bookmarks.write(repo)
103 103 return
104 104
105 105 if mark is None:
106 106 if rev:
107 107 raise util.Abort(_("bookmark name required"))
108 108 if len(marks) == 0:
109 109 ui.status(_("no bookmarks set\n"))
110 110 else:
111 111 for bmark, n in marks.iteritems():
112 112 if ui.configbool('bookmarks', 'track.current'):
113 113 current = repo._bookmarkcurrent
114 114 if bmark == current and n == cur:
115 115 prefix, label = '*', 'bookmarks.current'
116 116 else:
117 117 prefix, label = ' ', ''
118 118 else:
119 119 if n == cur:
120 120 prefix, label = '*', 'bookmarks.current'
121 121 else:
122 122 prefix, label = ' ', ''
123 123
124 124 if ui.quiet:
125 125 ui.write("%s\n" % bmark, label=label)
126 126 else:
127 127 ui.write(" %s %-25s %d:%s\n" % (
128 128 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
129 129 label=label)
130 130 return
131 131
132 132 def _revstostrip(changelog, node):
133 133 srev = changelog.rev(node)
134 134 tostrip = [srev]
135 135 saveheads = []
136 136 for r in xrange(srev, len(changelog)):
137 137 parents = changelog.parentrevs(r)
138 138 if parents[0] in tostrip or parents[1] in tostrip:
139 139 tostrip.append(r)
140 140 if parents[1] != nullrev:
141 141 for p in parents:
142 142 if p not in tostrip and p > srev:
143 143 saveheads.append(p)
144 144 return [r for r in tostrip if r not in saveheads]
145 145
146 146 def strip(oldstrip, ui, repo, node, backup="all"):
147 147 """Strip bookmarks if revisions are stripped using
148 148 the mercurial.strip method. This usually happens during
149 149 qpush and qpop"""
150 150 revisions = _revstostrip(repo.changelog, node)
151 151 marks = repo._bookmarks
152 152 update = []
153 153 for mark, n in marks.iteritems():
154 154 if repo.changelog.rev(n) in revisions:
155 155 update.append(mark)
156 156 oldstrip(ui, repo, node, backup)
157 157 if len(update) > 0:
158 158 for m in update:
159 159 marks[m] = repo.changectx('.').node()
160 160 bookmarks.write(repo)
161 161
162 162 def reposetup(ui, repo):
163 163 if not repo.local():
164 164 return
165 165
166 166 class bookmark_repo(repo.__class__):
167 167 def lookup(self, key):
168 168 if key in self._bookmarks:
169 169 key = self._bookmarks[key]
170 170 return super(bookmark_repo, self).lookup(key)
171 171
172 172 def pull(self, remote, heads=None, force=False):
173 173 result = super(bookmark_repo, self).pull(remote, heads, force)
174 174
175 175 self.ui.debug("checking for updated bookmarks\n")
176 176 rb = remote.listkeys('bookmarks')
177 177 changed = False
178 178 for k in rb.keys():
179 179 if k in self._bookmarks:
180 180 nr, nl = rb[k], self._bookmarks[k]
181 181 if nr in self:
182 182 cr = self[nr]
183 183 cl = self[nl]
184 184 if cl.rev() >= cr.rev():
185 185 continue
186 186 if cr in cl.descendants():
187 187 self._bookmarks[k] = cr.node()
188 188 changed = True
189 189 self.ui.status(_("updating bookmark %s\n") % k)
190 190 else:
191 191 self.ui.warn(_("not updating divergent"
192 192 " bookmark %s\n") % k)
193 193 if changed:
194 194 bookmarks.write(repo)
195 195
196 196 return result
197 197
198 198 def push(self, remote, force=False, revs=None, newbranch=False):
199 199 result = super(bookmark_repo, self).push(remote, force, revs,
200 200 newbranch)
201 201
202 202 self.ui.debug("checking for updated bookmarks\n")
203 203 rb = remote.listkeys('bookmarks')
204 204 for k in rb.keys():
205 205 if k in self._bookmarks:
206 206 nr, nl = rb[k], hex(self._bookmarks[k])
207 207 if nr in self:
208 208 cr = self[nr]
209 209 cl = self[nl]
210 210 if cl in cr.descendants():
211 211 r = remote.pushkey('bookmarks', k, nr, nl)
212 212 if r:
213 213 self.ui.status(_("updating bookmark %s\n") % k)
214 214 else:
215 215 self.ui.warn(_('updating bookmark %s'
216 216 ' failed!\n') % k)
217 217
218 218 return result
219 219
220 220 def addchangegroup(self, *args, **kwargs):
221 221 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
222 222 if result > 1:
223 223 # We have more heads than before
224 224 return result
225 225 node = self.changelog.tip()
226 226 parents = self.dirstate.parents()
227 227 bookmarks.update(self, parents, node)
228 228 return result
229 229
230 230 repo.__class__ = bookmark_repo
231 231
232 232 def pull(oldpull, ui, repo, source="default", **opts):
233 233 # translate bookmark args to rev args for actual pull
234 234 if opts.get('bookmark'):
235 235 # this is an unpleasant hack as pull will do this internally
236 236 source, branches = hg.parseurl(ui.expandpath(source),
237 237 opts.get('branch'))
238 238 other = hg.repository(hg.remoteui(repo, opts), source)
239 239 rb = other.listkeys('bookmarks')
240 240
241 241 for b in opts['bookmark']:
242 242 if b not in rb:
243 243 raise util.Abort(_('remote bookmark %s not found!') % b)
244 244 opts.setdefault('rev', []).append(b)
245 245
246 246 result = oldpull(ui, repo, source, **opts)
247 247
248 248 # update specified bookmarks
249 249 if opts.get('bookmark'):
250 250 for b in opts['bookmark']:
251 251 # explicit pull overrides local bookmark if any
252 252 ui.status(_("importing bookmark %s\n") % b)
253 253 repo._bookmarks[b] = repo[rb[b]].node()
254 254 bookmarks.write(repo)
255 255
256 256 return result
257 257
258 258 def push(oldpush, ui, repo, dest=None, **opts):
259 259 dopush = True
260 260 if opts.get('bookmark'):
261 261 dopush = False
262 262 for b in opts['bookmark']:
263 263 if b in repo._bookmarks:
264 264 dopush = True
265 265 opts.setdefault('rev', []).append(b)
266 266
267 267 result = 0
268 268 if dopush:
269 269 result = oldpush(ui, repo, dest, **opts)
270 270
271 271 if opts.get('bookmark'):
272 272 # this is an unpleasant hack as push will do this internally
273 273 dest = ui.expandpath(dest or 'default-push', dest or 'default')
274 274 dest, branches = hg.parseurl(dest, opts.get('branch'))
275 275 other = hg.repository(hg.remoteui(repo, opts), dest)
276 276 rb = other.listkeys('bookmarks')
277 277 for b in opts['bookmark']:
278 278 # explicit push overrides remote bookmark if any
279 279 if b in repo._bookmarks:
280 280 ui.status(_("exporting bookmark %s\n") % b)
281 281 new = repo[b].hex()
282 282 elif b in rb:
283 283 ui.status(_("deleting remote bookmark %s\n") % b)
284 284 new = '' # delete
285 285 else:
286 286 ui.warn(_('bookmark %s does not exist on the local '
287 287 'or remote repository!\n') % b)
288 288 return 2
289 289 old = rb.get(b, '')
290 290 r = other.pushkey('bookmarks', b, old, new)
291 291 if not r:
292 292 ui.warn(_('updating bookmark %s failed!\n') % b)
293 293 if not result:
294 294 result = 2
295 295
296 296 return result
297 297
298 298 def incoming(oldincoming, ui, repo, source="default", **opts):
299 299 if opts.get('bookmarks'):
300 300 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
301 301 other = hg.repository(hg.remoteui(repo, opts), source)
302 302 ui.status(_('comparing with %s\n') % url.hidepassword(source))
303 303 return bookmarks.diff(ui, repo, other)
304 304 else:
305 305 return oldincoming(ui, repo, source, **opts)
306 306
307 307 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
308 308 if opts.get('bookmarks'):
309 309 dest = ui.expandpath(dest or 'default-push', dest or 'default')
310 310 dest, branches = hg.parseurl(dest, opts.get('branch'))
311 311 other = hg.repository(hg.remoteui(repo, opts), dest)
312 312 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
313 313 return bookmarks.diff(ui, other, repo)
314 314 else:
315 315 return oldoutgoing(ui, repo, dest, **opts)
316 316
317 317 def uisetup(ui):
318 318 extensions.wrapfunction(repair, "strip", strip)
319 319 if ui.configbool('bookmarks', 'track.current'):
320 320 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
321 321
322 322 entry = extensions.wrapcommand(commands.table, 'pull', pull)
323 323 entry[1].append(('B', 'bookmark', [],
324 324 _("bookmark to import"),
325 325 _('BOOKMARK')))
326 326 entry = extensions.wrapcommand(commands.table, 'push', push)
327 327 entry[1].append(('B', 'bookmark', [],
328 328 _("bookmark to export"),
329 329 _('BOOKMARK')))
330 330 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
331 331 entry[1].append(('B', 'bookmarks', False,
332 332 _("compare bookmark")))
333 333 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
334 334 entry[1].append(('B', 'bookmarks', False,
335 335 _("compare bookmark")))
336 336
337 337 def updatecurbookmark(orig, ui, repo, *args, **opts):
338 338 '''Set the current bookmark
339 339
340 340 If the user updates to a bookmark we update the .hg/bookmarks.current
341 341 file.
342 342 '''
343 343 res = orig(ui, repo, *args, **opts)
344 344 rev = opts['rev']
345 345 if not rev and len(args) > 0:
346 346 rev = args[0]
347 347 bookmarks.setcurrent(repo, rev)
348 348 return res
349 349
350 350 cmdtable = {
351 351 "bookmarks":
352 352 (bookmark,
353 353 [('f', 'force', False, _('force')),
354 354 ('r', 'rev', '', _('revision'), _('REV')),
355 355 ('d', 'delete', False, _('delete a given bookmark')),
356 356 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
357 357 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
358 358 }
359
360 colortable = {'bookmarks.current': 'green'}
@@ -1,319 +1,320 b''
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''colorize output from some commands
20 20
21 21 This extension modifies the status and resolve commands to add color to their
22 22 output to reflect file status, the qseries command to add color to reflect
23 23 patch status (applied, unapplied, missing), and to diff-related
24 24 commands to highlight additions, removals, diff headers, and trailing
25 25 whitespace.
26 26
27 27 Other effects in addition to color, like bold and underlined text, are
28 28 also available. Effects are rendered with the ECMA-48 SGR control
29 29 function (aka ANSI escape codes). This module also provides the
30 30 render_text function, which can be used to add effects to any text.
31 31
32 32 Default effects may be overridden from your configuration file::
33 33
34 34 [color]
35 35 status.modified = blue bold underline red_background
36 36 status.added = green bold
37 37 status.removed = red bold blue_background
38 38 status.deleted = cyan bold underline
39 39 status.unknown = magenta bold underline
40 40 status.ignored = black bold
41 41
42 42 # 'none' turns off all effects
43 43 status.clean = none
44 44 status.copied = none
45 45
46 46 qseries.applied = blue bold underline
47 47 qseries.unapplied = black bold
48 48 qseries.missing = red bold
49 49
50 50 diff.diffline = bold
51 51 diff.extended = cyan bold
52 52 diff.file_a = red bold
53 53 diff.file_b = green bold
54 54 diff.hunk = magenta
55 55 diff.deleted = red
56 56 diff.inserted = green
57 57 diff.changed = white
58 58 diff.trailingwhitespace = bold red_background
59 59
60 60 resolve.unresolved = red bold
61 61 resolve.resolved = green bold
62 62
63 63 bookmarks.current = green
64 64
65 65 branches.active = none
66 66 branches.closed = black bold
67 67 branches.current = green
68 68 branches.inactive = none
69 69
70 70 The color extension will try to detect whether to use ANSI codes or
71 71 Win32 console APIs, unless it is made explicit::
72 72
73 73 [color]
74 74 mode = ansi
75 75
76 76 Any value other than 'ansi', 'win32', or 'auto' will disable color.
77 77
78 78 '''
79 79
80 80 import os
81 81
82 82 from mercurial import commands, dispatch, extensions, ui as uimod, util
83 83 from mercurial.i18n import _
84 84
85 85 # start and stop parameters for effects
86 86 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
87 87 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
88 88 'italic': 3, 'underline': 4, 'inverse': 7,
89 89 'black_background': 40, 'red_background': 41,
90 90 'green_background': 42, 'yellow_background': 43,
91 91 'blue_background': 44, 'purple_background': 45,
92 92 'cyan_background': 46, 'white_background': 47}
93 93
94 94 _styles = {'grep.match': 'red bold',
95 'bookmarks.current': 'green',
95 96 'branches.active': 'none',
96 97 'branches.closed': 'black bold',
97 98 'branches.current': 'green',
98 99 'branches.inactive': 'none',
99 100 'diff.changed': 'white',
100 101 'diff.deleted': 'red',
101 102 'diff.diffline': 'bold',
102 103 'diff.extended': 'cyan bold',
103 104 'diff.file_a': 'red bold',
104 105 'diff.file_b': 'green bold',
105 106 'diff.hunk': 'magenta',
106 107 'diff.inserted': 'green',
107 108 'diff.trailingwhitespace': 'bold red_background',
108 109 'diffstat.deleted': 'red',
109 110 'diffstat.inserted': 'green',
110 111 'log.changeset': 'yellow',
111 112 'resolve.resolved': 'green bold',
112 113 'resolve.unresolved': 'red bold',
113 114 'status.added': 'green bold',
114 115 'status.clean': 'none',
115 116 'status.copied': 'none',
116 117 'status.deleted': 'cyan bold underline',
117 118 'status.ignored': 'black bold',
118 119 'status.modified': 'blue bold',
119 120 'status.removed': 'red bold',
120 121 'status.unknown': 'magenta bold underline'}
121 122
122 123
123 124 def render_effects(text, effects):
124 125 'Wrap text in commands to turn on each effect.'
125 126 if not text:
126 127 return text
127 128 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 129 start = '\033[' + ';'.join(start) + 'm'
129 130 stop = '\033[' + str(_effects['none']) + 'm'
130 131 return ''.join([start, text, stop])
131 132
132 133 def extstyles():
133 134 for name, ext in extensions.extensions():
134 135 _styles.update(getattr(ext, 'colortable', {}))
135 136
136 137 def configstyles(ui):
137 138 for status, cfgeffects in ui.configitems('color'):
138 139 if '.' not in status:
139 140 continue
140 141 cfgeffects = ui.configlist('color', status)
141 142 if cfgeffects:
142 143 good = []
143 144 for e in cfgeffects:
144 145 if e in _effects:
145 146 good.append(e)
146 147 else:
147 148 ui.warn(_("ignoring unknown color/effect %r "
148 149 "(configured in color.%s)\n")
149 150 % (e, status))
150 151 _styles[status] = ' '.join(good)
151 152
152 153 class colorui(uimod.ui):
153 154 def popbuffer(self, labeled=False):
154 155 if labeled:
155 156 return ''.join(self.label(a, label) for a, label
156 157 in self._buffers.pop())
157 158 return ''.join(a for a, label in self._buffers.pop())
158 159
159 160 _colormode = 'ansi'
160 161 def write(self, *args, **opts):
161 162 label = opts.get('label', '')
162 163 if self._buffers:
163 164 self._buffers[-1].extend([(str(a), label) for a in args])
164 165 elif self._colormode == 'win32':
165 166 for a in args:
166 167 win32print(a, super(colorui, self).write, **opts)
167 168 else:
168 169 return super(colorui, self).write(
169 170 *[self.label(str(a), label) for a in args], **opts)
170 171
171 172 def write_err(self, *args, **opts):
172 173 label = opts.get('label', '')
173 174 if self._colormode == 'win32':
174 175 for a in args:
175 176 win32print(a, super(colorui, self).write_err, **opts)
176 177 else:
177 178 return super(colorui, self).write_err(
178 179 *[self.label(str(a), label) for a in args], **opts)
179 180
180 181 def label(self, msg, label):
181 182 effects = []
182 183 for l in label.split():
183 184 s = _styles.get(l, '')
184 185 if s:
185 186 effects.append(s)
186 187 effects = ''.join(effects)
187 188 if effects:
188 189 return '\n'.join([render_effects(s, effects)
189 190 for s in msg.split('\n')])
190 191 return msg
191 192
192 193
193 194 def uisetup(ui):
194 195 if ui.plain():
195 196 return
196 197 mode = ui.config('color', 'mode', 'auto')
197 198 if mode == 'auto':
198 199 if os.name == 'nt' and 'TERM' not in os.environ:
199 200 # looks line a cmd.exe console, use win32 API or nothing
200 201 mode = w32effects and 'win32' or 'none'
201 202 else:
202 203 mode = 'ansi'
203 204 if mode == 'win32':
204 205 if w32effects is None:
205 206 # only warn if color.mode is explicitly set to win32
206 207 ui.warn(_('win32console not found, please install pywin32\n'))
207 208 return
208 209 _effects.update(w32effects)
209 210 elif mode != 'ansi':
210 211 return
211 212 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 213 coloropt = opts['color']
213 214 auto = coloropt == 'auto'
214 215 always = util.parsebool(coloropt)
215 216 if (always or
216 217 (always is None and
217 218 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 219 colorui._colormode = mode
219 220 colorui.__bases__ = (ui_.__class__,)
220 221 ui_.__class__ = colorui
221 222 extstyles()
222 223 configstyles(ui_)
223 224 return orig(ui_, opts, cmd, cmdfunc)
224 225 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225 226
226 227 def extsetup(ui):
227 228 commands.globalopts.append(
228 229 ('', 'color', 'auto',
229 230 # i18n: 'always', 'auto', and 'never' are keywords and should
230 231 # not be translated
231 232 _("when to colorize (boolean, always, auto, or never)"),
232 233 _('TYPE')))
233 234
234 235 try:
235 236 import re, pywintypes, win32console as win32c
236 237
237 238 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
238 239 w32effects = {
239 240 'none': -1,
240 241 'black': 0,
241 242 'red': win32c.FOREGROUND_RED,
242 243 'green': win32c.FOREGROUND_GREEN,
243 244 'yellow': win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN,
244 245 'blue': win32c.FOREGROUND_BLUE,
245 246 'magenta': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_RED,
246 247 'cyan': win32c.FOREGROUND_BLUE | win32c.FOREGROUND_GREEN,
247 248 'white': (win32c.FOREGROUND_RED | win32c.FOREGROUND_GREEN |
248 249 win32c.FOREGROUND_BLUE),
249 250 'bold': win32c.FOREGROUND_INTENSITY,
250 251 'black_background': 0x100, # unused value > 0x0f
251 252 'red_background': win32c.BACKGROUND_RED,
252 253 'green_background': win32c.BACKGROUND_GREEN,
253 254 'yellow_background': win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN,
254 255 'blue_background': win32c.BACKGROUND_BLUE,
255 256 'purple_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_RED,
256 257 'cyan_background': win32c.BACKGROUND_BLUE | win32c.BACKGROUND_GREEN,
257 258 'white_background': (win32c.BACKGROUND_RED | win32c.BACKGROUND_GREEN |
258 259 win32c.BACKGROUND_BLUE),
259 260 'bold_background': win32c.BACKGROUND_INTENSITY,
260 261 'underline': win32c.COMMON_LVB_UNDERSCORE, # double-byte charsets only
261 262 'inverse': win32c.COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
262 263 }
263 264
264 265 passthrough = set([win32c.FOREGROUND_INTENSITY,
265 266 win32c.BACKGROUND_INTENSITY,
266 267 win32c.COMMON_LVB_UNDERSCORE,
267 268 win32c.COMMON_LVB_REVERSE_VIDEO])
268 269
269 270 try:
270 271 stdout = win32c.GetStdHandle(win32c.STD_OUTPUT_HANDLE)
271 272 if stdout is None:
272 273 raise ImportError()
273 274 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
274 275 except pywintypes.error:
275 276 # stdout may be defined but not support
276 277 # GetConsoleScreenBufferInfo(), when called from subprocess or
277 278 # redirected.
278 279 raise ImportError()
279 280 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
280 281
281 282 def win32print(text, orig, **opts):
282 283 label = opts.get('label', '')
283 284 attr = origattr
284 285
285 286 def mapcolor(val, attr):
286 287 if val == -1:
287 288 return origattr
288 289 elif val in passthrough:
289 290 return attr | val
290 291 elif val > 0x0f:
291 292 return (val & 0x70) | (attr & 0x8f)
292 293 else:
293 294 return (val & 0x07) | (attr & 0xf8)
294 295
295 296 # determine console attributes based on labels
296 297 for l in label.split():
297 298 style = _styles.get(l, '')
298 299 for effect in style.split():
299 300 attr = mapcolor(w32effects[effect], attr)
300 301
301 302 # hack to ensure regexp finds data
302 303 if not text.startswith('\033['):
303 304 text = '\033[m' + text
304 305
305 306 # Look for ANSI-like codes embedded in text
306 307 m = re.match(ansire, text)
307 308 while m:
308 309 for sattr in m.group(1).split(';'):
309 310 if sattr:
310 311 attr = mapcolor(int(sattr), attr)
311 312 stdout.SetConsoleTextAttribute(attr)
312 313 orig(m.group(2), **opts)
313 314 m = re.match(ansire, m.group(3))
314 315
315 316 # Explicity reset original attributes
316 317 stdout.SetConsoleTextAttribute(origattr)
317 318
318 319 except ImportError:
319 320 w32effects = None
General Comments 0
You need to be logged in to leave comments. Login now