cross-platform coloured text in terminal

Discussion in 'Python' started by Jonathan Hartley, Apr 16, 2010.

  1. Hi,

    It irks me that I know of no simple cross-platform way to print
    colored terminal text from Python.

    As I understand it, printing ANSI escape codes (as wrapped nicely by
    module termcolor and others) works on Macs and *nix, but only works on
    Windows if one has installed the ANSI.SYS device driver, which most
    users have not. However, on Windows, there is an alternative method,
    which is to make win32 calls via ctypes.

    I'd like to try and unite these different implementations under a
    single cross-platform API. Has this been done already? I understand
    that the detailed capabilities of the two implementations (eg. dim/
    bright colors) might not map neatly, but at least for simple colored
    text, it should be OK.

    I'm playing with ideas of what API to expose. My favourite one is to
    simply embed ANSI codes in the stream to be printed. Then this will
    work as-is on Mac and *nix. To make it work on Windows, printing could
    be done to a file0-like object which wraps stdout:


    class ColorStream(object):

    def __init__(self, wrapped):
    self.wrapped = wrapped

    def write(self, text):
    # magic goes here
    self.wrapped.write(text)

    def __getattr__(self, name):
    return getattr(self.wrapped, name)

    term = ColorTerm(sys.stdout)
    print <<term, ANSI.GREEN + "hello"

    The idea being that in place of 'magic goes here', there will be code
    that, on Windows, searches 'text' for ANSI escape codes, strips them
    from the text, and converts them into the appropriate win32 calls.

    For extra nasty magic, either the module or the user of the module
    could wrap sys.stdout globally:

    sys.stdout = ColoredStream(sys.stdout)

    Then print statements in the user's code would simply be:

    print ANSI.GREEN + "hello"

    and this would work on all platforms.

    No doubt there are many problems with these ideas. I would love to
    hear about them. Many thanks.
    Jonathan Hartley, Apr 16, 2010
    #1
    1. Advertising

  2. On Apr 16, 10:28 am, Jonathan Hartley <> wrote:
    > Hi,
    >
    > It irks me that I know of no simple cross-platform way to print
    > colored terminal text from Python.
    >
    > As I understand it, printing ANSI escape codes (as wrapped nicely by
    > module termcolor and others) works on Macs and *nix, but only works on
    > Windows if one has installed the ANSI.SYS device driver, which most
    > users have not. However, on Windows, there is an alternative method,
    > which is to make win32 calls via ctypes.
    >
    > I'd like to try and unite these different implementations under a
    > single cross-platform API. Has this been done already? I understand
    > that the detailed capabilities of the two implementations (eg. dim/
    > bright colors) might not map neatly, but at least for simple colored
    > text, it should be OK.
    >
    > I'm playing with ideas of what API to expose. My favourite one is to
    > simply embed ANSI codes in the stream to be printed. Then this will
    > work as-is on Mac and *nix. To make it work on Windows, printing could
    > be done to a file0-like object which wraps stdout:
    >
    > class ColorStream(object):
    >
    >     def __init__(self, wrapped):
    >         self.wrapped = wrapped
    >
    >     def write(self, text):
    >         # magic goes here
    >         self.wrapped.write(text)
    >
    >     def __getattr__(self, name):
    >         return getattr(self.wrapped, name)
    >
    > term = ColorTerm(sys.stdout)
    > print <<term, ANSI.GREEN + "hello"
    >
    > The idea being that in place of 'magic goes here', there will be code
    > that, on Windows, searches 'text' for ANSI escape codes, strips them
    > from the text, and converts them into the appropriate win32 calls.
    >
    > For extra nasty magic, either the module or the user of the module
    > could wrap sys.stdout globally:
    >
    > sys.stdout = ColoredStream(sys.stdout)
    >
    > Then print statements in the user's code would simply be:
    >
    > print ANSI.GREEN + "hello"
    >
    > and this would work on all platforms.
    >
    > No doubt there are many problems with these ideas. I would love to
    > hear about them. Many thanks.



    Sorry, I forgot to mention: The reason I like this idea is that, in
    theory, all existing libraries like termcolor will then work,
    unmodified, on all platforms.
    Jonathan Hartley, Apr 16, 2010
    #2
    1. Advertising

  3. Jonathan Hartley

    Lie Ryan Guest

    On 04/16/10 19:28, Jonathan Hartley wrote:
    > I'm playing with ideas of what API to expose. My favourite one is to
    > simply embed ANSI codes in the stream to be printed. Then this will
    > work as-is on Mac and *nix. To make it work on Windows, printing could
    > be done to a file0-like object which wraps stdout:


    The problem with that is you're simply reinventing ANSI.SYS device driver.

    An alternative API is you could override .__add__(), like so (completely
    untested):

    class Color(object):
    def __init__(self, color):
    self.color = map_the_color(color)
    self.string = ""
    def __add__(self, string):
    self.string += string
    return self
    def __str__(self):
    if terminal_can_do_ansi_color:
    return ansicolorescape(self.string, self.color)
    elif windows:
    syscalltocolor(self.color)
    print self.string
    syscalltocolor(reset the color)
    return ""

    GREEN = Color('green')
    print GREEN + "Great" + "Good"

    you can even go a bit further and allow chained calls (again, completely
    untested, but you get the idea):

    class Color(object):
    def __init__(self, color):
    self.color = map_the_color(color)
    self.stack = []
    def __add__(self, string):
    if isinstance(string, Color):
    # not a string, chain the calls
    self.stack.append((string.color, []]))
    else:
    # a string,
    self.stack[-1][1].append(string)
    return self
    def __radd__(self, string):
    self.stack.append([self.default, string])
    return self

    def __str__(self):
    if ansi_capable:
    return colorescape(format, string)
    elif windows:
    for format, string in self.stack:
    syscalltocolor(color)
    print string
    return ""

    GREEN = Color('green')
    RED = Color('red')

    print "Fairly" + GREEN + "Great" + RED + "Poor"

    or something like that, and you will have an API that works
    transparently on all platforms. The downside is that you cannot call
    str(GREEN + "foo") on windows.
    Lie Ryan, Apr 16, 2010
    #3
  4. On Apr 16, 5:59 pm, Lie Ryan <> wrote:
    > On 04/16/10 19:28, Jonathan Hartley wrote:
    >
    > > I'm playing with ideas of what API to expose. My favourite one is to
    > > simply embed ANSI codes in the stream to be printed. Then this will
    > > work as-is on Mac and *nix. To make it work on Windows, printing could
    > > be done to a file0-like object which wraps stdout:

    >
    > The problem with that is you're simply reinventing ANSI.SYS device driver..
    >
    > An alternative API is you could override .__add__(), like so (completely
    > untested):
    >
    > class Color(object):
    >    def __init__(self, color):
    >        self.color =  map_the_color(color)
    >        self.string = ""
    >    def __add__(self, string):
    >        self.string += string
    >        return self
    >    def __str__(self):
    >        if terminal_can_do_ansi_color:
    >            return ansicolorescape(self.string, self.color)
    >        elif windows:
    >            syscalltocolor(self.color)
    >            print self.string
    >            syscalltocolor(reset the color)
    >            return ""
    >
    > GREEN = Color('green')
    > print GREEN + "Great" + "Good"
    >
    > you can even go a bit further and allow chained calls (again, completely
    > untested, but you get the idea):
    >
    > class Color(object):
    >    def __init__(self, color):
    >        self.color =  map_the_color(color)
    >        self.stack = []
    >    def __add__(self, string):
    >        if isinstance(string, Color):
    >            # not a string, chain the calls
    >            self.stack.append((string.color, []]))
    >        else:
    >            # a string,
    >            self.stack[-1][1].append(string)
    >        return self
    >    def __radd__(self, string):
    >        self.stack.append([self.default, string])
    >        return self
    >
    >    def __str__(self):
    >        if ansi_capable:
    >            return colorescape(format, string)
    >        elif windows:
    >            for format, string in self.stack:
    >                syscalltocolor(color)
    >                print string
    >                return ""
    >
    > GREEN = Color('green')
    > RED = Color('red')
    >
    > print "Fairly" + GREEN + "Great" + RED + "Poor"
    >
    > or something like that, and you will have an API that works
    > transparently on all platforms. The downside is that you cannot call
    > str(GREEN + "foo") on windows.




    Hey Lie,

    Thanks heaps for the reply!

    >> The problem with that is you're simply reinventing ANSI.SYS device driver.


    I don't see that as a problem - in fact I think it's exactly my
    goal! :)

    The difference is that the ANSI driver requires installation and a
    reboot on the end-user's computer, which is a fiddly and intrusive
    thing for a Python developer to achieve. Whereas doing the same job in
    a Python module is easy to use for the Python developer - they just
    import the module, maybe call an 'init()' function, and then the ANSI
    functionality works on all platforms.

    Your ideas about generating and chaining the ANSI code strings are
    great. I worry though, about intermingling the code that generates
    ANSI escape sequences with the code which makes them work on Windows.
    The problem is that then, only applications which use your ANSI-
    generation library will work on Windows. Whereas if these two things
    are kept separate, then applications which use any other ANSI-
    generation techniques, such as using 'termcolor', or manaully printing
    raw ANSI sequences, these can also all work on Windows too, simply by
    adding an import and an 'init()' call to the start of the application.

    Am I making sense? Many thanks for your thoughts.

    Jonathan
    Jonathan Hartley, Apr 17, 2010
    #4
  5. On Apr 17, 11:52 am, Jonathan Hartley <> wrote:
    > On Apr 16, 5:59 pm, Lie Ryan <> wrote:
    >
    >
    >
    > > On 04/16/10 19:28, Jonathan Hartley wrote:

    >
    > > > I'm playing with ideas of what API to expose. My favourite one is to
    > > > simply embed ANSI codes in the stream to be printed. Then this will
    > > > work as-is on Mac and *nix. To make it work on Windows, printing could
    > > > be done to a file0-like object which wraps stdout:

    >
    > > The problem with that is you're simply reinventing ANSI.SYS device driver.

    >
    > > An alternative API is you could override .__add__(), like so (completely
    > > untested):

    >
    > > classColor(object):
    > >    def __init__(self,color):
    > >        self.color=  map_the_color(color)
    > >        self.string = ""
    > >    def __add__(self, string):
    > >        self.string += string
    > >        return self
    > >    def __str__(self):
    > >        if terminal_can_do_ansi_color:
    > >            return ansicolorescape(self.string, self.color)
    > >        elif windows:
    > >            syscalltocolor(self.color)
    > >            print self.string
    > >            syscalltocolor(reset thecolor)
    > >            return ""

    >
    > > GREEN =Color('green')
    > > print GREEN + "Great" + "Good"

    >
    > > you can even go a bit further and allow chained calls (again, completely
    > > untested, but you get the idea):

    >
    > > classColor(object):
    > >    def __init__(self,color):
    > >        self.color=  map_the_color(color)
    > >        self.stack = []
    > >    def __add__(self, string):
    > >        if isinstance(string,Color):
    > >            # not a string, chain the calls
    > >            self.stack.append((string.color, []]))
    > >        else:
    > >            # a string,
    > >            self.stack[-1][1].append(string)
    > >        return self
    > >    def __radd__(self, string):
    > >        self.stack.append([self.default, string])
    > >        return self

    >
    > >    def __str__(self):
    > >        if ansi_capable:
    > >            return colorescape(format, string)
    > >        elif windows:
    > >            for format, string in self.stack:
    > >                syscalltocolor(color)
    > >                print string
    > >                return ""

    >
    > > GREEN =Color('green')
    > > RED =Color('red')

    >
    > > print "Fairly" + GREEN + "Great" + RED + "Poor"

    >
    > > or something like that, and you will have an API that works
    > > transparently on all platforms. The downside is that you cannot call
    > > str(GREEN + "foo") on windows.

    >
    > Hey Lie,
    >
    > Thanks heaps for the reply!
    >
    > >> The problem with that is you're simply reinventing ANSI.SYS device driver.

    >
    > I don't see that as a problem - in fact I think it's exactly my
    > goal! :)
    >
    > The difference is that the ANSI driver requires installation and a
    > reboot on the end-user's computer, which is a fiddly and intrusive
    > thing for a Python developer to achieve. Whereas doing the same job in
    > a Python module is easy to use for the Python developer - they just
    > import the module, maybe call an 'init()' function, and then the ANSI
    > functionality works on all platforms.
    >
    > Your ideas about generating and chaining the ANSI code strings are
    > great. I worry though, about intermingling the code that generates
    > ANSI escape sequences with the code which makes them work on Windows.
    > The problem is that then, only applications which use your ANSI-
    > generation library will work on Windows. Whereas if these two things
    > are kept separate, then applications which use any other ANSI-
    > generation techniques, such as using 'termcolor', or manaully printing
    > raw ANSI sequences, these can also all work on Windows too, simply by
    > adding an import and an 'init()' call to the start of the application.
    >
    > Am I making sense? Many thanks for your thoughts.
    >
    >   Jonathan



    I have implemented these ideas here. It seems to work.
    http://pypi.python.org/pypi/colorama
    Jonathan Hartley, Apr 20, 2010
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    2
    Views:
    354
  2. sudip

    coloured text...

    sudip, Jul 1, 2005, in forum: C Programming
    Replies:
    4
    Views:
    394
    Dik T. Winter
    Jul 7, 2005
  3. Ghai

    coloured text in console

    Ghai, Apr 19, 2006, in forum: C++
    Replies:
    4
    Views:
    404
    Alf P. Steinbach
    Apr 19, 2006
  4. gaurav kashyap
    Replies:
    3
    Views:
    6,619
    Paul Boddie
    Oct 31, 2008
  5. Tomás Ó hÉilidhe

    Cross-platform: Coloured text, Networking, Multithreading

    Tomás Ó hÉilidhe, Nov 12, 2008, in forum: C Programming
    Replies:
    34
    Views:
    934
    CBFalconer
    Nov 14, 2008
Loading...

Share This Page