Forum Archive

StaSH - is it possible to get color output from a script to the StaSH console?

MikeBeard

I'm trying to see if there is a way to get colorized output from a script I'm running.

In regular Python, I get color, as I'm using escape codes to the terminal. In StaSH, I obviously don't get that (and thankfully, I don't see the escape codes either, so that's good).

Is there a way to do this programmatically from Python, if I know I'm working with StaSH?

Any help or pointers would be appreciated.

Thanks,
Mike

ccc

https://forum.omz-software.com/topic/4131/colorama-see-characters-in-color

JonB

Stash includes pyte which is a terminal emulator, and includes ANSI escape codes.

I thought you could use normal print commands from within stash with the appropriate escape codes --though possibly not all of them are supported. But, I could be wrong. stash also has some other "shortcut" methods -- see for example ls.py within stash/bin which uses color. telnet.py also supports color, I believe by instantiating. pyte.Screen directly, though I am not entirely sure how it gets attached to the stash pyte stream.

MikeBeard

Thanks everyone. I'll take a look at both.

MikeBeard

So, I tried to go the route that it looks like ls and telnet use in StaSH by using the following:

_stash = globals()['_stash']

Then, once you have that, then you can use __stash.text_color() and the like.

The only issue is that when I try this using a script that is run IN StaSH, I get: Error: '_stash'.

To me, this means that this variable is NOT in the globals dictionary.

So, is there something else that a script running in StaSH (but not part of StaSH itself) needs to do? Does anyone have an idea?

Thanks so much! I'll keep looking and trying as well, but any help would be appreciated.

Thanks again,
Mike

MikeBeard

@ccc

This seems to be for the Pythonista console window, which is good knowledge to have, but it doesn't seem that it applies to the 'console' windows that StaSH is using (which is a iOS window control of some sort?).

Thanks for the good tip though. It will be useful for some other things I was thinking about!

ccc

If it is not in globals() then is it in locals()?

MikeBeard

@ccc
I printed out both globals() and locals() and do not see it in either, so maybe I'm doing something wrong/unexpected?

Also, when I run the script IN StaSH versus in Pythonista, I get slightly different results (globals seems to have a LOT more when run in StaSH), but locals is pretty slim in both.

Thanks for the help!

JonB

_stash is available right after running launch_stash, (in the 2.7 console, not 3.0)
It gets cleared when globals are cleared, so when running a script from the play button it will not be available without extra work.. I thought we used to protect that variable, but i guess not. But, You can find it again (in the 2.7 interpreter) as follows:

_stash=[o for o in gc.get_objects() if 'StaSh' in str(type(o))][0]

For scripts run from within stash, the variable is there automatically, which is why you see ls just use it.

myfolder/myscript.py:
   print(_stash)

....
``` from within stash:
cd myfolder
myscript


if you run you script using the `python` command

python myscript arg1

there seems to be a bug in bin/python.py.  It populates _stash for non-argument scripts, or scripts run as modules, but does not if the script has arguments, or using -c.  runpy takes init_globals, so we should be populating that.

normal commands like you'd find in bin, or I think in the current path, are run through exec_py_file inside shruntime.py, which populates the namespace (globals/locals).

Finally, stash provides the following ways to format strings (in stash.py)
def text_color(self, s, color_name='default', **kwargs):
    return self.text_style(s, {'color': color_name}, **kwargs)

def text_bgcolor(self, s, color_name='default', **kwargs):
    return self.text_style(s, {'bgcolor': color_name}, **kwargs)

def text_bold(self, s, **kwargs):
    return self.text_style(s, {'traits': ['bold']}, **kwargs)

def text_italic(self, s, **kwargs):
    return self.text_style(s, {'traits': ['italic']}, **kwargs)

def text_bold_italic(self, s, **kwargs):
    return self.text_style(s, {'traits': ['bold', 'italic']}, **kwargs)

def text_underline(self, s, **kwargs):
    return self.text_style(s, {'traits': ['underline']}, **kwargs)

def text_strikethrough(self, s, **kwargs):
    return self.text_style(s, {'traits': ['strikethrough']}, **kwargs)

```
Combinations can also be made with text_style, and most ansi strings from other scripts should just work.

MikeBeard

@JonB

Thanks JonB. I'll have to give it a look later, but that would explain some things. I do pass in arguments, so ...

Also, thanks for the explanations. I'll play around with it and might have some questions later! 😃

Thanks again!
Mike

MikeBeard

@JonB
Maybe I'm missing something here. I tried to just use _stash, and I am told that it is undefined. When I try to get _stash from the go.get_objects(), I'm told that it can't find the global variable gc.

