Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color #213

Merged
merged 90 commits into from
Aug 27, 2015
Merged

Color #213

merged 90 commits into from
Aug 27, 2015

Conversation

henryiii
Copy link
Collaborator

This is the plumbum.colors module I've been working on, designed to make adding colors easy and safe. I've prepared a post about it here, this post has been updated, and there is extensive documentation in the addition. As a quick example:

from plumbum import colors
print("This is red" << colors.red + "This is not red")
with (colors.blue & colors.bold):
    print("Styles can be combined, and are context managers.")

You can access the ANSI 16 basic colors, 256 colors (with names), and full 24 bit color - and can convert between them (This is the only library I know of that can do that). You can easily subclass Style to create more representations (HTMLStyle is included to make documentation easier to write). Styles are smart and know how to combine to create the most effective ANSI sequence. Styles correctly handle being turned off, and don't turn on for non-posix tty systems (can be forced, though). Basic colors can be used on Windows with a tool (like colorama) to convert ANSI codes. (See examples).

Docs (with list of color names), API docs, tests, examples included.

Closes #128.

Replacement for #206, as this is now a branch in the main repo to make it easier to test and github doesn't allow this to be changed after the fact. I'll close that one in favor of this one.

Henry Schreiner (Muon Linux) and others added 30 commits July 13, 2015 17:00
…thods, full property name support, properties now dynamic, some support for setting .use_color
Conflicts:
	docs/_news.rst
	docs/local_commands.rst
Conflicts:
	.travis.yml
	CHANGELOG.rst
	plumbum/machines/base.py
	tests/test_local.py
henryiii and others added 21 commits July 23, 2015 07:33
Conflicts:
	docs/_cheatsheet.rst
@henryiii henryiii added this to the v1.6.0 milestone Aug 27, 2015
tomerfiliba added a commit that referenced this pull request Aug 27, 2015
@tomerfiliba tomerfiliba merged commit 3e73ae1 into master Aug 27, 2015
@henryiii henryiii deleted the color branch August 27, 2015 20:26
@tomerfiliba
Copy link
Owner

