I'm trying to write a simple console color utility that takes a class full of ANSI codes and generates some helper methods on my console utility, so that instead of doing console.add('text', 'blue')
, I can do console.blue('text')
.
I know I can define all of these statically (e.g. def blue(self, s):
), but that doesn't really scale well if I want to add 100 or so more helpers (not that I would, but if...)
Here's the simple ANSI map:
class _AnsiColors:
def __init__(self):
self.green = 35
self.red = 1
self.blue = 32
self.yellow = 214
self.amber = 208
self.olive = 106
self.orange = 166
self.purple = 18
self.pink = 197
self.gray = 243
self.dark_gray = 238
self.light_gray = 248
self.black = 0
self.white = 255
self.debug = 24
ansi = _AnsiColors()
And the console utility (which proxies methods to pyfancy
and uses colors
):
import copy
from colors import color
from pyfancy import *
from ansi import ansi
class console(object):
def __init__(self, s):
self._s = pyfancy(s)
def add(self, s, c='white'):
if hasattr(ansi, self.add.__name__):
c = self.add.__name__
self._s.add(color(s, fg=getattr(ansi, c)))
return self
def bold(self, s):
self._s.bold(s)
return self
def raw(self, s):
self._s.raw(s)
return self
def dim(self, s):
self._s.dim(s)
return self
def print(self):
self._s.output()
# Inject ansi color convenience methods
for c in vars(ansi):
setattr(console, c, copy.deepcopy(console.add))
getattr(console, c).__name__ = c
Then I can use it like so:
console('raw').bold(' bold').raw(' raw').blue(' blue').red(' red').print()
You can see that the helper methods blue
and red
at least execute, so my copying of add()
works, but what's happening here (even though I thought I could solve it with copy.deepcopy
), is that when I try and set the __name__
property of each method copy, it's setting the reference to add
instead, and I end up with all of the colored output being the same color (ansi.debug
).
Is there a way to do what I'm trying to do without statically defining each helper?
MCVE without colors/pyfancy:
import copy
from ansi import ansi
class console(object):
def __init__(self, s):
self._s = s
def add(self, s, c='white'):
if hasattr(ansi, self.add.__name__):
c = self.add.__name__
self._s += '%s(%s)' % (s, c)
return self
def print(self):
print(self._s)
# Inject ansi color convenience methods
for c in vars(ansi):
setattr(console, c, copy.deepcopy(console.add))
getattr(console, c).__name__ = c
console('white').blue(' blue').red(' red').print()
# white blue(debug) red(debug)
I would solve it with a closure:
class console(object):
def __init__(self, s):
self._s = s
for color in vars(ansi):
self._colorizer(color)
def _colorizer(self, c):
def add(s):
self._s += '%s(%s)' % (s, c)
return self
self.__setattr__(c, add)
def print(self):
print(self._s)
console('white').blue(' blue').red(' red').print()
# white blue(blue) red(red)