Am I supposed to be importing something to get access to these?

To be clear, this is the code that I'm running -- in a very simple script where I do the following:

        print "globals variable is"
        print globals()
        print "locals are"
        print locals()
        print "_stash variable is"
        #print _stash

        _stash=[o for o in gc.get_objects() if 'StaSh' in str(type(o))][0]
        print "get from gc -- _stash is\n", _stash

        _stash = globals()['_stash']

I'm running this from the StaSH console.

Anyway, any insights would be gladly accepted.

Thanks,
Mike

JonB

How are you running the command from within stash?

i.e

[~/Documents]  myscript

or

[~/Documents]  python myscript

?

JonB

gc is the garbage collector module. import gc. That method would be if you have an external script (that you run from the play menu) and want to interact with a running stash instance. But if you want to run from within stash, _stash is already there for you.

[~/Documents]$ version
StaSh v0.6.18
Pythonista 3.1.1 (311004)
iOS 8.4.1 (32-bit iPad3,3)
root: ~/Documents/site-packages/stash
stash.py: 2017-07-17 07:49:40
SELFUPDATE_BRANCH: master
BIN_PATH:
  ~/Documents/bin
  ~/Documents/stash_extensions/bin
  ~/Documents/site-packages/stash/bin

[~/Documents]$ cat myscript.py
print "_stash variable is"
print _stash
[~/Documents]$ myscript
_stash variable is
<stash.stash.StaSh object at 0x726a4cc>
[~/Documents]$ myscript arg
_stash variable is
<stash.stash.StaSh object at 0x726a4cc>
[~/Documents]$ myscript.py
_stash variable is
<stash.stash.StaSh object at 0x726a4cc>
[~/Documents]$ python myscript.py
_stash variable is
Error: name '_stash' is not defined
[~/Documents]$ python myscript.py arg
_stash variable is
Error: name '_stash' is not defined
[~/Documents]$ 
MikeBeard

@JonB
Hi JonB! I'm using the 'python script'. It never occurred to me that I could just run the script, though it probably should.

I'll try that in a bit when I get a chance later this morning.

What would the difference be between the two?

Thanks!

MikeBeard

@JonB

I hadn't looked as closely to your other reply before sending my first reply. Sigh. You answered my questions there. Sorry for the noise.

Thank you for showing the difference between the two calls. Good to know.

I'm still curious as to the why? If you don't mind explaining!

Thanks so much!
Mike

MikeBeard

@JonB

That seems to work now. At least I can get the _stash variable and it's defined.

Now, on to actually getting it to do what I'd like!

Thanks for the help!
Mike

JonB

I'd say the difference is mainly a "bug" in bin/python.py, in that it does not populate the namespace. You can compare the code by looking at system/shruntime.py, and bin/python.py.

MikeBeard

@JonB

Thanks!

MikeBeard

So, another question on this -- it seems that there is something not quite right.

Looking at the documentation, it states:

When setting this attribute, you can pass a string (CSS color name or hex, e.g. 'red' or '#ff0000'), a tuple (e.g. (1.0, 0.0, 0.0, 0.5) for half-transparent red), or a number (e.g. 0.5 for 50% gray).

See the code below:

print "text_color test"
    print _stash.text_color("red text - using color 'red'", 'red')
    print _stash.text_color("red text - using #ff0000", "#ff0000")
    print _stash.text_color("red text - using (1.0, 0.0, 0.0, 1.0)")
    print _stash.text_color("grey text - using 0.75")
    print "\n\n"

    ''' output is:

        text_color test
        red text - using color 'red' -- (this is actually red in output!)
        red text - using #ff0000
        red text - using (1.0, 0.0, 0.0, 1.0)
        grey text - using 0.75
    '''

As noted with a comment, the only text that actually prints out red is when I'm using the 'red' color to text_color. Also, the last line (just a number -- 0.75) doesn't do anything except output standard white text.

Is this expected? Should I be able to pass in the other ways to print red of a shade?

Interestingly, the editor will correctly interpret the color values correctly by bringing up a 'popup' of the correct color. So, needless to say, I'm a little confused.

As an aside, does anyone know what color values are actually valid?

I have looked at the code, but haven't been able to find where the list of colors are (if there is such a thing). I've tried a bunch but only found these to actually give me colored output:

  • red
  • yellow
  • green
  • blue
  • black
  • grey
  • brown
  • cyan
  • magenta

Thanks for any help or direction on this!
Mike

ccc

You might look at how latte does colored text in StaSH. Perhaps you need to turn the color off before applying a new color.

