1

Suppose I have a string like this

"Style.BRIGHT + 'BRIGHT' + '\\n' + Style.DIM + 'DIM' + '\\n' + Style.NORMAL + 'NORMAL'"

I try to print it directly

from colorama import Style

s = "Style.BRIGHT + 'BRIGHT' + '\\n' + Style.DIM + 'DIM' + '\\n' + Style.NORMAL + 'NORMAL'"
print(s)

Output:

Style.BRIGHT + 'BRIGHT' + '\n' + Style.DIM + 'DIM' + '\n' + Style.NORMAL + 'NORMAL'

This is not the result I want, I want it to display the result in the terminal like this.

enter image description here

So I tried eval and it worked.

from colorama import Style

s = "Style.BRIGHT + 'BRIGHT' + '\\n' + Style.DIM + 'DIM' + '\\n' + Style.NORMAL + 'NORMAL'"
print(eval(s))

Output:

BRIGHT
DIM
NORMAL

Although it succeeded, it seems to be unsafe, I also tried adding the global variable {"Style": Style}, so it might be relatively safe.

But maybe I'll extract properties in other variables, like this x.y, where x and y can be any value. So it feels like eval doesn't apply.

The answer of @jfs in this question seems to be what I want, and I modified the program.

from colorama import Style

import ast
import operator as op
# supported operators
operators = {ast.Add: op.add}


def eval_expr(expr):

    return eval_(ast.parse(expr, mode='eval').body)


def eval_(node):
    if isinstance(node, ast.Constant):  # <number>
        return node.n
    elif isinstance(node, ast.BinOp):  # <left> <operator> <right>
        return operators[type(node.op)](eval_(node.left), eval_(node.right))
    elif isinstance(node, ast.Attribute):
        return ast.unparse(node)
    else:
        raise TypeError(node)


s = "Style.BRIGHT + 'BRIGHT' + '\\n' + Style.DIM + 'DIM' + '\\n' + Style.NORMAL + 'NORMAL'"
print(eval_expr(s))

Output:

Style.BRIGHTBRIGHT
Style.DIMDIM
Style.NORMALNORMAL

In the program the string Style.BRIGHT is converted into an ast.Attribute object, I don't know how to get its value('\x1b[1m').

In [1]: from colorama import Style

In [2]: Style.BRIGHT
Out[2]: '\x1b[1m'

If I use eval(ast.unparse(node)) it succeeds, but if so why not just use eval directly...

So my question is:

  • How can I extract the primitive value corresponding to the ast.Attribute object?

Or is there any other way to interpret the string.


The demo above is just one case, it can be applied to other cases. For example, I want to define a macro, which is a string that adds and sums some properties of a class, which will be applied to other programs or processes. Demo:

class Base:

    a = 1
    b = 2


def get(cls):
    s = []
    for k, v in cls.__dict__.items():
        if k.startswith("_"):
            continue
        s.append(f"{cls.__name__}.{k}")
    return " + ".join(s)


s = get(Base)
print(s)

# Base.a + Base.b

Maybe my case is a bit unsatisfactory, but this problem may appear in a future scenario, maybe it's just me wanting to know how to deal with it.

pppig
  • 1,215
  • 1
  • 6
  • 12
  • `Style.BRIGHT` is a character defined by the `colorama` package that is interpreted by your terminal as "make anything after this character bright". – Pranav Hosangadi Jan 15 '22 at 05:17
  • 2
    Why do you need to have the input as string and not use `Style` objects? – mozway Jan 15 '22 at 05:20
  • @mozway This is what I've seen from other places, and after trying it out, I'm curious how to use `ast.Attribute` and how to interpret the value corresponding to `x.y`. – pppig Jan 15 '22 at 05:32
  • "Suppose I have a string like this" - why do you *have* a string like that? The structure of that string is very tightly coupled to the specific `colorama` package you're using. If this string is coming from user input, you're tying your program's input format to the implementation details of your code in a way that makes it very awkward to change your implementation details. If the string *isn't* coming from user input, then why is it a string at all? And why is it in this format? – user2357112 Jan 15 '22 at 05:44
  • @ppig I provided another way to interpret the Styler codes, but it'd be hundred times better to use the correct object and not a string in the first place – mozway Jan 15 '22 at 05:45
  • @user2357112supportsMonica I added a bit of other possible scenarios at the end of the question. – pppig Jan 15 '22 at 06:31

1 Answers1

0

NB. It would be hundred times better to use the correct object and not a string in the first place, starting with correct input is almost always more explicit and efficient than trying to fix a broken input.

If you have a string with arbitrarily complex operations, you will need to understand its grammar, which is exactly what ast is doing, or blindly (and potentially unsafely) evaluate it with eval.

That said, if you want to simplify your process in the particular case you showed, you need to do 3 things. 1- replace the Styler definitions with the string formatting codes they represent, 2- remove the quotes and +, 3- unescape the escaped newlines.

2 and 3 are quite easy to achieve, so I'll focus only on interpreting the codes here. You can use a regex to find the Styler definitions and re.sub to replace them safely with the actual formatting code.

from colorama import Style
import re

s = "Style.BRIGHT + 'BRIGHT' + '\\n' + Style.DIM + 'DIM' + '\\n' + Style.NORMAL + 'NORMAL'"

s2 = re.sub('Style\.[A-Z]+', lambda m: getattr(Style, m.group(0).split('.')[1]), s)

Output:

"\x1b[1m + 'BRIGHT' + '\\n' + \x1b[2m + 'DIM' + '\\n' + \x1b[22m + 'NORMAL'"
mozway
  • 194,879
  • 13
  • 39
  • 75