Welcome, guest | Sign In | My Account | Store | Cart

This is a subclass of a standard str object that adds methods for applying color, and video attributes to text.

Python, 60 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import re

class Scale(str):
    def __len__(self):
        '''
        This ensures that you will always get the actual length of the string,
        minus the extended characters. Which is of course important, when you
        are calculating output field sizes. Requires the re module.
        '''
        tmp = self[:]
        cnt = 0
        for i in re.sub('\\x1b[\[0-9;]*m', '', tmp):
            cnt += 1
        return(cnt)

    def __getattr__(self, method):
        '''
        This is essentially an implimentation of Ruby's .method_missing
        that shortens the code dramatically, and allows for simply extending
        to support other escape codes. As a note, the modifier methods like
        .bold() and .underline() and such, need to come before the color
        methods. The color should always be the last modifier.
        '''
        method_map = {
            'black':     {'color': True,  'value': 30, 'mode': 'm'},
            'red':       {'color': True,  'value': 31, 'mode': 'm'},
            'green':     {'color': True,  'value': 32, 'mode': 'm'},
            'yellow':    {'color': True,  'value': 33, 'mode': 'm'},
            'blue':      {'color': True,  'value': 34, 'mode': 'm'},
            'purple':    {'color': True,  'value': 35, 'mode': 'm'},
            'cyan':      {'color': True,  'value': 36, 'mode': 'm'},
            'white':     {'color': True,  'value': 37, 'mode': 'm'},
            'clean':     {'color': False, 'value': 0,  'mode': 'm'},
            'bold':      {'color': False, 'value': 1,  'mode': 'm'},
            'underline': {'color': False, 'value': 4,  'mode': 'm'},
            'blink':     {'color': False, 'value': 5,  'mode': 'm'},
            'reverse':   {'color': False, 'value': 7,  'mode': 'm'},
            'conceal':   {'color': False, 'value': 8,  'mode': 'm'},
        }

        def get(self, **kwargs):
            if method_map[method]['color']:
                reset=''
            else:
                reset=''

            return(
                Scale('%s[%s%s%s%s' % (
                    reset,
                    method_map[method]['value'],
                    method_map[method]['mode'],
                    self,
                    reset
                )
            ))

        if method in method_map:
            return get.__get__(self)
        else:
            raise(AttributeError, method)

3 comments

Mike 'Fuzzy' Partin (author) 9 years, 9 months ago  # | flag

wow, I'm an idiot.

lysky 9 years, 9 months ago  # | flag

Good to know the method

Daniel 9 years, 9 months ago  # | flag

I am learning Python 3 and found your code very instructive. However I had a hard time understanding this line:

return get.__get__(self)

since I did not understand what the __get__ method of a function did.

I find that it is shorter and clearer to define attributes of the class instead of functions, as such:

import re

class Scale(str):
    def __len__(self):
        '''
        This ensures that you will always get the actual length of the string,
        minus the extended characters. Which is of course important, when you
        are calculating output field sizes. Requires the re module.
        '''

        return len( re.sub('\\x1b[\[0-9;]*m', '', self) )

    def __getattr__(self, attr):
        '''
        The modifier methods like .bold() and .underline() and such, need
        to come before the color methods. The color should always be the
        last modifier.
        '''

        attr_map = {
            'black':     {'color': True,  'value': 30, 'mode': 'm'},
            'red':       {'color': True,  'value': 31, 'mode': 'm'},
            'green':     {'color': True,  'value': 32, 'mode': 'm'},
            'yellow':    {'color': True,  'value': 33, 'mode': 'm'},
            'blue':      {'color': True,  'value': 34, 'mode': 'm'},
            'purple':    {'color': True,  'value': 35, 'mode': 'm'},
            'cyan':      {'color': True,  'value': 36, 'mode': 'm'},
            'white':     {'color': True,  'value': 37, 'mode': 'm'},
            'clean':     {'color': False, 'value': 0,  'mode': 'm'},
            'bold':      {'color': False, 'value': 1,  'mode': 'm'},
            'underline': {'color': False, 'value': 4,  'mode': 'm'},
            'blink':     {'color': False, 'value': 5,  'mode': 'm'},
            'reverse':   {'color': False, 'value': 7,  'mode': 'm'},
            'conceal':   {'color': False, 'value': 8,  'mode': 'm'},
        }

        if attr in attr_map:
            if attr_map[attr]['color']:
                reset=''
            else:
                reset=''

            return Scale('%s[%s%s%s%s' % (
                         reset,
                         attr_map[attr]['value'],
                         attr_map[attr]['mode'],
                         self,
                         reset)
                        )
        else:
            raise(AttributeError, attr)

This works in a similar way, but you use:

print(Scale("myText").underline.red)

instead of

print(Scale("myText").underline().red())

However, as I mentioned I am just learning Python 3, so I may be missing something here.