0

I'm trying to implement colour cycling on my text in Python, ie i want it to cycle through the colour of every character typed (amongst other effects) My progress so far has been hacked together from an ansi colour recipe improvement suggestions welcomed.

I was also vaguely aware of, but never used: termcolor, colorama, curses

during the hack i managed to make the attributes not work (ie reverse blink etc) and its not perfect probably mainly because I dont understand these lines properly:

cmd.append(format % (colours[tmpword]+fgoffset))

c=format % attrs[tmpword] if tmpword in attrs else None

if anyone can clarify that a bit, I would appreciate it. this runs and does something, but its not quite there. I changed the code so instead of having to separate colour commands from your string you can include them.

 #!/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()))
bpoints = ( " [*] ", " [!] ", )

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

    cmd, txt = [reset], []
    if arg:
            if bnum != 0:
                    sys.stdout.write(bpoints[bnum-1])

        # 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')
    print
    echo()
    echo('@cyan this text has a bullet point',bnum=1)
    print
    echo('@yellow this yellow text has another bullet point',bnum=2)
    print
    echo('@blue this blue text has a bullet point and no random case',bnum=1,rndcase=False)
    print
    echo('@red this red text has no bullet point, no random case and no typing effect',txtspeed=0,bnum=0,rndcase=False)
#   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('%s @29;33 hit ctrl-c to quit' % c,rndcase=False,txtspeed=0)
            #echo('@yellow @6;66 %s' % strftime('%H:%M:%S', gmtime()))
        except KeyboardInterrupt:
            break
        except:
            raise
    echo('@10;1')
    print

should also mention that i have absolutely no idea what this line does :) - well i see that it puts colours into a dictionary object, but how it does it is confusing. not used to this python syntax yet.

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()))
  • 1
    To insert special characters inside a Python string, you can just scape then with "\xHH" where HH is the hex of the character code. Thus, no need to do `esc = '%s['%chr(27)` - just `esc = '\x1b' `does the trick – jsbueno Nov 29 '11 at 13:00
  • ah ok, ill change that bit, thankyou. im thinking of this as an all round txt effects type module, perhaps i should rethink it in a class, not that i know how to do a class, but ill figure it out :) –  Nov 29 '11 at 13:07

1 Answers1

0

This is a rather convoluted code - but, sticking to you r question, about the lines:

cmd.append(format % (colours[tmpword]+fgoffset))

This expression appends to the list named cmd the interpolation of the string contained in the variable format with the result of the expression (colours[tmpword]+fgoffset))- which concatenates the code in the color table (colours) named by tmpword with fgoffset.

The format string contains '1;%dm' which means it expects an integer number, whcih will replace the "%d" inside it. (Python's % string substitution inherits from C's printf formatting) . You "colours" color table ont he other hand is built in a convoluted way I'd recomend in no code, setting directly the entry in "globals" for it - but let's assume it does have the correct numeric value for each color entry. In that case, adding it to fgoffset will generate color codes out of range (IRCC, above 15) for some color codes and offsets.

Now the second line in which you are in doubt:

c=format % attrs[tmpword] if tmpword in attrs else None

This if is just Python's ternary operator - equivalent to the C'ish expr?:val1: val2

It is equivalent to:

if tmpword in attrs: c = format % attrs[tmpword] else: c = format % None

Note that it has less precedence than the % operator. Maybe you would prefer:

c= (format % attrs[tmpword]) if tmpword in attrs else ''

instead

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • hey! thanks a lot! thats really cleared up a lot for me. yes, its kind of working but it was an ugly hack which im trying to make better. i should mention im a total beginner and im trying to make sense of all this stuff. i dont understand what you mean about the colour table built in a convoluted way ? any improvements much appreciated. oh and i put a print out of the colour table with its numbers in the comments, just to check the nums are ok. it seems to work. –  Nov 29 '11 at 13:16
  • As for the "convoluted way" - in Python we prise readability a lot, and as part of that, modifying the dictionary returned by "globals()" should be done just in extreme cases - so, one example to simplify the start of your code is to replace the `for k, v in dict( ... enumerate(v.split()))` line with these 2 lines: `attrs = dict ((name, val) for val, name in enumerate('none bold faint italic underline blink fast reverse concealed' .split()))` and `colours = dict((name, val) for val, name in enumerate('grey red green yellow blue magenta cyan white' .split()))` – jsbueno Nov 29 '11 at 15:26
  • ok ill put that in, trying to convert it to a class at the moment. thanks again dude. –  Nov 29 '11 at 15:44