user1064306
user1064306

Reputation:

colour terminal libraries

I'm trying to implement colour cycling on my text in Python... This question has been edited and resubmitted as another question because of a massive change in context. please see here instead.

this question was more about what library I should use - termcolor, colorama, curses and an ansi colour recipe,

code so far:

#!/usr/bin/env python

'''
        "arg" is a string or None
        if "arg" is None : the terminal is reset to his default values.
        if "arg" is a string it must contain "sep" separated values.
        if args are found in globals "attrs" or "colors", or start with "@" \
    they are interpreted as ANSI commands else they are output as text.
        @* commands:

            @x;y : go to xy
            @    : go to 1;1
            @@   : clear screen and go to 1;1
        @[colour] : set foreground colour
        ^[colour] : set background colour

        examples:
    echo('@red')                  : set red as the foreground color
    echo('@red ^blue')             : red on blue
    echo('@red @blink')            : blinking red
    echo()                       : restore terminal default values
    echo('@reverse')              : swap default colors
    echo('^cyan @blue reverse')    : blue on cyan <=> echo('blue cyan)
    echo('@red @reverse')          : a way to set up the background only
    echo('@red @reverse @blink')    : you can specify any combinaison of \
            attributes in any order with or without colors
    echo('@blink Python')         : output a blinking 'Python'
    echo('@@ hello')             : clear the screen and print 'hello' at 1;1

colours:
{'blue': 4, 'grey': 0, 'yellow': 3, 'green': 2, 'cyan': 6, 'magenta': 5, 'white': 7, 'red': 1}

    '''

'''
    Set ANSI Terminal Color and Attributes.
'''
from sys import stdout
import random
import sys
import time

esc = '%s['%chr(27)
reset = '%s0m'%esc
format = '1;%dm'
fgoffset, bgoffset = 30, 40
for k, v in dict(
    attrs = 'none bold faint italic underline blink fast reverse concealed',
    colours = 'grey red green yellow blue magenta cyan white'
).items(): globals()[k]=dict((s,i) for i,s in enumerate(v.split()))

def echo(arg=None, sep=' ', end='\n', rndcase=True, txtspeed=0.03):

    cmd, txt = [reset], []
    if arg:
        # split the line up into 'sep' seperated values - arglist
            arglist=arg.split(sep)

        # cycle through arglist - word seperated list 
            for word in arglist:

                if word.startswith('@'):
            ### First check for a colour command next if deals with position ###
                # go through each fg and bg colour  
                tmpword = word[1:]
                    if tmpword in colours:
                        cmd.append(format % (colours[tmpword]+fgoffset))
                    c=format % attrs[tmpword] if tmpword in attrs else None
                    if c and c not in cmd:
                                cmd.append(c)
                    stdout.write(esc.join(cmd))
                    continue
                # positioning (starts with @)
                word=word[1:]
                if word=='@':
                    cmd.append('2J')
                    cmd.append('H')
                    stdout.write(esc.join(cmd))
                    continue
                else:
                    cmd.append('%sH'%word)
                    stdout.write(esc.join(cmd))
                    continue

                if word.startswith('^'):
            ### First check for a colour command next if deals with position ###
                # go through each fg and bg colour  
                tmpword = word[1:]
                    if tmpword in colours:
                        cmd.append(format % (colours[tmpword]+bgoffset))
                    c=format % attrs[tmpword] if tmpword in attrs else None
                    if c and c not in cmd:
                                cmd.append(c)
                    stdout.write(esc.join(cmd))
                    continue                    
            else:
                for x in word:  
                    if rndcase:
                        # thankyou mark!
                        if random.randint(0,1):
                                x = x.upper()
                        else:
                            x = x.lower()
                    stdout.write(x)
                    stdout.flush()
                    time.sleep(txtspeed)
                stdout.write(' ')
                time.sleep(txtspeed)
    if txt and end: txt[-1]+=end
    stdout.write(esc.join(cmd)+sep.join(txt))

if __name__ == '__main__':

    echo('@@') # clear screen
    #echo('@reverse') # attrs are ahem not working
    print 'default colors at 1;1 on a cleared screen'
    echo('@red hello this is red')
    echo('@blue this is blue @red i can ^blue change @yellow blah @cyan the colours in ^default the text string')
    print
    echo()
    echo('default')
    echo('@cyan ^blue cyan blue')
#   echo('@cyan ^blue @reverse cyan blue reverse')
#   echo('@blue ^cyan blue cyan')
    #echo('@red @reverse red reverse')
#    echo('yellow red yellow on red 1')
#    echo('yellow,red,yellow on red 2', sep=',')
#    print 'yellow on red 3'

#        for bg in colours:
#                echo(bg.title().center(8), sep='.', end='')
#                for fg in colours:
#                        att=[fg, bg]
#                        if fg==bg: att.append('blink')
#                        att.append(fg.center(8))
#                        echo(','.join(att), sep=',', end='')

    #for att in attrs:
    #   echo('%s,%s' % (att, att.title().center(10)), sep=',', end='')
    #   print

    from time import sleep, strftime, gmtime
    colist='@grey @blue @cyan @white @cyan @blue'.split()
    while True:
        try:
            for c in colist:
                sleep(.1)
                echo('%s @28;33 hit ctrl-c to quit' % c,txtspeed=0)
            #echo('@yellow @6;66 %s' % strftime('%H:%M:%S', gmtime()))
        except KeyboardInterrupt:
            break
        except:
            raise
    echo('@10;1')
    print

Upvotes: 2

Views: 1013

Answers (2)

Mark Tolonen
Mark Tolonen

Reputation: 177800

Here are a few techniques to try:

  1. This block of code creates lists of the actual escape strings. It uses a list comprehension to iterate over the list of color names and look up the escape codes in your colour dictionary. The .split() is just a lazy way to create a list of strings without typing lots of quote-comma-quote sequences.

    color_cycle = [
        [colour[name] for name in 'bldylw bldred bldgrn bldblu txtwht'.split()],
        [colour[name] for name in 'txtblu txtcyn'.split()]
    ]
    
  2. Later, your function can use these lists by creating an iterator. This particular iterator uses a standard library function itertools.cycle, which repeats a sequence indefinitely. I'm making an assumption here that you want to write each character of the string in a different color.

    import itertools
    
    # Create an iterator for the selected color sequence.
    if colourc:
        icolor = itertools.cycle(color_cycle[colourc - 1])
    
    for a in stringy:
        # Write out the escape code for next color
        if colourc:
            color = next(icolor)
            sys.stdout.write(color)
    
  3. Here's another way to select random case. In Python zero is considered false:

        if rndcase:
            if random.randint(0,1):
                a = a.upper()
            else:
                a = a.lower()
    

Upvotes: 1

Epcylon
Epcylon

Reputation: 4727

There are a couple problems here. First of all, why are you using 0 and 1 instead of True and False on the colourc variable? It's much easier to tell what's happening if you use proper booleans.

In the first if-block, if colourc is not 0, you write the entire string to stdout. I am surprised this doesn't actually print with colors, as it does when I run the code.

When it comes to printing one character at a time, this is where your code comes into problems. The ANSI escape sequences are not single characters, and can't be treated as such. For one thing, your random case code could clobber any ANSI-sequences it happens to hit, by randomly upper or lowercasing the m or K characters in the sequence.

When I run your code, but with rndcase=False, even the single character at a time code works fine.

You should re-think how you set the colors in your input, so that when a new color should take effect, you can print the entire ANSI-sequence, followed by the next character in the output.

Upvotes: 0

Related Questions