JonB

I think you might be confusing pythonista docs with stash docs?

Anyway, in stash, stash.text_color calls stash.style_text, which looks up colors via graphics, which is an imported alias of shcommon.Graphics. If you open up shcommon.py, you will see the list of colors

        30: "black",
        31: "red",
        32: "green",
        33: "brown",
        34: "blue",
        35: "magenta",
        36: "cyan",
        37: "white",
        39: "default",  # white.
        50: "gray",
        51: "yellow",
        52: "smoke",

These are the standard ANSI color code definitions -- stash supports a reasonable approximation of an ANSI terminal (via pyte).

The stash terminal actually has another text_color, which takes a tuple... but you would need to go a little lower level. For basic work, stick with these.

MikeBeard

@ccc

Thanks ccc! I'll give that a look. I was using the terminal escape codes for a bash script, but they don't work in StaSh. A different way sounds fun!

Thanks again!

MikeBeard

@JonB

Thanks JonB! I certainly got those from the Pythonista docs. Was under the impression that was how StaSh was doing it. I didn't go down the code chain as far as I should have.

Thanks for the color references. That will work for now. I really didn't relish working through the couple hundred different color names I found on the web for CSS colors. You saved me some work! Thanks!

Good to know that there is another way to set text with a tuple. I might give that a look. With what you gave me though, probably won't need to do anything with that for awhile though!

Thanks again for the help!
Mike

AtomBombed

Or you could just use base escape codes. That's what I use since they're cleaner and more dynamic:

class stashansi:
    fore_red = "\x9b31m"
    fore_blue = "\x9b34m"
    fore_end = "\x9b39m"
    back_red = "\x9b41m"
    back_blue = "\x9b44m"
    back_end = "\x9b49m"
    bold = "\x9b1m"
    underline = "\x9b4m"
    all_end = "\9x0m"

print(stashansi.back_blue + stashansi.fore_red + "Hello world" + back_end + "This is just red with no blue background" + fore_end + "Now it's all just normal text...")

Be aware that whenever you print something with ANSI escape codes, you MUST use the ender escape codes to change the colors back to the original state, or else if your program ends, the colors will continue to be the same even outside of your program!

MikeBeard

@AtomBombed

Thanks AtomBombed! That is very similar to what I was using with standard sh/bash escape codes.

I might update my code to use this, but it's working well right now, so ...

It's good to have the escape codes in one place though!

Thanks again!
Mike

MikeBeard

@AtomBombed

I forgot and didn't mention -- I went the other way! I had this working on Mac terminal with colors and wanted it to work in StaSh. Now, it selects things dynamically and should work on either without any changes!

Thanks again!
Mike

Darren

Hi Mike,

You mentioned you went the other way - just wondering what this means, can you share an example that works interchangeably with StaSh and macOS terminal?

I am trying to accomplish a similar goal and any pointers would be greatly appreciated :-)

Thanks,
Darren

MikeBeard

@Darren

Hi Darren!

I made the classes (shown below) to handle the color. I had the TerminalColors class already written, so didn't want to change the interface to handle both cases, so I created the getTerminalColorClass() function to key off of the existence (or not) of the _stash variable that can give access to the text_xxx functions.

There is certainly some refactoring that could be done -- just haven't gotten to it yet. If I used the ANSI codes, as suggested, then it's likely all I would need to change are the actual definitions of the colors. I'd probably use some sort of enum that is defined differently like the getTerminalColorClass() function.

def getTerminalColorClass():
    if _stash != None:
        return StashTerminalColors()

    return TerminalColors()