hey, i merged it in so it would stop "rotting" on side branches, but i'd love if you could cleanup (or explain the reason for) the following:

  • why is there need for a module hack? why not from plumbum.color import fg, bg and then fg.blue | bg.red, where fg and bg are perfectly normal modules?
  • why is there more than "one obvious way to do it (TM)"? e.g., why + and - and [] and `<<`` and context managers and "stick shift" all in one? i find it very confusing and too error prone.
  • subtracting styles is a bad practice, i.e., HTML vs XHTML. while supported by HTML4, <i><b>foo</i>bar</b> was deprecated and went away in HTML5. you don't change style that often in order to be that efficient.

here's a snippet from your example:

with colors:
    print('It is always a good idea to be in a context manager, to avoid being',
          'left with a colorsed terminal if there is an exception!')
    print(colors.bold + "This is bold and exciting!" - colors.bold)
    print(colors.bg.cyan + "This is on a cyan background." + colors.reset)
    print(colors.fg[42] + "If your terminal supports 256 colorss, this is colorsful!" + colors.reset)
    print()
    for c in colors:
        print(c + u'\u2588', end='')
    colors.reset()
    print()
    print('Colors can be reset ' + colors.underline['Too!'])
    for c in colors[:16]:
       print(c["This is in colors!"])
  • i find it awkward that colors is an iterable (i.e., except for the example using it here, where would anyone iterate through all possible colors? or the first 16 colors? what does that even mean? is there a first color? ANSI color numbers are just arbitrary)
  • also, i find it strange that the module is a context manager on its own. i understand the need for cleanup on exceptions, but that's what atexit is for. when you first import the module, it could register an atexit callback that just resets color. simpler, cleaner and doesn't require the user to be not forget doing it.
  • hex colors are nice, but would rarely ever be used. i'd just have fd.rgb and bg.rgb functions that take either (r,g,b) or "#rrggbb" and return the appropriate color objects.
  • also, naming so many colors seems superfluous (e.g., who would ever use deep_pink_4?) but since you already did the hard work i'm fine with that.

here's a simplified, cleaner version that i have in mind:

from plumbum.colors import fg, bg, bold, underline
# or ``from plumbum.colors.fg import red`` if that's all you want

with bg.blue:
    print("hello ", fg.red["world"], " bingo was his ", underline["name-o"])
    yell = fg.red | underline | bold
    print(yell["SHAKIRA SHKIRA"])
    print("and ", fg.red["it can ", bold["be nested ", underline["just as well"]]])

thanks for your hard work, man, i really appreciate it. but here i think you should let usability guide you (i.e., "the lean startup" approach) and not "think in advance" what would be cool (to be able) to do.

on the other hand, i think the library is missing some built-in semantic combinators such as warn, title, fatal, highlight. that's what your end users would most probably want in the end of the day, i.e., a "CSS layer" on top of the low level. looking at the evolution of HTML is really helpful here.

@henryiii
Copy link
Collaborator Author

  • The module hack greatly simplifies code; you'll notice that there is really only one color manager, and colors, fg, and bg are all instances of that. And, that way it's easy to generate all the colors instead of having them all listed in the .py files. You should not be able to detect that the module 'hack' is a hack from using it, and it's about 5 lines. Plus, this way, you can specialize to different color backends (like html for documentation). And, this way you can use from plumbum.colors import fg; fg.red or from plumbum import colors; colors.fg.red. But I'll try to remove it if you still want me to, though it will require a lot of extra code. Of all the changes, this is the hardest for me...
  • That's because I didn't know which would be best when I was writing it, so I put them all in. Once we decide on a 'best way', I will remove the other ways - they were not supposed to all be there in the end. I am starting to cleaning some of them out now. That should answer several of your points, actually, like subtracting colors.
  • Iterating colors is really handy if you want to cycle through colors (I do it in my git-all program, for example). And you'll notice the ColorFactory does it to generate names (and the color order is not arbitrary, look in names.py, you'll see that each block is special, and is ordered in a certain way, that's why colors is now so fast looking up colors from rgb). PS: Since you can access the colors through [] notation, it makes sense to be able to slice and iterate, imo.
  • I didn't know about atexit. I've added it. Thanks! I like having a color safe general context manager available too, to make it easy to make something internally color safe (this was originally a function until the last few commits), and making colors a context manager too seemed like a nice way to save having to remember and import another name.

I tried using my library with git-all and a few demo scripts, that's where some of the ideas came from.

Should we combine colors with | or &? You've used both in previous comments.

The names are almost the "official" names, just with some extra text added to make each unique.

To me, print("and ", fg.red["it can ", bold["be nested ", underline["just as well"]]]) is very ugly. It's very hard to parse visually, and I don't see what combining strings with commas inside the square brakets is supposed to do. Should it add a space like print? The verbose print("and " + fg.red["it can " + bold["be nested " + underline["just as well"]]]) is better, IMO. I'm thinking using () might be better than []. If we used () notation, print("and " + fg.red("it can " + bold("be nested " + underline("just as well")))).

I'll be adding the warn, etc, and looking at moving hex into rgb, and working on tightening it up.

@henryiii
Copy link
Collaborator Author

By the way, thanks for taking the time to review it and add it! And let me know if you still want changes, I'll be working on it off and on for the next week or so.

@henryiii
Copy link
Collaborator Author

I'm standardising on & to combine colors. | is used to wrap colors, if you want a single operator to do so. + is still available in it's old form (it treats colors like strings). The other operators, like *, <<, >>, -, have been removed. I'm also working on docs, and other cleanup.

rgb now works as mentioned, and I'm thinking about a way to add the warn, etc.

This means that you can do the following:

from plumbum.colors import fg, bg, bold, underline
with bg.blue:
    print("hello ", fg.red | "world", " bingo was his ", underline | "name-o")
    yell = fg.red & underline & bold
    print(yell["SHAKIRA SHKIRA"])
    print( fg.red & underline & bold | "SHAKIRA SHKIRA")
    print("and ", fg.red[ "it can " + bold["be nested " + underline["just as well"]]])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add terminal colors (posix/windows) to cli.terminal
2 participants