class TerminalColors(object):
    """
    here is a good url for the different colors and things:
        http://blog.taylormcgann.com/tag/prompt-color/
    """
    HEADER = '\033[95m'
    BLUE = '\033[94m'
    DARK_BLUE = '\033[34m'
    GREEN = '\033[92m'
    DARK_GREEN = '\033[32m'
    YELLOW = '\033[93m'
    DARK_YELLOW = '\033[33m'
    RED = '\033[91m'
    DARK_RED = '\033[31m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    CYAN = '\033[96m'
    DARK_CYAN = '\033[36m'
    WHITE = '\033[97m'
    PURPLE = '\033[95m'
    DARK_PURPLE = '\033[35m'
    END = '\033[0m'

    def __init__(self):
        pass

    def simple_print(self, color, str_msg):
        print( "%s%s" % (self.get_color_string(color, str_msg), self.END))

    def complex_print(self, color, str_format, data):
        str_msg = str_format % data
        self.simple_print( color, str_msg )

    def multi_color_print(self, color_str_tuple_list):
        str_msg = ""
        for clr, str in color_str_tuple_list:
            str_msg += self.get_color_string(clr, str)
        self.println(str_msg)

    def get_color_string(self, color, str_msg):
        return "%s%s" % (color, str_msg)

    def println(self, str_msg):
        print( "%s%s" % (str_msg, self.END) )


class StashTerminalColors (object):

    # from shcommon.py in stash code

    HEADER = 'UNDERLINE'
    BLACK = 'black'
    RED = 'red'
    GREEN = 'green'
    BROWN = 'brown'
    BLUE = 'blue'
    MAGENTA = 'magenta'
    PURPLE = 'magenta'
    CYAN = 'cyan'
    WHITE = 'white'
    GRAY = 'gray'
    YELLOW = 'yellow'
    SMOKE = 'smoke'
    DEFAULT = 'white'
    STRIKE = 'STRIKE'
    BOLD = 'BOLD'
    UNDERLINE = 'UNDERLINE'
    BOLD_ITALIC = 'BOLD_ITALIC'
    ITALIC = 'ITALIC'
    END = 'END'

    def __init__(self):
        self._stash = _stash

    def simple_print(self, color, str_msg): 
        print("%s" % self.get_color_string(color, str_msg))

    def get_color_string(self, color, str_msg):
        if color == self.BOLD:
            prn_str = _stash.text_bold(str_msg)
        elif color == self.UNDERLINE:
            prn_str = _stash.text_underline(str_msg)
        elif color == self.BOLD_ITALIC:
            prn_str = _stash.text_bold_italic(str_msg)
        elif color == self.ITALIC:
            prn_str = _stash.text_italic(str_msg)
        elif color == self.STRIKE:
            prn_str = _stash.text_strikethrough(str_msg)
        else:
            prn_str = _stash.text_color(str_msg, color)

        return prn_str

    def multi_color_print(self, color_str_tuple_list):
        str_msg = ""
        for clr, str in color_str_tuple_list:
            str_msg += self.get_color_string(clr, str)
        print(str_msg)

The class is pretty simple to use (written explicitly for my use case, so ... YMMV). I'll give examples of usage for both below.

Here are some examples of using simple_print():

tc = getTerminalColorClass()

tc.simple_print(tc.BLUE, "this should be BLUE")
print("hopefully, this is NOT blue")
tc.simple_print(tc.DEFAULT, "this is the DEFAULT color (white)")
tc.simple_print(tc.BROWN, "this is BROWN text")
tc.simple_print(tc.BOLD, "this should be BOLD")
tc.simple_print(tc.UNDERLINE, "this should be UNDERLINE")
tc.simple_print(tc.STRIKE, "this should be STRIKETHROUGH")
tc.simple_print(tc.ITALIC, "this should be ITALIC")
tc.simple_print(tc.BOLD_ITALIC, "this should be BOLD ITALIC")


# will combine multiple effects
msg = '\n  ID  Status   Date(-t)           Owner(-u)   Description (-d)\n'
tc.simple_print(self.tc.BOLD + self.tc.UNDERLINE, msg)

Here is some example for multi_color_print():

def print_status_footer():
    tc = getTerminalColorClass()

    # statusLine = "Status: [+]add [@]block [-]reject [*]accept [#]workon [.]finish"
    prn_list = [
        (tc.WHITE,      "Status: "),
        (get_color_for_status('+'), "[+]add "),
        (get_color_for_status('@'), "[@]block "),
        (get_color_for_status('-'), "[-]reject "),
        (get_color_for_status('*'), "[*]accept "),
        (get_color_for_status('#'), "[#]workon "),
        (get_color_for_status('.'), "[.]finish ")
    ]
    tc.multi_color_print(prn_list)
    tc.simple_print(tc.WHITE, "        ST=status  PR=priority")

The commented out line shows what it was before I added color to it. The get_color_for_status() just returns one of the colors defined in the terminal color class.

I hope that this helps!
Mike

MikeBeard

@Darren

Was finally able to test on my script on bash, so a small addition (actually just replace the getTerminalColorClass() function with the following:

def getTerminalColorClass():
    if '_stash' in globals() or '_stash' in locals():
        if _stash != None:
            return StashTerminalColors()

    return TerminalColors()

How I was checking before was never going to work! ;-)

Darren

@MikeBeard

Hi Mike,

I appreciate you taking the time to post your code, this is indeed very helpful.

Thanks!
Darren

MikeBeard

@Darren

You're welcome! Glad to help.