From 96a288c86e32802bd35f8f631c70f1cbc01776ef Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 13 Jul 2015 17:00:58 -0500 Subject: [PATCH 01/80] Adding new color module --- .gitignore | 1 + CHANGELOG.rst | 1 + docs/cli.rst | 25 +++++ examples/color.py | 19 ++++ plumbum/__init__.py | 1 + plumbum/cli/__init__.py | 1 + plumbum/cli/application.py | 9 +- plumbum/cli/color.py | 182 +++++++++++++++++++++++++++++++++++++ tests/test_color.py | 53 +++++++++++ 9 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 examples/color.py create mode 100644 plumbum/cli/color.py create mode 100644 tests/test_color.py diff --git a/.gitignore b/.gitignore index 5e7f39d2d..795fb8d12 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ __pycache__ /plumbum.egg-info /build *.class +*.ropeproject diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 66a15b44b..6f3cfe85d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,6 @@ 1.4.3 ----- +* CLI: add ``COLOR``, which allows simple access to ANSI color sequences * CLI: add ``invoke``, which allows you to programmatically run applications (`#149 `_) * CLI: add ``--help-all`` and various cosmetic fixes: (`#125 `_), (`#126 `_), (`#127 `_) diff --git a/docs/cli.rst b/docs/cli.rst index c34de2734..3f1eda651 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -444,6 +444,31 @@ Here's an example of running this application:: committing... +Color tools +----------- +.. versionadded:: 1.4.3 + +A built in color module provides quick, clean access to ANSI colors for your scripts. They are +accessed through the ``COLOR`` object. For example:: + + from plumbum import COLOR + with COLOR.FG.RED: + print('This is in red') + COLOR.FG.GREEN() + print('This is green') + print('This is completly restored, even if an exception is thrown!') + print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again') + with COLOR: + print('It is always a good idea to be in a context manager, to avoid being', + 'left with a colored terminal if there is an exception!') + COLOR.FG.RED() + print(COLOR.BOLD + "This is red, bold, and exciting!" - COLOR.BOLD + " And this is red only.") + print(COLOR.BG.CYAN + "This is red on a cyan background." + COLOR.RESET) + print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) + print('Colors made ' + COLOR.UNDERLINE + 'easy!') + +The name of a script is automatically colored with the `.COLOR_NAME` property. Other properties may be added. + See Also -------- * `filecopy.py `_ example diff --git a/examples/color.py b/examples/color.py new file mode 100644 index 000000000..566291e7e --- /dev/null +++ b/examples/color.py @@ -0,0 +1,19 @@ +from __future__ import with_statement, print_function + +from plumbum import COLOR + +with COLOR.FG.RED: + print('This is in red') +print('This is completly restored, even if an exception is thrown!') +with COLOR: + print('It is always a good idea to be in a context manager, to avoid being', + 'left with a colored terminal if there is an exception!') + print(COLOR.BOLD + "This is bold and exciting!" - COLOR.BOLD) + print(COLOR.BG.CYAN + "This is on a cyan background." + COLOR.RESET) + print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) + print() + for color in COLOR.BG: + print(color + ' ', end='') + COLOR.RESET() + print() + print('Colors can be reset ' + COLOR.UNDERLINE + 'Too!') diff --git a/plumbum/__init__.py b/plumbum/__init__.py index 129b39d96..545698932 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -39,6 +39,7 @@ from plumbum.path import Path, LocalPath, RemotePath from plumbum.machines import local, BaseRemoteMachine, SshMachine, PuttyMachine from plumbum.version import version +from plumbum.cli import COLOR __author__ = "Tomer Filiba (tomerfiliba@gmail.com)" __version__ = version diff --git a/plumbum/cli/__init__.py b/plumbum/cli/__init__.py index 8ac2b4bb5..18c04bd13 100644 --- a/plumbum/cli/__init__.py +++ b/plumbum/cli/__init__.py @@ -1,3 +1,4 @@ from plumbum.cli.switches import SwitchError, switch, autoswitch, SwitchAttr, Flag, CountOf from plumbum.cli.switches import Range, Set, ExistingDirectory, ExistingFile, NonexistentPath, Predicate from plumbum.cli.application import Application +from plumbum.cli.color import COLOR, with_color diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 4e9d4d9df..280403c3b 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -8,6 +8,7 @@ from plumbum.cli.switches import (SwitchError, UnknownSwitch, MissingArgument, WrongArgumentType, MissingMandatorySwitch, SwitchCombinationError, PositionalArgumentsError, switch, SubcommandError, Flag, CountOf) +from plumbum.cli.color import COLOR class ShowHelp(SwitchError): @@ -100,6 +101,7 @@ def main(self, src, dst): DESCRIPTION = None VERSION = None USAGE = None + NAME_COLOR = COLOR.FG.CYAN CALL_MAIN_IF_NESTED_COMMAND = True parent = None @@ -575,8 +577,5 @@ def _get_prog_version(self): def version(self): """Prints the program's version and quits""" ver = self._get_prog_version() - if sys.stdout.isatty() and os.name == "posix": - fmt = "\033[0;36m%s\033[0m %s" - else: - fmt = "%s %s" - print (fmt % (self.PROGNAME, ver if ver is not None else "(version not set)")) + program_name = self.NAME_COLOR + self.PROGNAME + COLOR.RESET + print (self.PROGNAME, ver if ver is not None else "(version not set)") diff --git a/plumbum/cli/color.py b/plumbum/cli/color.py new file mode 100644 index 000000000..7d5eda1eb --- /dev/null +++ b/plumbum/cli/color.py @@ -0,0 +1,182 @@ +""" +Color-related utilities. Feel free to use any color library on +the ``with_color`` statement. + +The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, +and ``ATTRIBUTE`` and ``RESTORE`` to access attributes like bold and +underlined text. It also provides ``RESET`` to recover the normal font. + +With the ``color_str`` string subclass being used, any color can be +directly called or given to a with statement. +""" + +from __future__ import with_statement +import sys +import os +from contextlib import contextmanager + +# This can be manually forced using plumbum.cli.color.USE_COLOR = True +USE_COLOR = sys.stdout.isatty() and os.name == "posix" + + +class color_str(str): + + """This class allows the color change strings to be called directly + to write them to stdout, and can be called in a with statement.""" + + def __call__(self, wrap_this=None): + if wrap_this is None: + sys.stdout.write(self) + return self + else: + if USE_COLOR: + return self + wrap_this - self + else: + return wrap_this + + def __neg__(self): + """This negates the effect of the current color""" + return self.__class__(self._remove() if USE_COLOR else '') + + def __rsub__(self, other): + """Implemented to make using negatives easier""" + return other + (-self) + + def _remove(self): + """Don't use directly. Will find best match for negative of current color.""" + try: + if self[:2] == '\033[' and self[-1] == 'm': + v = self[2:-1].split(';') + n = list(map(int, v)) + if len(n) == 1 or (len(n) == 3 and n[1] == 5 and n[0] in (38, 48)): + if 30 <= n[0] <= 39: + return '\033[39m' + elif 40 <= n[0] <= 49: + return '\033[49m' + elif n[0] in (1, 2, 4, 5, 7, 8): + return '\033['+str(n[0]+20)+'m' + elif n[0] in (21, 22, 24, 25, 27, 28): + return '\033['+str(n[0]-20)+'m' + except ValueError: + pass + return '\033[0m' + + def __enter__(self): + if USE_COLOR: + sys.stdout.write(self) + return self + + def __exit__(self, type, value, traceback): + if USE_COLOR: + sys.stdout.write(-self) + return False + + +def ansi_color(n): + """Return an ANSI escape sequence given a number.""" + return color_str('\033[' + str(n) + 'm') if USE_COLOR else color_str('') + + +def extended_ansi_color(n, begin=38): + """Return an ANSI extended color given number.""" + return color_str('\033[' + str(begin) + ';5;' + str(n) + 'm') if USE_COLOR else color_str('') + + +class _COLOR_NAMES(object): + + """This creates color names given a modifier value (FG, BG)""" + + def __init__(self, val): + self.val = val + self.BLACK = ansi_color(0+self.val) + self.RED = ansi_color(1+self.val) + self.GREEN = ansi_color(2+self.val) + self.YELLOW = ansi_color(3+self.val) + self.BLUE = ansi_color(4+self.val) + self.MAGENTA = ansi_color(5+self.val) + self.CYAN = ansi_color(6+self.val) + self.WHITE = ansi_color(7+self.val) + self.RESET = ansi_color(9+self.val) + + def regular(self, val): + """Access colors by value.""" + return ansi_color(val + self.val) + + def extended(self, val): + """Return the extended color scheme color for a value.""" + return extended_ansi_color(val, self.val + 8) + + def __call__(self, val): + """Shortcut to provide way to access colors by number""" + return self.regular(val) + + def __getitem__(self, val): + """Shortcut to provide way to access extended colors by number""" + return self.extended(val) + + def __iter__(self): + """Iterates through all colors in extended colorset.""" + return (self.extended(i) for i in range(256)) + + def __neg__(self): + """Allows clearing a color""" + return self.RESET + + def __rsub__(self, other): + return other + (-self) + + +class COLOR(object): + + def __init__(self): + self.BOLD = ansi_color(1) + self.DIM = ansi_color(2) + self.UNDERLINE = ansi_color(4) + self.BLINK = ansi_color(5) + self.REVERSE = ansi_color(7) + self.HIDDEN = ansi_color(8) + + self.FG = _COLOR_NAMES(30) + self.BG = _COLOR_NAMES(40) + + self.BLACK = self.FG.BLACK + self.RED = self.FG.RED + self.GREEN = self.FG.GREEN + self.YELLOW = self.FG.YELLOW + self.BLUE = self.FG.BLUE + self.MAGENTA = self.FG.MAGENTA + self.CYAN = self.FG.CYAN + self.WHITE = self.FG.WHITE + + self.RESET = ansi_color(0) + + def __call__(self, color): + return color_str(color) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + sys.stdout.write(ansi_color(0)) + return False + + +COLOR = COLOR() + + +@contextmanager +def with_color(color='', out=None): + """Sets the color to a given color or style, + resets when done, + even if an exception is thrown. Optional ``out`` to give a + different output channel (defaults to sys.stdout).""" + + if out is None: + out = sys.stdout + out.write(str(color)) + try: + yield + finally: + out.write(ansi_color(0)) + +__all__ = ['COLOR', 'with_color', 'ansi_color', 'extended_ansi_color', 'color_str', 'USE_COLOR'] diff --git a/tests/test_color.py b/tests/test_color.py new file mode 100644 index 000000000..20cc55406 --- /dev/null +++ b/tests/test_color.py @@ -0,0 +1,53 @@ +from __future__ import with_statement, print_function +import unittest +from plumbum.cli import COLOR, with_color + + +class TestColor(unittest.TestCase): + + def testColorStrings(self): + self.assertEqual('\033[0m', COLOR.RESET) + self.assertEqual('\033[1m', COLOR.BOLD) + + def testUndoColor(self): + self.assertEqual('\033[39m', -COLOR.FG) + self.assertEqual('\033[39m', ''-COLOR.FG) + self.assertEqual('\033[49m', -COLOR.BG) + self.assertEqual('\033[49m', ''-COLOR.BG) + self.assertEqual('\033[21m', -COLOR.BOLD) + self.assertEqual('\033[22m', -COLOR.DIM) + for i in (1, 2, 4, 5, 7, 8): + self.assertEqual('\033[%im' % i, -COLOR('\033[%im' % (20 + i))) + self.assertEqual('\033[%im' % (i + 20), -COLOR('\033[%im' % i)) + for i in range(10): + self.assertEqual('\033[39m', -COLOR('\033[%im' % (30 + i))) + self.assertEqual('\033[49m', -COLOR('\033[%im' % (40 + i))) + self.assertEqual('\033[39m', -COLOR.FG(i)) + self.assertEqual('\033[49m', -COLOR.BG(i)) + for i in range(256): + self.assertEqual('\033[39m', -COLOR.FG[i]) + self.assertEqual('\033[49m', -COLOR.BG[i]) + self.assertEqual('\033[0m', -COLOR.RESET) + self.assertEqual('\033[0m', -COLOR('this is random')) + + def testVisualColors(self): + print() + for c in (COLOR.FG(x) for x in range(1, 6)): + with with_color(c): + print('Cycle color test', end=' ') + print(' - > back to normal') + with with_color(): + print(COLOR.FG.GREEN + "Green " + + COLOR.BOLD + "Bold " + - COLOR.BOLD + "Normal") + print("Reset all") + + def testToggleColors(self): + print() + print(COLOR.FG.RED("this is in red"), "but this is not") + print(COLOR.FG.GREEN + "Hi, " + COLOR.BG[23] + + "This is on a BG" - COLOR.BG + " and this is not") + COLOR.RESET() + +if __name__ == '__main__': + unittest.main() From a687cf17aed32825a29b175ffe7049705ed9e70e Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Mon, 13 Jul 2015 21:25:23 -0500 Subject: [PATCH 02/80] Adding docs and cleanup for COLOR (and Style) classes --- docs/api/cli.rst | 3 +++ docs/cli.rst | 37 +++++++++++++++++++++++--- plumbum/cli/application.py | 2 +- plumbum/cli/color.py | 54 +++++++++++++++++++++++++------------- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/docs/api/cli.rst b/docs/api/cli.rst index b5e971c41..8eea774ba 100644 --- a/docs/api/cli.rst +++ b/docs/api/cli.rst @@ -8,3 +8,6 @@ Package plumbum.cli .. automodule:: plumbum.cli.terminal :members: + +.. automodule:: plumbum.cli.color + :members: diff --git a/docs/cli.rst b/docs/cli.rst index 2df5743b1..63d740f87 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -449,7 +449,25 @@ Color tools .. versionadded:: 1.4.3 A built in color module provides quick, clean access to ANSI colors for your scripts. They are -accessed through the ``COLOR`` object. For example:: +accessed through the ``COLOR`` object, which is a collection of ``Style`` objects. The ``COLOR`` object has the following properties: + + ``FG`` and ``BG`` + The forground and background colors, reset to default with ``COLOR.FG.RESET`` or ``-COLOR.FG`` and likewise for ``BG``. (Named forground colors are available directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, ``BLUE``, ``MAGENTA``, + ``CYAN``, ``WHITE``, as well as ``RESET``, are available. + You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. + ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` + All the `ASNI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. + ``RESET`` + The global reset will restore all properties at once. + ``DO_NOTHING`` + Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. + +A color can be used directly as if it was a string, +or called for immediate printing to stdout. Calling a +color with an argument will wrap the string in the color and the matching negation. +Any color can be used as the target of a with statement, and normal color will be restored on exiting the with statement, even with an Exception. + +An example of the usage of ``COLOR``:: from plumbum import COLOR with COLOR.FG.RED: @@ -457,17 +475,28 @@ accessed through the ``COLOR`` object. For example:: COLOR.FG.GREEN() print('This is green') print('This is completly restored, even if an exception is thrown!') + +We could have used the shortcut ``COLOR.GREEN()`` instead. You can also use COLOR directly as a context manager if you only want the restoring ability, and if you call ``COLOR(...)``, you can manually pass in any ANSI escape sequence. + +Further examples of manipulations possible with the library:: + print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again') with COLOR: print('It is always a good idea to be in a context manager, to avoid being', 'left with a colored terminal if there is an exception!') COLOR.FG.RED() - print(COLOR.BOLD + "This is red, bold, and exciting!" - COLOR.BOLD + " And this is red only.") + print(COLOR.BOLD("This is red, bold, and exciting!"), "And this is red only.") print(COLOR.BG.CYAN + "This is red on a cyan background." + COLOR.RESET) print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) - print('Colors made ' + COLOR.UNDERLINE + 'easy!') + COLOR.YELLOW() + print('Colors made', COLOR.UNDERLINE + 'very' - COLOR.UNDERLINE, 'easy!') -The name of a script is automatically colored with the `.COLOR_NAME` property. Other properties may be added. +The name of a script is automatically colored with the ``.COLOR_NAME`` property. Other style properties may be added; they will be set to ``COLOR.DO_NOTHING``, but can be overriden by your class. Any callable can be used to provide formats. + +.. note:: + The color library looks for a tty terminal and posix operating system. If you want to manually + enable or disable color, you can set ``plumbum.cli.color.USE_COLOR`` to True or False explicitly. + The same is true for ``plumbum.cli.color.STDOUT=sys.stdout``. See Also -------- diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 280403c3b..d0f3cb39e 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -577,5 +577,5 @@ def _get_prog_version(self): def version(self): """Prints the program's version and quits""" ver = self._get_prog_version() - program_name = self.NAME_COLOR + self.PROGNAME + COLOR.RESET + program_name = self.NAME_COLOR(self.PROGNAME) print (self.PROGNAME, ver if ver is not None else "(version not set)") diff --git a/plumbum/cli/color.py b/plumbum/cli/color.py index 7d5eda1eb..4be62ca14 100644 --- a/plumbum/cli/color.py +++ b/plumbum/cli/color.py @@ -3,40 +3,54 @@ the ``with_color`` statement. The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, -and ``ATTRIBUTE`` and ``RESTORE`` to access attributes like bold and +and attributes like bold and underlined text. It also provides ``RESET`` to recover the normal font. -With the ``color_str`` string subclass being used, any color can be +With the ``Style`` string subclass being used, any color can be directly called or given to a with statement. """ -from __future__ import with_statement +from __future__ import with_statement, print_function import sys import os from contextlib import contextmanager # This can be manually forced using plumbum.cli.color.USE_COLOR = True USE_COLOR = sys.stdout.isatty() and os.name == "posix" +STDOUT = sys.stdout -class color_str(str): - +class Style(str): """This class allows the color change strings to be called directly to write them to stdout, and can be called in a with statement.""" - def __call__(self, wrap_this=None): + + def negate(self): + """This negates the effect of the current color""" + return self.__class__(self._remove() if USE_COLOR else '') + + def now(self, wrap_this=None): + STDOUT.write(self.wrap(wrap_this)) + + def wrap(self, wrap_this=None): if wrap_this is None: - sys.stdout.write(self) - return self + return self if USE_COLOR else self.__class__('') else: if USE_COLOR: return self + wrap_this - self else: return wrap_this + def __call__(self, wrap_this=None): + """This sets the color (no arguments) or wraps a str (1 arg)""" + if wrap_this is None: + self.now() + else: + return self.wrap(wrap_this) + def __neg__(self): """This negates the effect of the current color""" - return self.__class__(self._remove() if USE_COLOR else '') + return self.negate() def __rsub__(self, other): """Implemented to make using negatives easier""" @@ -44,6 +58,8 @@ def __rsub__(self, other): def _remove(self): """Don't use directly. Will find best match for negative of current color.""" + if self == '': + return '' try: if self[:2] == '\033[' and self[-1] == 'm': v = self[2:-1].split(';') @@ -63,23 +79,23 @@ def _remove(self): def __enter__(self): if USE_COLOR: - sys.stdout.write(self) + STDOUT.write(self) return self def __exit__(self, type, value, traceback): if USE_COLOR: - sys.stdout.write(-self) + STDOUT.write(-self) return False def ansi_color(n): """Return an ANSI escape sequence given a number.""" - return color_str('\033[' + str(n) + 'm') if USE_COLOR else color_str('') + return Style('\033[' + str(n) + 'm') if USE_COLOR else Style('') def extended_ansi_color(n, begin=38): """Return an ANSI extended color given number.""" - return color_str('\033[' + str(begin) + ';5;' + str(n) + 'm') if USE_COLOR else color_str('') + return Style('\033[' + str(begin) + ';5;' + str(n) + 'm') if USE_COLOR else Style('') class _COLOR_NAMES(object): @@ -149,15 +165,16 @@ def __init__(self): self.WHITE = self.FG.WHITE self.RESET = ansi_color(0) + self.DO_NOTHING = Style('') def __call__(self, color): - return color_str(color) + return Style(color) def __enter__(self): return self def __exit__(self, type, value, traceback): - sys.stdout.write(ansi_color(0)) + STDOUT.write(ansi_color(0)) return False @@ -169,14 +186,15 @@ def with_color(color='', out=None): """Sets the color to a given color or style, resets when done, even if an exception is thrown. Optional ``out`` to give a - different output channel (defaults to sys.stdout).""" + different output channel (defaults to STDOUT, which is set + to sys.stdout).""" if out is None: - out = sys.stdout + out = STDOUT out.write(str(color)) try: yield finally: out.write(ansi_color(0)) -__all__ = ['COLOR', 'with_color', 'ansi_color', 'extended_ansi_color', 'color_str', 'USE_COLOR'] +__all__ = ['COLOR', 'with_color', 'ansi_color', 'extended_ansi_color', 'Style', 'USE_COLOR', 'STDOUT'] From e9d97227174537e9c8a47e7a21f3b8c4ed30a0b2 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 14 Jul 2015 14:05:57 -0500 Subject: [PATCH 03/80] Moved color to it's own submodule, moved ansi colors to Style classmethods, full property name support, properties now dynamic, some support for setting .use_color --- .gitignore | 2 + CHANGELOG.rst | 4 + docs/_cheatsheet.rst | 16 ++ docs/_news.rst | 2 + docs/api/cli.rst | 2 - docs/api/color.rst | 8 + docs/cli.rst | 53 ----- docs/color.rst | 346 ++++++++++++++++++++++++++++++++ docs/index.rst | 4 +- plumbum/__init__.py | 2 +- plumbum/cli/__init__.py | 1 - plumbum/cli/application.py | 2 +- plumbum/color/__init__.py | 2 + plumbum/{cli => color}/color.py | 130 +++++++----- plumbum/color/names.py | 294 +++++++++++++++++++++++++++ setup.py | 2 +- tests/test_color.py | 11 +- 17 files changed, 766 insertions(+), 115 deletions(-) create mode 100644 docs/api/color.rst create mode 100644 docs/color.rst create mode 100644 plumbum/color/__init__.py rename plumbum/{cli => color}/color.py (55%) create mode 100644 plumbum/color/names.py diff --git a/.gitignore b/.gitignore index 795fb8d12..25de52f4b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ __pycache__ /build *.class *.ropeproject +*.swp +*~ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b039e8bbe..4c2718ef8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,7 @@ +1.5.0 +----- +* Color: new module for working with ANSI color codes for colored output + 1.4.3 ----- * CLI: add ``COLOR``, which allows simple access to ANSI color sequences diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index a0ed8c868..5f416b715 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -106,3 +106,19 @@ Sample output:: Include dirs: ['foo/bar', 'spam/eggs'] Compiling: ('x.cpp', 'y.cpp', 'z.cpp') +**Color controls**:: + + from plumbum import COLOR + with COLOR.RED: + print("Warning! I said " + COLOR.BOLD("WARNING") + "!") + print("That was an", COLOR.GREEN + "important" - COLOR.GREEN, "warning!") + +Sample output: + +.. raw:: html + +
+
Warning! I said WARNING!
+    That was an important warning!
+
+ diff --git a/docs/_news.rst b/docs/_news.rst index 9ba6a19ba..08fd2cdce 100644 --- a/docs/_news.rst +++ b/docs/_news.rst @@ -1,3 +1,5 @@ +* **planned**: Version 1.5.0 released. This release added the ``plumbum.color`` module. + * **soon**: Version 1.4.3 released. This release is a collection of smaller features and bug fixes. .. note:: ``setenv`` has been renamed ``with_env`` diff --git a/docs/api/cli.rst b/docs/api/cli.rst index 8eea774ba..dac60abf4 100644 --- a/docs/api/cli.rst +++ b/docs/api/cli.rst @@ -9,5 +9,3 @@ Package plumbum.cli .. automodule:: plumbum.cli.terminal :members: -.. automodule:: plumbum.cli.color - :members: diff --git a/docs/api/color.rst b/docs/api/color.rst new file mode 100644 index 000000000..34f5b4b44 --- /dev/null +++ b/docs/api/color.rst @@ -0,0 +1,8 @@ +Package plumbum.color +=================== +.. automodule:: plumbum.color.color + :members: + +.. automodule:: plumbum.color.names + :members: + diff --git a/docs/cli.rst b/docs/cli.rst index 63d740f87..69c99dbb1 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -444,59 +444,6 @@ Here's an example of running this application:: committing... -Color tools ------------ -.. versionadded:: 1.4.3 - -A built in color module provides quick, clean access to ANSI colors for your scripts. They are -accessed through the ``COLOR`` object, which is a collection of ``Style`` objects. The ``COLOR`` object has the following properties: - - ``FG`` and ``BG`` - The forground and background colors, reset to default with ``COLOR.FG.RESET`` or ``-COLOR.FG`` and likewise for ``BG``. (Named forground colors are available directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, ``BLUE``, ``MAGENTA``, - ``CYAN``, ``WHITE``, as well as ``RESET``, are available. - You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. - ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` - All the `ASNI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. - ``RESET`` - The global reset will restore all properties at once. - ``DO_NOTHING`` - Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. - -A color can be used directly as if it was a string, -or called for immediate printing to stdout. Calling a -color with an argument will wrap the string in the color and the matching negation. -Any color can be used as the target of a with statement, and normal color will be restored on exiting the with statement, even with an Exception. - -An example of the usage of ``COLOR``:: - - from plumbum import COLOR - with COLOR.FG.RED: - print('This is in red') - COLOR.FG.GREEN() - print('This is green') - print('This is completly restored, even if an exception is thrown!') - -We could have used the shortcut ``COLOR.GREEN()`` instead. You can also use COLOR directly as a context manager if you only want the restoring ability, and if you call ``COLOR(...)``, you can manually pass in any ANSI escape sequence. - -Further examples of manipulations possible with the library:: - - print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again') - with COLOR: - print('It is always a good idea to be in a context manager, to avoid being', - 'left with a colored terminal if there is an exception!') - COLOR.FG.RED() - print(COLOR.BOLD("This is red, bold, and exciting!"), "And this is red only.") - print(COLOR.BG.CYAN + "This is red on a cyan background." + COLOR.RESET) - print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) - COLOR.YELLOW() - print('Colors made', COLOR.UNDERLINE + 'very' - COLOR.UNDERLINE, 'easy!') - -The name of a script is automatically colored with the ``.COLOR_NAME`` property. Other style properties may be added; they will be set to ``COLOR.DO_NOTHING``, but can be overriden by your class. Any callable can be used to provide formats. - -.. note:: - The color library looks for a tty terminal and posix operating system. If you want to manually - enable or disable color, you can set ``plumbum.cli.color.USE_COLOR`` to True or False explicitly. - The same is true for ``plumbum.cli.color.STDOUT=sys.stdout``. See Also -------- diff --git a/docs/color.rst b/docs/color.rst new file mode 100644 index 000000000..00e385fbf --- /dev/null +++ b/docs/color.rst @@ -0,0 +1,346 @@ +.. _guide-color: + +Color tools +=========== + +.. versionadded:: 1.5.0 + + +A built in color module provides quick, clean access to ANSI colors for your scripts. They are +accessed through the ``COLOR`` object, which is a collection of ``Style`` objects. The ``COLOR`` object has the following properties: + + ``FG`` and ``BG`` + The forground and background colors, reset to default with ``COLOR.FG.RESET`` + or ``-COLOR.FG`` and likewise for ``BG``. (Named forground colors are available + directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, + ``BLUE``, ``MAGENTA``, ``CYAN``, ``WHITE``, as well as ``RESET``, are available. + You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, + and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. + ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` + All the `ASNI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. + ``RESET`` + The global reset will restore all properties at once. + ``DO_NOTHING`` + Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. + +A color can be used directly as if it was a string, +or called for immediate printing to ``stdout``. Calling a +color with an argument will wrap the string in the color and the matching negation. +Any color can be used as the target of a with statement, and normal color +will be restored on exiting the with statement, even with an Exception. + +An example of the usage of ``COLOR``:: + + from plumbum import COLOR + with COLOR.FG.RED: + print('This is in red') + COLOR.FG.GREEN() + print('This is green') + print('This is completly restored, even if an exception is thrown!') + +We could have used the shortcut ``COLOR.GREEN()`` instead. You can also use ``COLOR`` +directly as a context manager if you only want the restoring ability, and if you call +``COLOR(...)``, you can manually pass in any ANSI escape sequence. + +Further examples of manipulations possible with the library:: + + print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again') + with COLOR: + print('It is always a good idea to be in a context manager, to avoid being', + 'left with a colored terminal if there is an exception!') + COLOR.FG.RED() + print(COLOR.BOLD("This is red, bold, and exciting!"), "And this is red only.") + print(COLOR.BG.CYAN + "This is red on a cyan background." + COLOR.RESET) + print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) + COLOR.YELLOW() + print('Colors made', COLOR.UNDERLINE + 'very' - COLOR.UNDERLINE, 'easy!') + +The name of a script is automatically colored with the ``.COLOR_NAME`` property. Other style properties may be added; they will be set to ``COLOR.DO_NOTHING``, but can be overriden by your class. Any callable can be used to provide formats. + +.. note:: + The color library looks for a tty terminal and posix operating + system and only creates colors if those are found. If you want to manually + enable or disable color, you can set ``plumbum.color.Style.use_color = True`` + (or ``False``); however, this will only + affect new objects or manipulations. All Style color objects are dynamically + generated, so this usually is not a problem. + + +256 color support +----------------- + +The library support 256 colors through numbers, names or HEX html codes. You can access them +as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#0000FF']. The supported colors are + +.. raw:: html + +
    +
  1. #000000 Black
  2. +
  3. #800000 Red
  4. +
  5. #008000 Green
  6. +
  7. #808000 Yellow
  8. +
  9. #000080 Blue
  10. +
  11. #800080 Magenta
  12. +
  13. #008080 Cyan
  14. +
  15. #c0c0c0 LightGray
  16. +
  17. #808080 DarkGray
  18. +
  19. #ff0000 LightRed
  20. +
  21. #00ff00 LightGreen
  22. +
  23. #ffff00 LightYellow
  24. +
  25. #0000ff LightBlue
  26. +
  27. #ff00ff LightMagenta
  28. +
  29. #00ffff LightCyan
  30. +
  31. #ffffff White
  32. +
  33. #000000 Grey0
  34. +
  35. #00005f NavyBlue
  36. +
  37. #000087 DarkBlue
  38. +
  39. #0000af Blue3
  40. +
  41. #0000d7 Blue3
  42. +
  43. #0000ff Blue1
  44. +
  45. #005f00 DarkGreen
  46. +
  47. #005f5f DeepSkyBlue4
  48. +
  49. #005f87 DeepSkyBlue4
  50. +
  51. #005faf DeepSkyBlue4
  52. +
  53. #005fd7 DodgerBlue3
  54. +
  55. #005fff DodgerBlue2
  56. +
  57. #008700 Green4
  58. +
  59. #00875f SpringGreen4
  60. +
  61. #008787 Turquoise4
  62. +
  63. #0087af DeepSkyBlue3
  64. +
  65. #0087d7 DeepSkyBlue3
  66. +
  67. #0087ff DodgerBlue1
  68. +
  69. #00af00 Green3
  70. +
  71. #00af5f SpringGreen3
  72. +
  73. #00af87 DarkCyan
  74. +
  75. #00afaf LightSeaGreen
  76. +
  77. #00afd7 DeepSkyBlue2
  78. +
  79. #00afff DeepSkyBlue1
  80. +
  81. #00d700 Green3
  82. +
  83. #00d75f SpringGreen3
  84. +
  85. #00d787 SpringGreen2
  86. +
  87. #00d7af Cyan3
  88. +
  89. #00d7d7 DarkTurquoise
  90. +
  91. #00d7ff Turquoise2
  92. +
  93. #00ff00 Green1
  94. +
  95. #00ff5f SpringGreen2
  96. +
  97. #00ff87 SpringGreen1
  98. +
  99. #00ffaf MediumSpringGreen
  100. +
  101. #00ffd7 Cyan2
  102. +
  103. #00ffff Cyan1
  104. +
  105. #5f0000 DarkRed
  106. +
  107. #5f005f DeepPink4
  108. +
  109. #5f0087 Purple4
  110. +
  111. #5f00af Purple4
  112. +
  113. #5f00d7 Purple3
  114. +
  115. #5f00ff BlueViolet
  116. +
  117. #5f5f00 Orange4
  118. +
  119. #5f5f5f Grey37
  120. +
  121. #5f5f87 MediumPurple4
  122. +
  123. #5f5faf SlateBlue3
  124. +
  125. #5f5fd7 SlateBlue3
  126. +
  127. #5f5fff RoyalBlue1
  128. +
  129. #5f8700 Chartreuse4
  130. +
  131. #5f875f DarkSeaGreen4
  132. +
  133. #5f8787 PaleTurquoise4
  134. +
  135. #5f87af SteelBlue
  136. +
  137. #5f87d7 SteelBlue3
  138. +
  139. #5f87ff CornflowerBlue
  140. +
  141. #5faf00 Chartreuse3
  142. +
  143. #5faf5f DarkSeaGreen4
  144. +
  145. #5faf87 CadetBlue
  146. +
  147. #5fafaf CadetBlue
  148. +
  149. #5fafd7 SkyBlue3
  150. +
  151. #5fafff SteelBlue1
  152. +
  153. #5fd700 Chartreuse3
  154. +
  155. #5fd75f PaleGreen3
  156. +
  157. #5fd787 SeaGreen3
  158. +
  159. #5fd7af Aquamarine3
  160. +
  161. #5fd7d7 MediumTurquoise
  162. +
  163. #5fd7ff SteelBlue1
  164. +
  165. #5fff00 Chartreuse2
  166. +
  167. #5fff5f SeaGreen2
  168. +
  169. #5fff87 SeaGreen1
  170. +
  171. #5fffaf SeaGreen1
  172. +
  173. #5fffd7 Aquamarine1
  174. +
  175. #5fffff DarkSlateGray2
  176. +
  177. #870000 DarkRed
  178. +
  179. #87005f DeepPink4
  180. +
  181. #870087 DarkMagenta
  182. +
  183. #8700af DarkMagenta
  184. +
  185. #8700d7 DarkViolet
  186. +
  187. #8700ff Purple
  188. +
  189. #875f00 Orange4
  190. +
  191. #875f5f LightPink4
  192. +
  193. #875f87 Plum4
  194. +
  195. #875faf MediumPurple3
  196. +
  197. #875fd7 MediumPurple3
  198. +
  199. #875fff SlateBlue1
  200. +
  201. #878700 Yellow4
  202. +
  203. #87875f Wheat4
  204. +
  205. #878787 Grey53
  206. +
  207. #8787af LightSlateGrey
  208. +
  209. #8787d7 MediumPurple
  210. +
  211. #8787ff LightSlateBlue
  212. +
  213. #87af00 Yellow4
  214. +
  215. #87af5f DarkOliveGreen3
  216. +
  217. #87af87 DarkSeaGreen
  218. +
  219. #87afaf LightSkyBlue3
  220. +
  221. #87afd7 LightSkyBlue3
  222. +
  223. #87afff SkyBlue2
  224. +
  225. #87d700 Chartreuse2
  226. +
  227. #87d75f DarkOliveGreen3
  228. +
  229. #87d787 PaleGreen3
  230. +
  231. #87d7af DarkSeaGreen3
  232. +
  233. #87d7d7 DarkSlateGray3
  234. +
  235. #87d7ff SkyBlue1
  236. +
  237. #87ff00 Chartreuse1
  238. +
  239. #87ff5f LightGreen
  240. +
  241. #87ff87 LightGreen
  242. +
  243. #87ffaf PaleGreen1
  244. +
  245. #87ffd7 Aquamarine1
  246. +
  247. #87ffff DarkSlateGray1
  248. +
  249. #af0000 Red3
  250. +
  251. #af005f DeepPink4
  252. +
  253. #af0087 MediumVioletRed
  254. +
  255. #af00af Magenta3
  256. +
  257. #af00d7 DarkViolet
  258. +
  259. #af00ff Purple
  260. +
  261. #af5f00 DarkOrange3
  262. +
  263. #af5f5f IndianRed
  264. +
  265. #af5f87 HotPink3
  266. +
  267. #af5faf MediumOrchid3
  268. +
  269. #af5fd7 MediumOrchid
  270. +
  271. #af5fff MediumPurple2
  272. +
  273. #af8700 DarkGoldenrod
  274. +
  275. #af875f LightSalmon3
  276. +
  277. #af8787 RosyBrown
  278. +
  279. #af87af Grey63
  280. +
  281. #af87d7 MediumPurple2
  282. +
  283. #af87ff MediumPurple1
  284. +
  285. #afaf00 Gold3
  286. +
  287. #afaf5f DarkKhaki
  288. +
  289. #afaf87 NavajoWhite3
  290. +
  291. #afafaf Grey69
  292. +
  293. #afafd7 LightSteelBlue3
  294. +
  295. #afafff LightSteelBlue
  296. +
  297. #afd700 Yellow3
  298. +
  299. #afd75f DarkOliveGreen3
  300. +
  301. #afd787 DarkSeaGreen3
  302. +
  303. #afd7af DarkSeaGreen2
  304. +
  305. #afd7d7 LightCyan3
  306. +
  307. #afd7ff LightSkyBlue1
  308. +
  309. #afff00 GreenYellow
  310. +
  311. #afff5f DarkOliveGreen2
  312. +
  313. #afff87 PaleGreen1
  314. +
  315. #afffaf DarkSeaGreen2
  316. +
  317. #afffd7 DarkSeaGreen1
  318. +
  319. #afffff PaleTurquoise1
  320. +
  321. #d70000 Red3
  322. +
  323. #d7005f DeepPink3
  324. +
  325. #d70087 DeepPink3
  326. +
  327. #d700af Magenta3
  328. +
  329. #d700d7 Magenta3
  330. +
  331. #d700ff Magenta2
  332. +
  333. #d75f00 DarkOrange3
  334. +
  335. #d75f5f IndianRed
  336. +
  337. #d75f87 HotPink3
  338. +
  339. #d75faf HotPink2
  340. +
  341. #d75fd7 Orchid
  342. +
  343. #d75fff MediumOrchid1
  344. +
  345. #d78700 Orange3
  346. +
  347. #d7875f LightSalmon3
  348. +
  349. #d78787 LightPink3
  350. +
  351. #d787af Pink3
  352. +
  353. #d787d7 Plum3
  354. +
  355. #d787ff Violet
  356. +
  357. #d7af00 Gold3
  358. +
  359. #d7af5f LightGoldenrod3
  360. +
  361. #d7af87 Tan
  362. +
  363. #d7afaf MistyRose3
  364. +
  365. #d7afd7 Thistle3
  366. +
  367. #d7afff Plum2
  368. +
  369. #d7d700 Yellow3
  370. +
  371. #d7d75f Khaki3
  372. +
  373. #d7d787 LightGoldenrod2
  374. +
  375. #d7d7af LightYellow3
  376. +
  377. #d7d7d7 Grey84
  378. +
  379. #d7d7ff LightSteelBlue1
  380. +
  381. #d7ff00 Yellow2
  382. +
  383. #d7ff5f DarkOliveGreen1
  384. +
  385. #d7ff87 DarkOliveGreen1
  386. +
  387. #d7ffaf DarkSeaGreen1
  388. +
  389. #d7ffd7 Honeydew2
  390. +
  391. #d7ffff LightCyan1
  392. +
  393. #ff0000 Red1
  394. +
  395. #ff005f DeepPink2
  396. +
  397. #ff0087 DeepPink1
  398. +
  399. #ff00af DeepPink1
  400. +
  401. #ff00d7 Magenta2
  402. +
  403. #ff00ff Magenta1
  404. +
  405. #ff5f00 OrangeRed1
  406. +
  407. #ff5f5f IndianRed1
  408. +
  409. #ff5f87 IndianRed1
  410. +
  411. #ff5faf HotPink
  412. +
  413. #ff5fd7 HotPink
  414. +
  415. #ff5fff MediumOrchid1
  416. +
  417. #ff8700 DarkOrange
  418. +
  419. #ff875f Salmon1
  420. +
  421. #ff8787 LightCoral
  422. +
  423. #ff87af PaleVioletRed1
  424. +
  425. #ff87d7 Orchid2
  426. +
  427. #ff87ff Orchid1
  428. +
  429. #ffaf00 Orange1
  430. +
  431. #ffaf5f SandyBrown
  432. +
  433. #ffaf87 LightSalmon1
  434. +
  435. #ffafaf LightPink1
  436. +
  437. #ffafd7 Pink1
  438. +
  439. #ffafff Plum1
  440. +
  441. #ffd700 Gold1
  442. +
  443. #ffd75f LightGoldenrod2
  444. +
  445. #ffd787 LightGoldenrod2
  446. +
  447. #ffd7af NavajoWhite1
  448. +
  449. #ffd7d7 MistyRose1
  450. +
  451. #ffd7ff Thistle1
  452. +
  453. #ffff00 Yellow1
  454. +
  455. #ffff5f LightGoldenrod1
  456. +
  457. #ffff87 Khaki1
  458. +
  459. #ffffaf Wheat1
  460. +
  461. #ffffd7 Cornsilk1
  462. +
  463. #ffffff Grey100
  464. +
  465. #080808 Grey3
  466. +
  467. #121212 Grey7
  468. +
  469. #1c1c1c Grey11
  470. +
  471. #262626 Grey15
  472. +
  473. #303030 Grey19
  474. +
  475. #3a3a3a Grey23
  476. +
  477. #444444 Grey27
  478. +
  479. #4e4e4e Grey30
  480. +
  481. #585858 Grey35
  482. +
  483. #626262 Grey39
  484. +
  485. #6c6c6c Grey42
  486. +
  487. #767676 Grey46
  488. +
  489. #808080 Grey50
  490. +
  491. #8a8a8a Grey54
  492. +
  493. #949494 Grey58
  494. +
  495. #9e9e9e Grey62
  496. +
  497. #a8a8a8 Grey66
  498. +
  499. #b2b2b2 Grey70
  500. +
  501. #bcbcbc Grey74
  502. +
  503. #c6c6c6 Grey78
  504. +
  505. #d0d0d0 Grey82
  506. +
  507. #dadada Grey85
  508. +
  509. #e4e4e4 Grey89
  510. +
  511. #eeeeee Grey93
  512. +
+ +Style object +------------ + +The library works through the Style object. It is a subclass of ``str`` that adds color +related methods. + + +See Also +-------- +* `colored `_ Another library with 256 color support +* `colorama `_ A library that supports colored text on Windows diff --git a/docs/index.rst b/docs/index.rst index 900265fd6..70d415d40 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,7 +54,7 @@ and cross-platform**. Apart from :ref:`shell-like syntax ` and :ref:`handy shortcuts `, the library provides local and :ref:`remote ` command execution (over SSH), local and remote file-system :ref:`paths `, easy working-directory and -environment :ref:`manipulation `, and a programmatic +environment :ref:`manipulation `, quick access to ANSI :ref:`colors `, and a programmatic :ref:`guide-cli` application toolkit. Now let's see some code! News @@ -110,6 +110,7 @@ you read it in order. remote utils cli + color changelog API Reference @@ -126,6 +127,7 @@ missing from the guide, so you might want to consult with the API reference in t api/machines api/path api/fs + api/color About ===== diff --git a/plumbum/__init__.py b/plumbum/__init__.py index 545698932..5f55b158b 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -39,7 +39,7 @@ from plumbum.path import Path, LocalPath, RemotePath from plumbum.machines import local, BaseRemoteMachine, SshMachine, PuttyMachine from plumbum.version import version -from plumbum.cli import COLOR +from plumbum.color import COLOR __author__ = "Tomer Filiba (tomerfiliba@gmail.com)" __version__ = version diff --git a/plumbum/cli/__init__.py b/plumbum/cli/__init__.py index 18c04bd13..8ac2b4bb5 100644 --- a/plumbum/cli/__init__.py +++ b/plumbum/cli/__init__.py @@ -1,4 +1,3 @@ from plumbum.cli.switches import SwitchError, switch, autoswitch, SwitchAttr, Flag, CountOf from plumbum.cli.switches import Range, Set, ExistingDirectory, ExistingFile, NonexistentPath, Predicate from plumbum.cli.application import Application -from plumbum.cli.color import COLOR, with_color diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index d0f3cb39e..bc65be255 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -8,7 +8,7 @@ from plumbum.cli.switches import (SwitchError, UnknownSwitch, MissingArgument, WrongArgumentType, MissingMandatorySwitch, SwitchCombinationError, PositionalArgumentsError, switch, SubcommandError, Flag, CountOf) -from plumbum.cli.color import COLOR +from plumbum.color import COLOR class ShowHelp(SwitchError): diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py new file mode 100644 index 000000000..a2cdd1486 --- /dev/null +++ b/plumbum/color/__init__.py @@ -0,0 +1,2 @@ +from plumbum.color.color import COLOR, with_color, Style + diff --git a/plumbum/cli/color.py b/plumbum/color/color.py similarity index 55% rename from plumbum/cli/color.py rename to plumbum/color/color.py index 4be62ca14..327faf25c 100644 --- a/plumbum/cli/color.py +++ b/plumbum/color/color.py @@ -14,33 +14,37 @@ import sys import os from contextlib import contextmanager - -# This can be manually forced using plumbum.cli.color.USE_COLOR = True -USE_COLOR = sys.stdout.isatty() and os.name == "posix" -STDOUT = sys.stdout +from plumbum.color.names import names, html, camel_names +from plumbum.color.names import _simple_colors, _simple_attributes +from functools import partial class Style(str): """This class allows the color change strings to be called directly - to write them to stdout, and can be called in a with statement.""" + to write them to stdout, and can be called in a with statement. + The use_color property causes this to return '' for colors, but only on + new instances of the object.""" + use_color = sys.stdout.isatty() and os.name == "posix" + stdout = sys.stdout def negate(self): """This negates the effect of the current color""" - return self.__class__(self._remove() if USE_COLOR else '') + return self.__class__(self._remove() if self.__class__.use_color else '') def now(self, wrap_this=None): - STDOUT.write(self.wrap(wrap_this)) + self.stdout.write(self.wrap(wrap_this)) def wrap(self, wrap_this=None): if wrap_this is None: - return self if USE_COLOR else self.__class__('') + return self if self.__class__.use_color else self.__class__('') else: - if USE_COLOR: + if self.__class__.use_color: return self + wrap_this - self else: return wrap_this + def __call__(self, wrap_this=None): """This sets the color (no arguments) or wraps a str (1 arg)""" if wrap_this is None: @@ -78,57 +82,73 @@ def _remove(self): return '\033[0m' def __enter__(self): - if USE_COLOR: - STDOUT.write(self) + if self.__class__.use_color: + self.stdout.write(self) return self def __exit__(self, type, value, traceback): - if USE_COLOR: - STDOUT.write(-self) + if self.__class__.use_color: + self.stdout.write(-self) return False -def ansi_color(n): - """Return an ANSI escape sequence given a number.""" - return Style('\033[' + str(n) + 'm') if USE_COLOR else Style('') + @classmethod + def ansi_color(cls, n): + """Return an ANSI escape sequence given a number.""" + return cls('\033[' + str(n) + 'm') if cls.use_color else cls('') + + @classmethod + def extended_ansi_color(cls, n, begin=38): + """Return an ANSI extended color given number.""" + return cls('\033[' + str(begin) + ';5;' + str(n) + 'm') if cls.use_color else cls('') + +def _get_style_color(color, ob): + 'Gets a color, intended to be used in discriptor protocol' + return Style.ansi_color(_simple_colors[color]+ob.val) -def extended_ansi_color(n, begin=38): - """Return an ANSI extended color given number.""" - return Style('\033[' + str(begin) + ';5;' + str(n) + 'm') if USE_COLOR else Style('') + +def _get_style_attribute(attribute, ob): + 'Gets an attribute, intended to be used in discriptor protocol' + return Style.ansi_color(_simple_attributes[attribute]) class _COLOR_NAMES(object): """This creates color names given a modifier value (FG, BG)""" + def __init__(self, val): self.val = val - self.BLACK = ansi_color(0+self.val) - self.RED = ansi_color(1+self.val) - self.GREEN = ansi_color(2+self.val) - self.YELLOW = ansi_color(3+self.val) - self.BLUE = ansi_color(4+self.val) - self.MAGENTA = ansi_color(5+self.val) - self.CYAN = ansi_color(6+self.val) - self.WHITE = ansi_color(7+self.val) - self.RESET = ansi_color(9+self.val) + + def from_name(self, name): + 'Gets the index of a name, raises key error if not found' + if name in names: + return Style.extended_ansi_color(names.index(name), self.val + 8) + if name in camel_names: + return Style.extended_ansi_color(camel_names.index(name), self.val + 8) + if name in html: + return Style.extended_ansi_color(html.index(name, 1), self.val + 8) # Assuming second #000000 is better + raise KeyError(name) def regular(self, val): """Access colors by value.""" - return ansi_color(val + self.val) + return Style.ansi_color(val + self.val) def extended(self, val): """Return the extended color scheme color for a value.""" - return extended_ansi_color(val, self.val + 8) + return Style.extended_ansi_color(val, self.val + 8) def __call__(self, val): """Shortcut to provide way to access colors by number""" return self.regular(val) def __getitem__(self, val): - """Shortcut to provide way to access extended colors by number""" - return self.extended(val) + """Shortcut to provide way to access extended colors by number, name, or html hex code""" + try: + self.from_name(val) + except KeyError: + return self.extended(val) def __iter__(self): """Iterates through all colors in extended colorset.""" @@ -141,43 +161,46 @@ def __neg__(self): def __rsub__(self, other): return other + (-self) +# Adding the color name shortcuts +for item in _simple_colors: + setattr(_COLOR_NAMES, item.upper(), property(partial(_get_style_color, item))) class COLOR(object): + """Holds font styles, FG and BG objects representing colors, and + imitates the FG object to some degree.""" + def __init__(self): - self.BOLD = ansi_color(1) - self.DIM = ansi_color(2) - self.UNDERLINE = ansi_color(4) - self.BLINK = ansi_color(5) - self.REVERSE = ansi_color(7) - self.HIDDEN = ansi_color(8) + self.val = 30 self.FG = _COLOR_NAMES(30) self.BG = _COLOR_NAMES(40) - self.BLACK = self.FG.BLACK - self.RED = self.FG.RED - self.GREEN = self.FG.GREEN - self.YELLOW = self.FG.YELLOW - self.BLUE = self.FG.BLUE - self.MAGENTA = self.FG.MAGENTA - self.CYAN = self.FG.CYAN - self.WHITE = self.FG.WHITE - - self.RESET = ansi_color(0) self.DO_NOTHING = Style('') + def __iter__(self): + return self.FG.__iter__() + def __call__(self, color): return Style(color) + def __getitem__(self, item): + return self.FG[item] + def __enter__(self): return self def __exit__(self, type, value, traceback): - STDOUT.write(ansi_color(0)) + Style.stdout.write(Style.ansi_color(0)) return False +for item in _simple_colors: + setattr(COLOR, item.upper(), property(partial(_get_style_color, item))) +for item in _simple_attributes: + setattr(COLOR, item.upper(), property(partial(_get_style_attribute, item))) + + COLOR = COLOR() @@ -186,15 +209,14 @@ def with_color(color='', out=None): """Sets the color to a given color or style, resets when done, even if an exception is thrown. Optional ``out`` to give a - different output channel (defaults to STDOUT, which is set - to sys.stdout).""" + different output channel (defaults to sys.stdout).""" if out is None: - out = STDOUT + out = sys.stdout out.write(str(color)) try: yield finally: - out.write(ansi_color(0)) + out.write(Style.ansi_color(0)) -__all__ = ['COLOR', 'with_color', 'ansi_color', 'extended_ansi_color', 'Style', 'USE_COLOR', 'STDOUT'] +__all__ = ['COLOR', 'with_color', 'Style'] diff --git a/plumbum/color/names.py b/plumbum/color/names.py new file mode 100644 index 000000000..6e64db469 --- /dev/null +++ b/plumbum/color/names.py @@ -0,0 +1,294 @@ +''' +Names for the standard and extended color set. +Extended set is similar to http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim, https://pypi.python.org/pypi/colored, etc. + +You can access the index of the colors with names.index(name). You can access the +rgb values with r=int(html[n][1:3],16), etc. +''' + + +_named_colors = '''\ +0,black,#000000 +1,red,#800000 +2,green,#008000 +3,yellow,#808000 +4,blue,#000080 +5,magenta,#800080 +6,cyan,#008080 +7,light_gray,#c0c0c0 +8,dark_gray,#808080 +9,light_red,#ff0000 +10,light_green,#00ff00 +11,light_yellow,#ffff00 +12,light_blue,#0000ff +13,light_magenta,#ff00ff +14,light_cyan,#00ffff +15,white,#ffffff +16,grey_0,#000000 +17,navy_blue,#00005f +18,dark_blue,#000087 +19,blue_3,#0000af +20,blue_3,#0000d7 +21,blue_1,#0000ff +22,dark_green,#005f00 +23,deep_sky_blue_4,#005f5f +24,deep_sky_blue_4,#005f87 +25,deep_sky_blue_4,#005faf +26,dodger_blue_3,#005fd7 +27,dodger_blue_2,#005fff +28,green_4,#008700 +29,spring_green_4,#00875f +30,turquoise_4,#008787 +31,deep_sky_blue_3,#0087af +32,deep_sky_blue_3,#0087d7 +33,dodger_blue_1,#0087ff +34,green_3,#00af00 +35,spring_green_3,#00af5f +36,dark_cyan,#00af87 +37,light_sea_green,#00afaf +38,deep_sky_blue_2,#00afd7 +39,deep_sky_blue_1,#00afff +40,green_3,#00d700 +41,spring_green_3,#00d75f +42,spring_green_2,#00d787 +43,cyan_3,#00d7af +44,dark_turquoise,#00d7d7 +45,turquoise_2,#00d7ff +46,green_1,#00ff00 +47,spring_green_2,#00ff5f +48,spring_green_1,#00ff87 +49,medium_spring_green,#00ffaf +50,cyan_2,#00ffd7 +51,cyan_1,#00ffff +52,dark_red,#5f0000 +53,deep_pink_4,#5f005f +54,purple_4,#5f0087 +55,purple_4,#5f00af +56,purple_3,#5f00d7 +57,blue_violet,#5f00ff +58,orange_4,#5f5f00 +59,grey_37,#5f5f5f +60,medium_purple_4,#5f5f87 +61,slate_blue_3,#5f5faf +62,slate_blue_3,#5f5fd7 +63,royal_blue_1,#5f5fff +64,chartreuse_4,#5f8700 +65,dark_sea_green_4,#5f875f +66,pale_turquoise_4,#5f8787 +67,steel_blue,#5f87af +68,steel_blue_3,#5f87d7 +69,cornflower_blue,#5f87ff +70,chartreuse_3,#5faf00 +71,dark_sea_green_4,#5faf5f +72,cadet_blue,#5faf87 +73,cadet_blue,#5fafaf +74,sky_blue_3,#5fafd7 +75,steel_blue_1,#5fafff +76,chartreuse_3,#5fd700 +77,pale_green_3,#5fd75f +78,sea_green_3,#5fd787 +79,aquamarine_3,#5fd7af +80,medium_turquoise,#5fd7d7 +81,steel_blue_1,#5fd7ff +82,chartreuse_2,#5fff00 +83,sea_green_2,#5fff5f +84,sea_green_1,#5fff87 +85,sea_green_1,#5fffaf +86,aquamarine_1,#5fffd7 +87,dark_slate_gray_2,#5fffff +88,dark_red,#870000 +89,deep_pink_4,#87005f +90,dark_magenta,#870087 +91,dark_magenta,#8700af +92,dark_violet,#8700d7 +93,purple,#8700ff +94,orange_4,#875f00 +95,light_pink_4,#875f5f +96,plum_4,#875f87 +97,medium_purple_3,#875faf +98,medium_purple_3,#875fd7 +99,slate_blue_1,#875fff +100,yellow_4,#878700 +101,wheat_4,#87875f +102,grey_53,#878787 +103,light_slate_grey,#8787af +104,medium_purple,#8787d7 +105,light_slate_blue,#8787ff +106,yellow_4,#87af00 +107,dark_olive_green_3,#87af5f +108,dark_sea_green,#87af87 +109,light_sky_blue_3,#87afaf +110,light_sky_blue_3,#87afd7 +111,sky_blue_2,#87afff +112,chartreuse_2,#87d700 +113,dark_olive_green_3,#87d75f +114,pale_green_3,#87d787 +115,dark_sea_green_3,#87d7af +116,dark_slate_gray_3,#87d7d7 +117,sky_blue_1,#87d7ff +118,chartreuse_1,#87ff00 +119,light_green,#87ff5f +120,light_green,#87ff87 +121,pale_green_1,#87ffaf +122,aquamarine_1,#87ffd7 +123,dark_slate_gray_1,#87ffff +124,red_3,#af0000 +125,deep_pink_4,#af005f +126,medium_violet_red,#af0087 +127,magenta_3,#af00af +128,dark_violet,#af00d7 +129,purple,#af00ff +130,dark_orange_3,#af5f00 +131,indian_red,#af5f5f +132,hot_pink_3,#af5f87 +133,medium_orchid_3,#af5faf +134,medium_orchid,#af5fd7 +135,medium_purple_2,#af5fff +136,dark_goldenrod,#af8700 +137,light_salmon_3,#af875f +138,rosy_brown,#af8787 +139,grey_63,#af87af +140,medium_purple_2,#af87d7 +141,medium_purple_1,#af87ff +142,gold_3,#afaf00 +143,dark_khaki,#afaf5f +144,navajo_white_3,#afaf87 +145,grey_69,#afafaf +146,light_steel_blue_3,#afafd7 +147,light_steel_blue,#afafff +148,yellow_3,#afd700 +149,dark_olive_green_3,#afd75f +150,dark_sea_green_3,#afd787 +151,dark_sea_green_2,#afd7af +152,light_cyan_3,#afd7d7 +153,light_sky_blue_1,#afd7ff +154,green_yellow,#afff00 +155,dark_olive_green_2,#afff5f +156,pale_green_1,#afff87 +157,dark_sea_green_2,#afffaf +158,dark_sea_green_1,#afffd7 +159,pale_turquoise_1,#afffff +160,red_3,#d70000 +161,deep_pink_3,#d7005f +162,deep_pink_3,#d70087 +163,magenta_3,#d700af +164,magenta_3,#d700d7 +165,magenta_2,#d700ff +166,dark_orange_3,#d75f00 +167,indian_red,#d75f5f +168,hot_pink_3,#d75f87 +169,hot_pink_2,#d75faf +170,orchid,#d75fd7 +171,medium_orchid_1,#d75fff +172,orange_3,#d78700 +173,light_salmon_3,#d7875f +174,light_pink_3,#d78787 +175,pink_3,#d787af +176,plum_3,#d787d7 +177,violet,#d787ff +178,gold_3,#d7af00 +179,light_goldenrod_3,#d7af5f +180,tan,#d7af87 +181,misty_rose_3,#d7afaf +182,thistle_3,#d7afd7 +183,plum_2,#d7afff +184,yellow_3,#d7d700 +185,khaki_3,#d7d75f +186,light_goldenrod_2,#d7d787 +187,light_yellow_3,#d7d7af +188,grey_84,#d7d7d7 +189,light_steel_blue_1,#d7d7ff +190,yellow_2,#d7ff00 +191,dark_olive_green_1,#d7ff5f +192,dark_olive_green_1,#d7ff87 +193,dark_sea_green_1,#d7ffaf +194,honeydew_2,#d7ffd7 +195,light_cyan_1,#d7ffff +196,red_1,#ff0000 +197,deep_pink_2,#ff005f +198,deep_pink_1,#ff0087 +199,deep_pink_1,#ff00af +200,magenta_2,#ff00d7 +201,magenta_1,#ff00ff +202,orange_red_1,#ff5f00 +203,indian_red_1,#ff5f5f +204,indian_red_1,#ff5f87 +205,hot_pink,#ff5faf +206,hot_pink,#ff5fd7 +207,medium_orchid_1,#ff5fff +208,dark_orange,#ff8700 +209,salmon_1,#ff875f +210,light_coral,#ff8787 +211,pale_violet_red_1,#ff87af +212,orchid_2,#ff87d7 +213,orchid_1,#ff87ff +214,orange_1,#ffaf00 +215,sandy_brown,#ffaf5f +216,light_salmon_1,#ffaf87 +217,light_pink_1,#ffafaf +218,pink_1,#ffafd7 +219,plum_1,#ffafff +220,gold_1,#ffd700 +221,light_goldenrod_2,#ffd75f +222,light_goldenrod_2,#ffd787 +223,navajo_white_1,#ffd7af +224,misty_rose_1,#ffd7d7 +225,thistle_1,#ffd7ff +226,yellow_1,#ffff00 +227,light_goldenrod_1,#ffff5f +228,khaki_1,#ffff87 +229,wheat_1,#ffffaf +230,cornsilk_1,#ffffd7 +231,grey_10_0,#ffffff +232,grey_3,#080808 +233,grey_7,#121212 +234,grey_11,#1c1c1c +235,grey_15,#262626 +236,grey_19,#303030 +237,grey_23,#3a3a3a +238,grey_27,#444444 +239,grey_30,#4e4e4e +240,grey_35,#585858 +241,grey_39,#626262 +242,grey_42,#6c6c6c +243,grey_46,#767676 +244,grey_50,#808080 +245,grey_54,#8a8a8a +246,grey_58,#949494 +247,grey_62,#9e9e9e +248,grey_66,#a8a8a8 +249,grey_70,#b2b2b2 +250,grey_74,#bcbcbc +251,grey_78,#c6c6c6 +252,grey_82,#d0d0d0 +253,grey_85,#dadada +254,grey_89,#e4e4e4 +255,grey_93,#eeeeee +''' + +names = [n.split(',')[1] for n in _named_colors.split()] +html = [n.split(',')[2] for n in _named_colors.split()] +camel_names = [n.replace('_', ' ').title().replace(' ','') for n in names] + +_simple_colors = dict( + black=0, + red=1, + green=2, + yellow=3, + blue=4, + magenta=5, + cyan=6, + white=7, + reset=9 +) + +_simple_attributes = dict( + reset=0, + bold=1, + dim=2, + underline=4, + blink=5, + reverse=7, + hidden=8 + ) + diff --git a/setup.py b/setup.py index b66ff3bd6..33d95c73a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ author_email = "tomerfiliba@gmail.com", license = "MIT", url = "http://plumbum.readthedocs.org", - packages = ["plumbum", "plumbum.cli", "plumbum.commands", "plumbum.machines", "plumbum.path", "plumbum.fs"], + packages = ["plumbum", "plumbum.cli", "plumbum.commands", "plumbum.machines", "plumbum.path", "plumbum.fs", "plumbum.color"], platforms = ["POSIX", "Windows"], provides = ["plumbum"], keywords = "path, local, remote, ssh, shell, pipe, popen, process, execution", diff --git a/tests/test_color.py b/tests/test_color.py index 20cc55406..8b9ac0dd4 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,10 +1,15 @@ from __future__ import with_statement, print_function import unittest -from plumbum.cli import COLOR, with_color +from plumbum import COLOR +from plumbum.color import with_color +from plumbum.color.color import Style class TestColor(unittest.TestCase): + def setUp(self): + Style.use_color = True + def testColorStrings(self): self.assertEqual('\033[0m', COLOR.RESET) self.assertEqual('\033[1m', COLOR.BOLD) @@ -30,6 +35,10 @@ def testUndoColor(self): self.assertEqual('\033[0m', -COLOR.RESET) self.assertEqual('\033[0m', -COLOR('this is random')) + def testLackOfColor(self): + Style.use_color = False + self.assertEqual('', COLOR.FG.RED) + def testVisualColors(self): print() for c in (COLOR.FG(x) for x in range(1, 6)): From 983e899f2df138cb83067705f0265de908b25af5 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 14 Jul 2015 14:22:00 -0500 Subject: [PATCH 04/80] Minor fixes for COLOR, shares code with _COLOR_NAMES now. More tests. --- plumbum/color/color.py | 32 ++++++++++++++------------------ tests/test_color.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/plumbum/color/color.py b/plumbum/color/color.py index 327faf25c..8d9062d8d 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -146,7 +146,7 @@ def __call__(self, val): def __getitem__(self, val): """Shortcut to provide way to access extended colors by number, name, or html hex code""" try: - self.from_name(val) + return self.from_name(val) except KeyError: return self.extended(val) @@ -161,42 +161,38 @@ def __neg__(self): def __rsub__(self, other): return other + (-self) + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + """This resets a FG/BG color or all colors, + due to different definition of RESET.""" + + Style.stdout.write(self.RESET) + return False + # Adding the color name shortcuts for item in _simple_colors: setattr(_COLOR_NAMES, item.upper(), property(partial(_get_style_color, item))) -class COLOR(object): +class COLOR(_COLOR_NAMES): """Holds font styles, FG and BG objects representing colors, and imitates the FG object to some degree.""" def __init__(self): - self.val = 30 + self.val = 30 # This acts like FG color self.FG = _COLOR_NAMES(30) self.BG = _COLOR_NAMES(40) self.DO_NOTHING = Style('') - def __iter__(self): - return self.FG.__iter__() - def __call__(self, color): + """Calling COLOR is a shortcut for Style(color)""" return Style(color) - def __getitem__(self, item): - return self.FG[item] - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - Style.stdout.write(Style.ansi_color(0)) - return False - - -for item in _simple_colors: - setattr(COLOR, item.upper(), property(partial(_get_style_color, item))) for item in _simple_attributes: setattr(COLOR, item.upper(), property(partial(_get_style_attribute, item))) diff --git a/tests/test_color.py b/tests/test_color.py index 8b9ac0dd4..48969a9c3 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -13,6 +13,19 @@ def setUp(self): def testColorStrings(self): self.assertEqual('\033[0m', COLOR.RESET) self.assertEqual('\033[1m', COLOR.BOLD) + self.assertEqual('\033[39m', COLOR.FG.RESET) + + def testNegateIsReset(self): + self.assertEqual(COLOR.RESET, -COLOR) + self.assertEqual(COLOR.FG.RESET, -COLOR.FG) + self.assertEqual(COLOR.BG.RESET, -COLOR.BG) + + def testLoadColorByName(self): + self.assertEqual(COLOR['LightBlue'], COLOR.FG['LightBlue']) + self.assertEqual(COLOR.BG['light_green'], COLOR.BG['LightGreen']) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) + def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) @@ -38,6 +51,8 @@ def testUndoColor(self): def testLackOfColor(self): Style.use_color = False self.assertEqual('', COLOR.FG.RED) + self.assertEqual('', -COLOR.FG) + self.assertEqual('', COLOR.FG['LightBlue']) def testVisualColors(self): print() From f88c4bfcb3fa35fc91fd985d551be4e4b88c0ea6 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 14 Jul 2015 15:03:17 -0500 Subject: [PATCH 05/80] Styles now can be summed. COLOR.BOLD + COLOR.RED is now a valid Style object and works like one. --- plumbum/color/color.py | 9 +++++++++ tests/test_color.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/plumbum/color/color.py b/plumbum/color/color.py index 8d9062d8d..5d73d2e25 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -56,10 +56,17 @@ def __neg__(self): """This negates the effect of the current color""" return self.negate() + def __sub__(self, other): + """Implemented to make muliple Style objects work""" + return self + (-other) + def __rsub__(self, other): """Implemented to make using negatives easier""" return other + (-self) + def __add__(self, other): + return self.__class__(super(Style, self).__add__(other)) + def _remove(self): """Don't use directly. Will find best match for negative of current color.""" if self == '': @@ -77,6 +84,8 @@ def _remove(self): return '\033['+str(n[0]+20)+'m' elif n[0] in (21, 22, 24, 25, 27, 28): return '\033['+str(n[0]-20)+'m' + else: + return '' except ValueError: pass return '\033[0m' diff --git a/tests/test_color.py b/tests/test_color.py index 48969a9c3..0e690894a 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -26,6 +26,9 @@ def testLoadColorByName(self): self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) + def testMultiColor(self): + sumcolor = COLOR.BOLD + COLOR.BLUE + self.assertEqual(COLOR.RESET, -sumcolor) def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) From dc7dc86adb61694e5a499c3592e8b547731fa05f Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 14 Jul 2015 15:41:45 -0500 Subject: [PATCH 06/80] Added color support to Application, use ColorfulApplication for brighter defaults --- CHANGELOG.rst | 1 + docs/cli.rst | 15 +++++ examples/geet.py | 35 ++++++----- plumbum/cli/__init__.py | 2 +- plumbum/cli/application.py | 122 ++++++++++++++++++++++++++----------- 5 files changed, 122 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c2718ef8..3d7189f36 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,7 @@ 1.5.0 ----- * Color: new module for working with ANSI color codes for colored output +* CLI: Added color support 1.4.3 ----- diff --git a/docs/cli.rst b/docs/cli.rst index 69c99dbb1..230d458c2 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -73,6 +73,21 @@ class-level attributes, such as ``PROGNAME``, ``VERSION`` and ``DESCRIPTION``. F class MyApp(cli.Application): PROGNAME = "Foobar" VERSION = "7.3" + +Colors are supported through the class level attributes +``COLOR_PROGNAME``, +``COLOR_DISCRIPTION``, +``COLOR_VERSION``, +``COLOR_HEADING``, +``COLOR_USAGE``, +``COLOR_SUBCOMMANDS``, +``COLOR_GROUPS[]``, and +``COLOR_GROUPS_BODY[]``, +which should contain Style objects. The dictionaries support custom colors +for named groups. The default is for only one color, ``COLOR_PROGNAME = COLOR.CYAN`` (to match +previous behavior), but if you just want more colorful defaults, subclass ``cli.ColorfulApplication``. + +.. versionadded:: 1.5 Switch Functions ---------------- diff --git a/examples/geet.py b/examples/geet.py index 5a406f5b8..016024b65 100644 --- a/examples/geet.py +++ b/examples/geet.py @@ -3,71 +3,74 @@ $ python geet.py no command given - + $ python geet.py leet unknown command 'leet' - + $ python geet.py --help geet v1.7.2 The l33t version control - + Usage: geet.py [SWITCHES] [SUBCOMMAND [SWITCHES]] args... Meta-switches: -h, --help Prints this help message and quits -v, --version Prints the program's version and quits - + Subcommands: commit creates a new commit in the current branch; see 'geet commit --help' for more info push pushes the current local branch to the remote one; see 'geet push --help' for more info - + $ python geet.py commit --help geet commit v1.7.2 creates a new commit in the current branch - + Usage: geet commit [SWITCHES] Meta-switches: -h, --help Prints this help message and quits -v, --version Prints the program's version and quits - + Switches: -a automatically add changed files -m VALUE:str sets the commit message; required - + $ python geet.py commit -m "foo" committing... """ from plumbum import cli +# To force no color support: +# from plumbum.color import Style +# Style.use_color = False -class Geet(cli.Application): +class Geet(cli.ColorfulApplication): """The l33t version control""" PROGNAME = "geet" VERSION = "1.7.2" - + verbosity = cli.SwitchAttr("--verbosity", cli.Set("low", "high", "some-very-long-name", "to-test-wrap-around"), help = "sets the verbosity level of the geet tool. doesn't really do anything except for testing line-wrapping " "in help " * 3) @Geet.subcommand("commit") -class GeetCommit(cli.Application): +class GeetCommit(cli.ColorfulApplication): """creates a new commit in the current branch""" - + auto_add = cli.Flag("-a", help = "automatically add changed files") message = cli.SwitchAttr("-m", str, mandatory = True, help = "sets the commit message") - + def main(self): print("committing...") GeetCommit.unbind_switches("-v", "--version") @Geet.subcommand("push") -class GeetPush(cli.Application): +class GeetPush(cli.ColorfulApplication): """pushes the current local branch to the remote one""" - + tags = cli.Flag("--tags", help = "whether to push tags (default is False)") - + def main(self, remote, branch = "master"): print("pushing to %s/%s..." % (remote, branch)) diff --git a/plumbum/cli/__init__.py b/plumbum/cli/__init__.py index 8ac2b4bb5..7ac0e6e20 100644 --- a/plumbum/cli/__init__.py +++ b/plumbum/cli/__init__.py @@ -1,3 +1,3 @@ from plumbum.cli.switches import SwitchError, switch, autoswitch, SwitchAttr, Flag, CountOf from plumbum.cli.switches import Range, Set, ExistingDirectory, ExistingFile, NonexistentPath, Predicate -from plumbum.cli.application import Application +from plumbum.cli.application import Application, ColorfulApplication diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index bc65be255..258098e67 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -90,6 +90,26 @@ def main(self, src, dst): * ``USAGE`` - the usage line (shown in help) + * ``COLOR_PROGNAME`` - the color to print the name in, defaults to ``CYAN`` + + * ``COLOR_PROGNAME`` - the color to print the discription in, defaults to ``DO_NOTHING`` + + * ``COLOR_VERSION`` - the color to print the version in, defaults to ``DO_NOTHING`` + + * ``COLOR_HEADING`` - the color for headings, can be an attribute, defaults to ``DO_NOTHING`` + + * ``COLOR_USAGE`` - the color for usage, defaults to ``DO_NOTHING`` + + * ``COLOR_SUBCOMMANDS`` - the color for subcommands, defaults to ``DO_NOTHING`` + + * ``COLOR_SWITCHES`` - the color for switches, defaults to ``DO_NOTHING`` + + * ``COLOR_METASWITCHES`` - the color for meta switches, defaults to ``DO_NOTHING`` + + * ``COLOR_GROUPS[]`` - Dictionary for colors for the groups, defaults to empty (no colors) + + * ``COLOR_GROUPS_BODY[]`` - Dictionary for colors for the group bodies, defaults nothing (will default to using COLOR_GROUPS instead)`` + A note on sub-commands: when an application is the root, its ``parent`` attribute is set to ``None``. When it is used as a nested-command, ``parent`` will point to be its direct ancestor. Likewise, when an application is invoked with a sub-command, its ``nested_command`` attribute @@ -101,7 +121,14 @@ def main(self, src, dst): DESCRIPTION = None VERSION = None USAGE = None - NAME_COLOR = COLOR.FG.CYAN + COLOR_PROGNAME = COLOR.CYAN + COLOR_DISCRIPTION = COLOR.DO_NOTHING + COLOR_VERSION = COLOR.DO_NOTHING + COLOR_HEADING = COLOR.DO_NOTHING + COLOR_USAGE = COLOR.DO_NOTHING + COLOR_SUBCOMMANDS = COLOR.DO_NOTHING + COLOR_GROUPS = dict() + COLOR_GROUPS_BODY = COLOR_GROUPS CALL_MAIN_IF_NESTED_COMMAND = True parent = None @@ -475,7 +502,7 @@ def help(self): # @ReservedAssignment self.version() print("") if self.DESCRIPTION: - print(self.DESCRIPTION.strip()) + print(self.COLOR_DISCRIPTION(self.DESCRIPTION.strip() + '\n')) m_args, m_varargs, _, m_defaults = inspect.getargspec(self.main) tailargs = m_args[1:] # skip self @@ -486,13 +513,14 @@ def help(self): # @ReservedAssignment tailargs.append("%s..." % (m_varargs,)) tailargs = " ".join(tailargs) - print("Usage:") - if not self.USAGE: - if self._subcommands: - self.USAGE = " %(progname)s [SWITCHES] [SUBCOMMAND [SWITCHES]] %(tailargs)s\n" - else: - self.USAGE = " %(progname)s [SWITCHES] %(tailargs)s\n" - print(self.USAGE % {"progname": self.PROGNAME, "tailargs": tailargs}) + with self.COLOR_USAGE: + print(self.COLOR_HEADING("Usage:")) + if not self.USAGE: + if self._subcommands: + self.USAGE = " %(progname)s [SWITCHES] [SUBCOMMAND [SWITCHES]] %(tailargs)s\n" + else: + self.USAGE = " %(progname)s [SWITCHES] %(tailargs)s\n" + print(self.USAGE % {"progname": self.PROGNAME, "tailargs": tailargs}) by_groups = {} for si in self._switches_by_func.values(): @@ -503,21 +531,24 @@ def help(self): # @ReservedAssignment def switchs(by_groups, show_groups): for grp, swinfos in sorted(by_groups.items(), key = lambda item: item[0]): if show_groups: - print("%s:" % (grp,)) - - for si in sorted(swinfos, key = lambda si: si.names): - swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names - if n in self._switches_by_name and self._switches_by_name[n] == si) - if si.argtype: - if isinstance(si.argtype, type): - typename = si.argtype.__name__ + with (self.COLOR_HEADING + self.COLOR_GROUPS.get(grp, COLOR.DO_NOTHING)): + print("%s:" % grp) + + # Print in body color unless empty, otherwise group color, otherwise nothing + with self.COLOR_GROUPS_BODY.get(grp, self.COLOR_GROUPS.get(grp, COLOR.DO_NOTHING)): + for si in sorted(swinfos, key = lambda si: si.names): + swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names + if n in self._switches_by_name and self._switches_by_name[n] == si) + if si.argtype: + if isinstance(si.argtype, type): + typename = si.argtype.__name__ + else: + typename = str(si.argtype) + argtype = " %s:%s" % (si.argname.upper(), typename) else: - typename = str(si.argtype) - argtype = " %s:%s" % (si.argname.upper(), typename) - else: - argtype = "" - prefix = swnames + argtype - yield si, prefix + argtype = "" + prefix = swnames + argtype + yield si, prefix if show_groups: print("") @@ -548,20 +579,22 @@ def switchs(by_groups, show_groups): print(description_indent % (prefix, padding, msg)) if self._subcommands: - print("Subcommands:") + with (self.COLOR_HEADING + self.COLOR_SUBCOMMANDS): + print("Subcommands:") for name, subcls in sorted(self._subcommands.items()): - subapp = subcls.get() - doc = subapp.DESCRIPTION if subapp.DESCRIPTION else inspect.getdoc(subapp) - help = doc + "; " if doc else "" # @ReservedAssignment - help += "see '%s %s --help' for more info" % (self.PROGNAME, name) + with self.COLOR_SUBCOMMANDS: + subapp = subcls.get() + doc = subapp.DESCRIPTION if subapp.DESCRIPTION else inspect.getdoc(subapp) + help = doc + "; " if doc else "" # @ReservedAssignment + help += "see '%s %s --help' for more info" % (self.PROGNAME, name) - msg = indentation.join(wrapper.wrap(" ".join(l.strip() for l in help.splitlines()))) + msg = indentation.join(wrapper.wrap(" ".join(l.strip() for l in help.splitlines()))) - if len(name) + wrapper.width >= cols: - padding = indentation - else: - padding = " " * max(cols - wrapper.width - len(name) - 4, 1) - print(description_indent % (name, padding, msg)) + if len(name) + wrapper.width >= cols: + padding = indentation + else: + padding = " " * max(cols - wrapper.width - len(name) - 4, 1) + print(description_indent % (name, padding, msg)) def _get_prog_version(self): ver = None @@ -577,5 +610,22 @@ def _get_prog_version(self): def version(self): """Prints the program's version and quits""" ver = self._get_prog_version() - program_name = self.NAME_COLOR(self.PROGNAME) - print (self.PROGNAME, ver if ver is not None else "(version not set)") + ver_name = self.COLOR_VERSION(ver if ver is not None else "(version not set)") + program_name = self.COLOR_PROGNAME(self.PROGNAME) + print('%s %s' % (program_name, ver_name)) + + + +class ColorfulApplication(Application): + """Application with more colorful defaults for easy color output.""" + COLOR_PROGNAME = COLOR.CYAN + COLOR.BOLD + COLOR_VERSION = COLOR.CYAN + COLOR_DISCRIPTION = COLOR.GREEN + COLOR_HEADING = COLOR.BOLD + COLOR_USAGE = COLOR.RED + COLOR_SUBCOMMANDS = COLOR.YELLOW + COLOR_GROUPS = {'Switches':COLOR.BLUE, + 'Meta-switches':COLOR.MAGENTA, + 'Hidden-switches':COLOR.CYAN} + COLOR_GROUPS_BODY = COLOR_GROUPS + From 6873a76aec4dec27d304f361cf772ae70ae5df3d Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 14 Jul 2015 16:10:42 -0500 Subject: [PATCH 07/80] Removed the CYAN color for program name, as now you can control it or use ColorfulApplication. --- docs/cli.rst | 4 ++-- plumbum/cli/application.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 230d458c2..745735ec8 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -84,8 +84,8 @@ Colors are supported through the class level attributes ``COLOR_GROUPS[]``, and ``COLOR_GROUPS_BODY[]``, which should contain Style objects. The dictionaries support custom colors -for named groups. The default is for only one color, ``COLOR_PROGNAME = COLOR.CYAN`` (to match -previous behavior), but if you just want more colorful defaults, subclass ``cli.ColorfulApplication``. +for named groups. The default is COLOR.DO_NOTHING, but if you just want more +colorful defaults, subclass ``cli.ColorfulApplication``. .. versionadded:: 1.5 diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 258098e67..102249b80 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -90,7 +90,7 @@ def main(self, src, dst): * ``USAGE`` - the usage line (shown in help) - * ``COLOR_PROGNAME`` - the color to print the name in, defaults to ``CYAN`` + * ``COLOR_PROGNAME`` - the color to print the name in, defaults to ``DO_NOTHING`` * ``COLOR_PROGNAME`` - the color to print the discription in, defaults to ``DO_NOTHING`` @@ -121,7 +121,7 @@ def main(self, src, dst): DESCRIPTION = None VERSION = None USAGE = None - COLOR_PROGNAME = COLOR.CYAN + COLOR_PROGNAME = COLOR.DO_NOTHING COLOR_DISCRIPTION = COLOR.DO_NOTHING COLOR_VERSION = COLOR.DO_NOTHING COLOR_HEADING = COLOR.DO_NOTHING From b93306824f50ff2c41172e078d94e50bbd321025 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Tue, 14 Jul 2015 21:58:38 -0500 Subject: [PATCH 08/80] Renamed _COLOR_NAMES to ColorCollection (Since it is a class, not an object) --- docs/color.rst | 21 ++++++++++++++------- docs/local_commands.rst | 2 ++ plumbum/color/color.py | 12 ++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index 00e385fbf..9452e7975 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -6,7 +6,13 @@ Color tools .. versionadded:: 1.5.0 -A built in color module provides quick, clean access to ANSI colors for your scripts. They are +The purpose of the `plumbum.color` library is to make adding +color to your scripts easy and safe. Color is often a great +addition, but not a necessity, and implementing it properly +is tricky. +A built in color module provides quick, clean access to ANSI colors for your scripts. + +Colors are accessed through the ``COLOR`` object, which is a collection of ``Style`` objects. The ``COLOR`` object has the following properties: ``FG`` and ``BG`` @@ -17,7 +23,7 @@ accessed through the ``COLOR`` object, which is a collection of ``Style`` object You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` - All the `ASNI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. + All the `ANSI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. ``RESET`` The global reset will restore all properties at once. ``DO_NOTHING`` @@ -40,7 +46,7 @@ An example of the usage of ``COLOR``:: We could have used the shortcut ``COLOR.GREEN()`` instead. You can also use ``COLOR`` directly as a context manager if you only want the restoring ability, and if you call -``COLOR(...)``, you can manually pass in any ANSI escape sequence. +``COLOR(...)``, you can manually pass in any `ANSI` escape sequence. Further examples of manipulations possible with the library:: @@ -333,11 +339,12 @@ as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#00
  • #eeeeee Grey93
  • -Style object ------------- +The Classes +----------- + +The library works through the ``Style`` class. It is a subclass of ``str`` that adds color related methods. It can be called without arguments to print to `Style.stdout`, and with arguments to wrap a string in the current Style and its negation. -The library works through the Style object. It is a subclass of ``str`` that adds color -related methods. +The colors are generated by two ``ColorCollection`` objects, one for forground and one for background. They are part of the main ``COLOR`` object, which also acts like a ``ColorCollection`` (foreground). See Also diff --git a/docs/local_commands.rst b/docs/local_commands.rst index b2c0794f7..cd700f8fe 100644 --- a/docs/local_commands.rst +++ b/docs/local_commands.rst @@ -154,6 +154,8 @@ objects that can be applied to your command to run it and get or test the retcod If you want to run these commands in the foreground (see `Background and Foreground`_), you can give ``FG=True`` to ``TF`` or ``RETCODE``. For instance, ``cat["non/existing.file"] & TF(1,FG=True)`` + +.. versionadded:: 1.4.3 Run and Popen diff --git a/plumbum/color/color.py b/plumbum/color/color.py index 5d73d2e25..b9f5e8924 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -122,7 +122,7 @@ def _get_style_attribute(attribute, ob): return Style.ansi_color(_simple_attributes[attribute]) -class _COLOR_NAMES(object): +class ColorCollection(object): """This creates color names given a modifier value (FG, BG)""" @@ -182,9 +182,9 @@ def __exit__(self, type, value, traceback): # Adding the color name shortcuts for item in _simple_colors: - setattr(_COLOR_NAMES, item.upper(), property(partial(_get_style_color, item))) + setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item))) -class COLOR(_COLOR_NAMES): +class COLOR(ColorCollection): """Holds font styles, FG and BG objects representing colors, and imitates the FG object to some degree.""" @@ -192,8 +192,8 @@ class COLOR(_COLOR_NAMES): def __init__(self): self.val = 30 # This acts like FG color - self.FG = _COLOR_NAMES(30) - self.BG = _COLOR_NAMES(40) + self.FG = ColorCollection(30) + self.BG = ColorCollection(40) self.DO_NOTHING = Style('') @@ -224,4 +224,4 @@ def with_color(color='', out=None): finally: out.write(Style.ansi_color(0)) -__all__ = ['COLOR', 'with_color', 'Style'] +__all__ = ['COLOR', 'ColorCollection', 'with_color', 'Style'] From b8498e16932622ce6f785e5fe0fb55b8da2f960c Mon Sep 17 00:00:00 2001 From: henryiii Date: Tue, 14 Jul 2015 23:37:43 -0500 Subject: [PATCH 09/80] Update .travis.yml to force classic build method (sudo needed) --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index f359e5aed..f3b18c8d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ +sudo: true language: python python: - "2.6" From a91d95ad7a31dbe48784419d0cc3308b040b7f9c Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 15 Jul 2015 16:49:27 -0500 Subject: [PATCH 10/80] Moved .__contains__ to BaseMachine, added .get, and getattr will work now --- plumbum/commands/processes.py | 2 +- plumbum/machines/base.py | 40 +++++++++++++++++++++++++++++++++++ plumbum/machines/local.py | 15 +++---------- plumbum/machines/remote.py | 14 ++---------- tests/test_local.py | 14 ++++++++++++ tests/test_remote.py | 16 +++++++++----- 6 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 plumbum/machines/base.py diff --git a/plumbum/commands/processes.py b/plumbum/commands/processes.py index a0eb6a675..42ecb7787 100644 --- a/plumbum/commands/processes.py +++ b/plumbum/commands/processes.py @@ -138,7 +138,7 @@ def __init__(self, msg, argv): Exception.__init__(self, msg, argv) self.argv = argv -class CommandNotFound(Exception): +class CommandNotFound(AttributeError): """Raised by :func:`local.which ` and :func:`RemoteMachine.which ` when a command was not found in the system's ``PATH``""" diff --git a/plumbum/machines/base.py b/plumbum/machines/base.py new file mode 100644 index 000000000..ca885ccce --- /dev/null +++ b/plumbum/machines/base.py @@ -0,0 +1,40 @@ +from __future__ import with_statement +from plumbum.commands.processes import CommandNotFound + + +class BaseMachine(object): + """This is a base class for other machines. It contains common code to + all machines in Plumbum.""" + + + def get(self, cmd, *othercommands): + """This works a little like the .get method with dict's, only + it supports an unlimited number of arguments, since later arguments + are tried as commands and could also fail. It + will try to call the first command, and if that is not found, + it will call the next, etc. + + Usage:: + + best_zip = local.get('pigz','gzip') + """ + try: + return self[cmd] + except CommandNotFound: + if othercommands: + return self.get(othercommands[0],*othercommands[1:]) + else: + raise + + def __contains__(self, cmd): + """Tests for the existance of the command, e.g., ``"ls" in plumbum.local``. + ``cmd`` can be anything acceptable by ``__getitem__``. + """ + try: + self[cmd] + except CommandNotFound: + return False + else: + return True + + diff --git a/plumbum/machines/local.py b/plumbum/machines/local.py index c2159c18d..4cdaebff0 100644 --- a/plumbum/machines/local.py +++ b/plumbum/machines/local.py @@ -16,6 +16,7 @@ from plumbum.lib import ProcInfo, IS_WIN32, six from plumbum.commands.daemons import win32_daemonize, posix_daemonize from plumbum.commands.processes import iter_lines +from plumbum.machines.base import BaseMachine from plumbum.machines.env import BaseEnv if sys.version_info >= (3, 2): @@ -111,7 +112,7 @@ def popen(self, args = (), cwd = None, env = None, **kwargs): #=================================================================================================== # Local Machine #=================================================================================================== -class LocalMachine(object): +class LocalMachine(BaseMachine): """The *local machine* (a singleton object). It serves as an entry point to everything related to the local machine, such as working directory and environment manipulation, command creation, etc. @@ -192,6 +193,7 @@ def __getitem__(self, cmd): ls = local["ls"] """ + if isinstance(cmd, LocalPath): return LocalCommand(cmd) elif not isinstance(cmd, RemotePath): @@ -204,17 +206,6 @@ def __getitem__(self, cmd): else: raise TypeError("cmd must not be a RemotePath: %r" % (cmd,)) - def __contains__(self, cmd): - """Tests for the existance of the command, e.g., ``"ls" in plumbum.local``. - ``cmd`` can be anything acceptable by ``__getitem__``. - """ - try: - self[cmd] - except CommandNotFound: - return False - else: - return True - def _popen(self, executable, argv, stdin = PIPE, stdout = PIPE, stderr = PIPE, cwd = None, env = None, new_session = False, **kwargs): if new_session: diff --git a/plumbum/machines/remote.py b/plumbum/machines/remote.py index 011c2d903..ed0019849 100644 --- a/plumbum/machines/remote.py +++ b/plumbum/machines/remote.py @@ -5,6 +5,7 @@ from plumbum.lib import _setdoc, ProcInfo, six from plumbum.machines.local import LocalPath from tempfile import NamedTemporaryFile +from plumbum.machines.base import BaseMachine from plumbum.machines.env import BaseEnv from plumbum.path.remote import RemotePath, RemoteWorkdir, StatRes @@ -127,7 +128,7 @@ def __getattr__(self, name): raise ClosedRemoteMachine("%r has been closed" % (self._obj,)) -class BaseRemoteMachine(object): +class BaseRemoteMachine(BaseMachine): """Represents a *remote machine*; serves as an entry point to everything related to that remote machine, such as working directory and environment manipulation, command creation, etc. @@ -236,17 +237,6 @@ def __getitem__(self, cmd): else: raise TypeError("cmd must not be a LocalPath: %r" % (cmd,)) - def __contains__(self, cmd): - """Tests for the existance of the command, e.g., ``"ls" in remote_machine``. - ``cmd`` can be anything acceptable by ``__getitem__``. - """ - try: - self[cmd] - except CommandNotFound: - return False - else: - return True - @property def python(self): """A command that represents the default remote python interpreter""" diff --git a/tests/test_local.py b/tests/test_local.py index 2244ecf09..fc8408bad 100644 --- a/tests/test_local.py +++ b/tests/test_local.py @@ -110,6 +110,20 @@ def test_imports(self): else: self.fail("from plumbum.cmd import non_exist1N9") + def test_get(self): + self.assertEqual(str(local['ls']),str(local.get('ls'))) + self.assertEqual(str(local['ls']),str(local.get('non_exist1N9', 'ls'))) + self.assertRaises(CommandNotFound, lambda: local.get("non_exist1N9")) + self.assertRaises(CommandNotFound, lambda: local.get("non_exist1N9", "non_exist1N8")) + + def test_getattr(self): + """Testing issue / feature request #193""" + import plumbum as pb + ls_cmd1 = pb.cmd.non_exist1N9 if hasattr(pb.cmd, 'non_exist1N9') else pb.cmd.ls + ls_cmd2 = getattr(pb.cmd, 'non_exist1N9', pb.cmd.ls) + self.assertEqual(str(ls_cmd1), str(local['ls'])) + self.assertEqual(str(ls_cmd2), str(local['ls'])) + def test_cwd(self): from plumbum.cmd import ls self.assertEqual(local.cwd, os.getcwd()) diff --git a/tests/test_remote.py b/tests/test_remote.py index d614ef5f2..2f9273f27 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -33,7 +33,6 @@ def wrapper(*args, **kwargs): class RemotePathTest(unittest.TestCase): def _connect(self): return SshMachine(TEST_HOST) - def test_basename(self): name = RemotePath(self._connect(), "/some/long/path/to/file.txt").basename @@ -44,7 +43,7 @@ def test_dirname(self): name = RemotePath(self._connect(), "/some/long/path/to/file.txt").dirname self.assertTrue(isinstance(name, RemotePath)) self.assertEqual("/some/long/path/to", str(name)) - + def test_suffix(self): p1 = RemotePath(self._connect(), "/some/long/path/to/file.txt") p2 = RemotePath(self._connect(), "file.tar.gz") @@ -58,7 +57,7 @@ def test_suffix(self): strcmp(p2.with_suffix(".other", 2), RemotePath(self._connect(), "file.other")) strcmp(p2.with_suffix(".other", 0), RemotePath(self._connect(), "file.tar.gz.other")) strcmp(p2.with_suffix(".other", None), RemotePath(self._connect(), "file.other")) - + def test_newname(self): p1 = RemotePath(self._connect(), "/some/long/path/to/file.txt") p2 = RemotePath(self._connect(), "file.tar.gz") @@ -212,6 +211,13 @@ def test_tunnel(self): p.communicate() + def test_get(self): + with self._connect() as rem: + self.assertEqual(str(rem['ls']),str(rem.get('ls'))) + self.assertEqual(str(rem['ls']),str(rem.get('not_a_valid_process_234','ls'))) + self.assertTrue('ls' in rem) + self.assertFalse('not_a_valid_process_234' in rem) + def test_list_processes(self): with self._connect() as rem: self.assertTrue(list(rem.list_processes())) @@ -266,7 +272,7 @@ def test_sshpass(self): class TestParamikoMachine(unittest.TestCase, BaseRemoteMachineTest): def _connect(self): return ParamikoMachine(TEST_HOST, missing_host_policy = paramiko.AutoAddPolicy()) - + def test_tunnel(self): with self._connect() as rem: p = rem.python["-c", self.TUNNEL_PROG].popen() @@ -275,7 +281,7 @@ def test_tunnel(self): except ValueError: print(p.communicate()) raise - + s = rem.connect_sock(port) s.send(six.b("world")) data = s.recv(100) From f3797e904ec35543090934d3bbcfb225ed2612b5 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 15 Jul 2015 17:29:52 -0500 Subject: [PATCH 11/80] Moved colors to separate file, added the function I used to generate them. Color API docstrings. --- docs/_color_list.html | 258 ++++++++++++++++++++++++++++++++++++++++ docs/api/color.rst | 3 +- docs/color.rst | 262 +---------------------------------------- plumbum/color/color.py | 10 +- plumbum/color/names.py | 12 +- 5 files changed, 278 insertions(+), 267 deletions(-) create mode 100644 docs/_color_list.html diff --git a/docs/_color_list.html b/docs/_color_list.html new file mode 100644 index 000000000..d4b399afe --- /dev/null +++ b/docs/_color_list.html @@ -0,0 +1,258 @@ +
      +
    1. #000000 Black
    2. +
    3. #800000 Red
    4. +
    5. #008000 Green
    6. +
    7. #808000 Yellow
    8. +
    9. #000080 Blue
    10. +
    11. #800080 Magenta
    12. +
    13. #008080 Cyan
    14. +
    15. #c0c0c0 LightGray
    16. +
    17. #808080 DarkGray
    18. +
    19. #ff0000 LightRed
    20. +
    21. #00ff00 LightGreen
    22. +
    23. #ffff00 LightYellow
    24. +
    25. #0000ff LightBlue
    26. +
    27. #ff00ff LightMagenta
    28. +
    29. #00ffff LightCyan
    30. +
    31. #ffffff White
    32. +
    33. #000000 Grey0
    34. +
    35. #00005f NavyBlue
    36. +
    37. #000087 DarkBlue
    38. +
    39. #0000af Blue3
    40. +
    41. #0000d7 Blue3
    42. +
    43. #0000ff Blue1
    44. +
    45. #005f00 DarkGreen
    46. +
    47. #005f5f DeepSkyBlue4
    48. +
    49. #005f87 DeepSkyBlue4
    50. +
    51. #005faf DeepSkyBlue4
    52. +
    53. #005fd7 DodgerBlue3
    54. +
    55. #005fff DodgerBlue2
    56. +
    57. #008700 Green4
    58. +
    59. #00875f SpringGreen4
    60. +
    61. #008787 Turquoise4
    62. +
    63. #0087af DeepSkyBlue3
    64. +
    65. #0087d7 DeepSkyBlue3
    66. +
    67. #0087ff DodgerBlue1
    68. +
    69. #00af00 Green3
    70. +
    71. #00af5f SpringGreen3
    72. +
    73. #00af87 DarkCyan
    74. +
    75. #00afaf LightSeaGreen
    76. +
    77. #00afd7 DeepSkyBlue2
    78. +
    79. #00afff DeepSkyBlue1
    80. +
    81. #00d700 Green3
    82. +
    83. #00d75f SpringGreen3
    84. +
    85. #00d787 SpringGreen2
    86. +
    87. #00d7af Cyan3
    88. +
    89. #00d7d7 DarkTurquoise
    90. +
    91. #00d7ff Turquoise2
    92. +
    93. #00ff00 Green1
    94. +
    95. #00ff5f SpringGreen2
    96. +
    97. #00ff87 SpringGreen1
    98. +
    99. #00ffaf MediumSpringGreen
    100. +
    101. #00ffd7 Cyan2
    102. +
    103. #00ffff Cyan1
    104. +
    105. #5f0000 DarkRed
    106. +
    107. #5f005f DeepPink4
    108. +
    109. #5f0087 Purple4
    110. +
    111. #5f00af Purple4
    112. +
    113. #5f00d7 Purple3
    114. +
    115. #5f00ff BlueViolet
    116. +
    117. #5f5f00 Orange4
    118. +
    119. #5f5f5f Grey37
    120. +
    121. #5f5f87 MediumPurple4
    122. +
    123. #5f5faf SlateBlue3
    124. +
    125. #5f5fd7 SlateBlue3
    126. +
    127. #5f5fff RoyalBlue1
    128. +
    129. #5f8700 Chartreuse4
    130. +
    131. #5f875f DarkSeaGreen4
    132. +
    133. #5f8787 PaleTurquoise4
    134. +
    135. #5f87af SteelBlue
    136. +
    137. #5f87d7 SteelBlue3
    138. +
    139. #5f87ff CornflowerBlue
    140. +
    141. #5faf00 Chartreuse3
    142. +
    143. #5faf5f DarkSeaGreen4
    144. +
    145. #5faf87 CadetBlue
    146. +
    147. #5fafaf CadetBlue
    148. +
    149. #5fafd7 SkyBlue3
    150. +
    151. #5fafff SteelBlue1
    152. +
    153. #5fd700 Chartreuse3
    154. +
    155. #5fd75f PaleGreen3
    156. +
    157. #5fd787 SeaGreen3
    158. +
    159. #5fd7af Aquamarine3
    160. +
    161. #5fd7d7 MediumTurquoise
    162. +
    163. #5fd7ff SteelBlue1
    164. +
    165. #5fff00 Chartreuse2
    166. +
    167. #5fff5f SeaGreen2
    168. +
    169. #5fff87 SeaGreen1
    170. +
    171. #5fffaf SeaGreen1
    172. +
    173. #5fffd7 Aquamarine1
    174. +
    175. #5fffff DarkSlateGray2
    176. +
    177. #870000 DarkRed
    178. +
    179. #87005f DeepPink4
    180. +
    181. #870087 DarkMagenta
    182. +
    183. #8700af DarkMagenta
    184. +
    185. #8700d7 DarkViolet
    186. +
    187. #8700ff Purple
    188. +
    189. #875f00 Orange4
    190. +
    191. #875f5f LightPink4
    192. +
    193. #875f87 Plum4
    194. +
    195. #875faf MediumPurple3
    196. +
    197. #875fd7 MediumPurple3
    198. +
    199. #875fff SlateBlue1
    200. +
    201. #878700 Yellow4
    202. +
    203. #87875f Wheat4
    204. +
    205. #878787 Grey53
    206. +
    207. #8787af LightSlateGrey
    208. +
    209. #8787d7 MediumPurple
    210. +
    211. #8787ff LightSlateBlue
    212. +
    213. #87af00 Yellow4
    214. +
    215. #87af5f DarkOliveGreen3
    216. +
    217. #87af87 DarkSeaGreen
    218. +
    219. #87afaf LightSkyBlue3
    220. +
    221. #87afd7 LightSkyBlue3
    222. +
    223. #87afff SkyBlue2
    224. +
    225. #87d700 Chartreuse2
    226. +
    227. #87d75f DarkOliveGreen3
    228. +
    229. #87d787 PaleGreen3
    230. +
    231. #87d7af DarkSeaGreen3
    232. +
    233. #87d7d7 DarkSlateGray3
    234. +
    235. #87d7ff SkyBlue1
    236. +
    237. #87ff00 Chartreuse1
    238. +
    239. #87ff5f LightGreen
    240. +
    241. #87ff87 LightGreen
    242. +
    243. #87ffaf PaleGreen1
    244. +
    245. #87ffd7 Aquamarine1
    246. +
    247. #87ffff DarkSlateGray1
    248. +
    249. #af0000 Red3
    250. +
    251. #af005f DeepPink4
    252. +
    253. #af0087 MediumVioletRed
    254. +
    255. #af00af Magenta3
    256. +
    257. #af00d7 DarkViolet
    258. +
    259. #af00ff Purple
    260. +
    261. #af5f00 DarkOrange3
    262. +
    263. #af5f5f IndianRed
    264. +
    265. #af5f87 HotPink3
    266. +
    267. #af5faf MediumOrchid3
    268. +
    269. #af5fd7 MediumOrchid
    270. +
    271. #af5fff MediumPurple2
    272. +
    273. #af8700 DarkGoldenrod
    274. +
    275. #af875f LightSalmon3
    276. +
    277. #af8787 RosyBrown
    278. +
    279. #af87af Grey63
    280. +
    281. #af87d7 MediumPurple2
    282. +
    283. #af87ff MediumPurple1
    284. +
    285. #afaf00 Gold3
    286. +
    287. #afaf5f DarkKhaki
    288. +
    289. #afaf87 NavajoWhite3
    290. +
    291. #afafaf Grey69
    292. +
    293. #afafd7 LightSteelBlue3
    294. +
    295. #afafff LightSteelBlue
    296. +
    297. #afd700 Yellow3
    298. +
    299. #afd75f DarkOliveGreen3
    300. +
    301. #afd787 DarkSeaGreen3
    302. +
    303. #afd7af DarkSeaGreen2
    304. +
    305. #afd7d7 LightCyan3
    306. +
    307. #afd7ff LightSkyBlue1
    308. +
    309. #afff00 GreenYellow
    310. +
    311. #afff5f DarkOliveGreen2
    312. +
    313. #afff87 PaleGreen1
    314. +
    315. #afffaf DarkSeaGreen2
    316. +
    317. #afffd7 DarkSeaGreen1
    318. +
    319. #afffff PaleTurquoise1
    320. +
    321. #d70000 Red3
    322. +
    323. #d7005f DeepPink3
    324. +
    325. #d70087 DeepPink3
    326. +
    327. #d700af Magenta3
    328. +
    329. #d700d7 Magenta3
    330. +
    331. #d700ff Magenta2
    332. +
    333. #d75f00 DarkOrange3
    334. +
    335. #d75f5f IndianRed
    336. +
    337. #d75f87 HotPink3
    338. +
    339. #d75faf HotPink2
    340. +
    341. #d75fd7 Orchid
    342. +
    343. #d75fff MediumOrchid1
    344. +
    345. #d78700 Orange3
    346. +
    347. #d7875f LightSalmon3
    348. +
    349. #d78787 LightPink3
    350. +
    351. #d787af Pink3
    352. +
    353. #d787d7 Plum3
    354. +
    355. #d787ff Violet
    356. +
    357. #d7af00 Gold3
    358. +
    359. #d7af5f LightGoldenrod3
    360. +
    361. #d7af87 Tan
    362. +
    363. #d7afaf MistyRose3
    364. +
    365. #d7afd7 Thistle3
    366. +
    367. #d7afff Plum2
    368. +
    369. #d7d700 Yellow3
    370. +
    371. #d7d75f Khaki3
    372. +
    373. #d7d787 LightGoldenrod2
    374. +
    375. #d7d7af LightYellow3
    376. +
    377. #d7d7d7 Grey84
    378. +
    379. #d7d7ff LightSteelBlue1
    380. +
    381. #d7ff00 Yellow2
    382. +
    383. #d7ff5f DarkOliveGreen1
    384. +
    385. #d7ff87 DarkOliveGreen1
    386. +
    387. #d7ffaf DarkSeaGreen1
    388. +
    389. #d7ffd7 Honeydew2
    390. +
    391. #d7ffff LightCyan1
    392. +
    393. #ff0000 Red1
    394. +
    395. #ff005f DeepPink2
    396. +
    397. #ff0087 DeepPink1
    398. +
    399. #ff00af DeepPink1
    400. +
    401. #ff00d7 Magenta2
    402. +
    403. #ff00ff Magenta1
    404. +
    405. #ff5f00 OrangeRed1
    406. +
    407. #ff5f5f IndianRed1
    408. +
    409. #ff5f87 IndianRed1
    410. +
    411. #ff5faf HotPink
    412. +
    413. #ff5fd7 HotPink
    414. +
    415. #ff5fff MediumOrchid1
    416. +
    417. #ff8700 DarkOrange
    418. +
    419. #ff875f Salmon1
    420. +
    421. #ff8787 LightCoral
    422. +
    423. #ff87af PaleVioletRed1
    424. +
    425. #ff87d7 Orchid2
    426. +
    427. #ff87ff Orchid1
    428. +
    429. #ffaf00 Orange1
    430. +
    431. #ffaf5f SandyBrown
    432. +
    433. #ffaf87 LightSalmon1
    434. +
    435. #ffafaf LightPink1
    436. +
    437. #ffafd7 Pink1
    438. +
    439. #ffafff Plum1
    440. +
    441. #ffd700 Gold1
    442. +
    443. #ffd75f LightGoldenrod2
    444. +
    445. #ffd787 LightGoldenrod2
    446. +
    447. #ffd7af NavajoWhite1
    448. +
    449. #ffd7d7 MistyRose1
    450. +
    451. #ffd7ff Thistle1
    452. +
    453. #ffff00 Yellow1
    454. +
    455. #ffff5f LightGoldenrod1
    456. +
    457. #ffff87 Khaki1
    458. +
    459. #ffffaf Wheat1
    460. +
    461. #ffffd7 Cornsilk1
    462. +
    463. #ffffff Grey100
    464. +
    465. #080808 Grey3
    466. +
    467. #121212 Grey7
    468. +
    469. #1c1c1c Grey11
    470. +
    471. #262626 Grey15
    472. +
    473. #303030 Grey19
    474. +
    475. #3a3a3a Grey23
    476. +
    477. #444444 Grey27
    478. +
    479. #4e4e4e Grey30
    480. +
    481. #585858 Grey35
    482. +
    483. #626262 Grey39
    484. +
    485. #6c6c6c Grey42
    486. +
    487. #767676 Grey46
    488. +
    489. #808080 Grey50
    490. +
    491. #8a8a8a Grey54
    492. +
    493. #949494 Grey58
    494. +
    495. #9e9e9e Grey62
    496. +
    497. #a8a8a8 Grey66
    498. +
    499. #b2b2b2 Grey70
    500. +
    501. #bcbcbc Grey74
    502. +
    503. #c6c6c6 Grey78
    504. +
    505. #d0d0d0 Grey82
    506. +
    507. #dadada Grey85
    508. +
    509. #e4e4e4 Grey89
    510. +
    511. #eeeeee Grey93
    512. +
    diff --git a/docs/api/color.rst b/docs/api/color.rst index 34f5b4b44..32da0bd6f 100644 --- a/docs/api/color.rst +++ b/docs/api/color.rst @@ -1,5 +1,6 @@ Package plumbum.color -=================== +===================== + .. automodule:: plumbum.color.color :members: diff --git a/docs/color.rst b/docs/color.rst index 9452e7975..936afd192 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -76,268 +76,10 @@ The name of a script is automatically colored with the ``.COLOR_NAME`` property. ----------------- The library support 256 colors through numbers, names or HEX html codes. You can access them -as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#0000FF']. The supported colors are +as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#0000FF']. The supported colors are: .. raw:: html - -
      -
    1. #000000 Black
    2. -
    3. #800000 Red
    4. -
    5. #008000 Green
    6. -
    7. #808000 Yellow
    8. -
    9. #000080 Blue
    10. -
    11. #800080 Magenta
    12. -
    13. #008080 Cyan
    14. -
    15. #c0c0c0 LightGray
    16. -
    17. #808080 DarkGray
    18. -
    19. #ff0000 LightRed
    20. -
    21. #00ff00 LightGreen
    22. -
    23. #ffff00 LightYellow
    24. -
    25. #0000ff LightBlue
    26. -
    27. #ff00ff LightMagenta
    28. -
    29. #00ffff LightCyan
    30. -
    31. #ffffff White
    32. -
    33. #000000 Grey0
    34. -
    35. #00005f NavyBlue
    36. -
    37. #000087 DarkBlue
    38. -
    39. #0000af Blue3
    40. -
    41. #0000d7 Blue3
    42. -
    43. #0000ff Blue1
    44. -
    45. #005f00 DarkGreen
    46. -
    47. #005f5f DeepSkyBlue4
    48. -
    49. #005f87 DeepSkyBlue4
    50. -
    51. #005faf DeepSkyBlue4
    52. -
    53. #005fd7 DodgerBlue3
    54. -
    55. #005fff DodgerBlue2
    56. -
    57. #008700 Green4
    58. -
    59. #00875f SpringGreen4
    60. -
    61. #008787 Turquoise4
    62. -
    63. #0087af DeepSkyBlue3
    64. -
    65. #0087d7 DeepSkyBlue3
    66. -
    67. #0087ff DodgerBlue1
    68. -
    69. #00af00 Green3
    70. -
    71. #00af5f SpringGreen3
    72. -
    73. #00af87 DarkCyan
    74. -
    75. #00afaf LightSeaGreen
    76. -
    77. #00afd7 DeepSkyBlue2
    78. -
    79. #00afff DeepSkyBlue1
    80. -
    81. #00d700 Green3
    82. -
    83. #00d75f SpringGreen3
    84. -
    85. #00d787 SpringGreen2
    86. -
    87. #00d7af Cyan3
    88. -
    89. #00d7d7 DarkTurquoise
    90. -
    91. #00d7ff Turquoise2
    92. -
    93. #00ff00 Green1
    94. -
    95. #00ff5f SpringGreen2
    96. -
    97. #00ff87 SpringGreen1
    98. -
    99. #00ffaf MediumSpringGreen
    100. -
    101. #00ffd7 Cyan2
    102. -
    103. #00ffff Cyan1
    104. -
    105. #5f0000 DarkRed
    106. -
    107. #5f005f DeepPink4
    108. -
    109. #5f0087 Purple4
    110. -
    111. #5f00af Purple4
    112. -
    113. #5f00d7 Purple3
    114. -
    115. #5f00ff BlueViolet
    116. -
    117. #5f5f00 Orange4
    118. -
    119. #5f5f5f Grey37
    120. -
    121. #5f5f87 MediumPurple4
    122. -
    123. #5f5faf SlateBlue3
    124. -
    125. #5f5fd7 SlateBlue3
    126. -
    127. #5f5fff RoyalBlue1
    128. -
    129. #5f8700 Chartreuse4
    130. -
    131. #5f875f DarkSeaGreen4
    132. -
    133. #5f8787 PaleTurquoise4
    134. -
    135. #5f87af SteelBlue
    136. -
    137. #5f87d7 SteelBlue3
    138. -
    139. #5f87ff CornflowerBlue
    140. -
    141. #5faf00 Chartreuse3
    142. -
    143. #5faf5f DarkSeaGreen4
    144. -
    145. #5faf87 CadetBlue
    146. -
    147. #5fafaf CadetBlue
    148. -
    149. #5fafd7 SkyBlue3
    150. -
    151. #5fafff SteelBlue1
    152. -
    153. #5fd700 Chartreuse3
    154. -
    155. #5fd75f PaleGreen3
    156. -
    157. #5fd787 SeaGreen3
    158. -
    159. #5fd7af Aquamarine3
    160. -
    161. #5fd7d7 MediumTurquoise
    162. -
    163. #5fd7ff SteelBlue1
    164. -
    165. #5fff00 Chartreuse2
    166. -
    167. #5fff5f SeaGreen2
    168. -
    169. #5fff87 SeaGreen1
    170. -
    171. #5fffaf SeaGreen1
    172. -
    173. #5fffd7 Aquamarine1
    174. -
    175. #5fffff DarkSlateGray2
    176. -
    177. #870000 DarkRed
    178. -
    179. #87005f DeepPink4
    180. -
    181. #870087 DarkMagenta
    182. -
    183. #8700af DarkMagenta
    184. -
    185. #8700d7 DarkViolet
    186. -
    187. #8700ff Purple
    188. -
    189. #875f00 Orange4
    190. -
    191. #875f5f LightPink4
    192. -
    193. #875f87 Plum4
    194. -
    195. #875faf MediumPurple3
    196. -
    197. #875fd7 MediumPurple3
    198. -
    199. #875fff SlateBlue1
    200. -
    201. #878700 Yellow4
    202. -
    203. #87875f Wheat4
    204. -
    205. #878787 Grey53
    206. -
    207. #8787af LightSlateGrey
    208. -
    209. #8787d7 MediumPurple
    210. -
    211. #8787ff LightSlateBlue
    212. -
    213. #87af00 Yellow4
    214. -
    215. #87af5f DarkOliveGreen3
    216. -
    217. #87af87 DarkSeaGreen
    218. -
    219. #87afaf LightSkyBlue3
    220. -
    221. #87afd7 LightSkyBlue3
    222. -
    223. #87afff SkyBlue2
    224. -
    225. #87d700 Chartreuse2
    226. -
    227. #87d75f DarkOliveGreen3
    228. -
    229. #87d787 PaleGreen3
    230. -
    231. #87d7af DarkSeaGreen3
    232. -
    233. #87d7d7 DarkSlateGray3
    234. -
    235. #87d7ff SkyBlue1
    236. -
    237. #87ff00 Chartreuse1
    238. -
    239. #87ff5f LightGreen
    240. -
    241. #87ff87 LightGreen
    242. -
    243. #87ffaf PaleGreen1
    244. -
    245. #87ffd7 Aquamarine1
    246. -
    247. #87ffff DarkSlateGray1
    248. -
    249. #af0000 Red3
    250. -
    251. #af005f DeepPink4
    252. -
    253. #af0087 MediumVioletRed
    254. -
    255. #af00af Magenta3
    256. -
    257. #af00d7 DarkViolet
    258. -
    259. #af00ff Purple
    260. -
    261. #af5f00 DarkOrange3
    262. -
    263. #af5f5f IndianRed
    264. -
    265. #af5f87 HotPink3
    266. -
    267. #af5faf MediumOrchid3
    268. -
    269. #af5fd7 MediumOrchid
    270. -
    271. #af5fff MediumPurple2
    272. -
    273. #af8700 DarkGoldenrod
    274. -
    275. #af875f LightSalmon3
    276. -
    277. #af8787 RosyBrown
    278. -
    279. #af87af Grey63
    280. -
    281. #af87d7 MediumPurple2
    282. -
    283. #af87ff MediumPurple1
    284. -
    285. #afaf00 Gold3
    286. -
    287. #afaf5f DarkKhaki
    288. -
    289. #afaf87 NavajoWhite3
    290. -
    291. #afafaf Grey69
    292. -
    293. #afafd7 LightSteelBlue3
    294. -
    295. #afafff LightSteelBlue
    296. -
    297. #afd700 Yellow3
    298. -
    299. #afd75f DarkOliveGreen3
    300. -
    301. #afd787 DarkSeaGreen3
    302. -
    303. #afd7af DarkSeaGreen2
    304. -
    305. #afd7d7 LightCyan3
    306. -
    307. #afd7ff LightSkyBlue1
    308. -
    309. #afff00 GreenYellow
    310. -
    311. #afff5f DarkOliveGreen2
    312. -
    313. #afff87 PaleGreen1
    314. -
    315. #afffaf DarkSeaGreen2
    316. -
    317. #afffd7 DarkSeaGreen1
    318. -
    319. #afffff PaleTurquoise1
    320. -
    321. #d70000 Red3
    322. -
    323. #d7005f DeepPink3
    324. -
    325. #d70087 DeepPink3
    326. -
    327. #d700af Magenta3
    328. -
    329. #d700d7 Magenta3
    330. -
    331. #d700ff Magenta2
    332. -
    333. #d75f00 DarkOrange3
    334. -
    335. #d75f5f IndianRed
    336. -
    337. #d75f87 HotPink3
    338. -
    339. #d75faf HotPink2
    340. -
    341. #d75fd7 Orchid
    342. -
    343. #d75fff MediumOrchid1
    344. -
    345. #d78700 Orange3
    346. -
    347. #d7875f LightSalmon3
    348. -
    349. #d78787 LightPink3
    350. -
    351. #d787af Pink3
    352. -
    353. #d787d7 Plum3
    354. -
    355. #d787ff Violet
    356. -
    357. #d7af00 Gold3
    358. -
    359. #d7af5f LightGoldenrod3
    360. -
    361. #d7af87 Tan
    362. -
    363. #d7afaf MistyRose3
    364. -
    365. #d7afd7 Thistle3
    366. -
    367. #d7afff Plum2
    368. -
    369. #d7d700 Yellow3
    370. -
    371. #d7d75f Khaki3
    372. -
    373. #d7d787 LightGoldenrod2
    374. -
    375. #d7d7af LightYellow3
    376. -
    377. #d7d7d7 Grey84
    378. -
    379. #d7d7ff LightSteelBlue1
    380. -
    381. #d7ff00 Yellow2
    382. -
    383. #d7ff5f DarkOliveGreen1
    384. -
    385. #d7ff87 DarkOliveGreen1
    386. -
    387. #d7ffaf DarkSeaGreen1
    388. -
    389. #d7ffd7 Honeydew2
    390. -
    391. #d7ffff LightCyan1
    392. -
    393. #ff0000 Red1
    394. -
    395. #ff005f DeepPink2
    396. -
    397. #ff0087 DeepPink1
    398. -
    399. #ff00af DeepPink1
    400. -
    401. #ff00d7 Magenta2
    402. -
    403. #ff00ff Magenta1
    404. -
    405. #ff5f00 OrangeRed1
    406. -
    407. #ff5f5f IndianRed1
    408. -
    409. #ff5f87 IndianRed1
    410. -
    411. #ff5faf HotPink
    412. -
    413. #ff5fd7 HotPink
    414. -
    415. #ff5fff MediumOrchid1
    416. -
    417. #ff8700 DarkOrange
    418. -
    419. #ff875f Salmon1
    420. -
    421. #ff8787 LightCoral
    422. -
    423. #ff87af PaleVioletRed1
    424. -
    425. #ff87d7 Orchid2
    426. -
    427. #ff87ff Orchid1
    428. -
    429. #ffaf00 Orange1
    430. -
    431. #ffaf5f SandyBrown
    432. -
    433. #ffaf87 LightSalmon1
    434. -
    435. #ffafaf LightPink1
    436. -
    437. #ffafd7 Pink1
    438. -
    439. #ffafff Plum1
    440. -
    441. #ffd700 Gold1
    442. -
    443. #ffd75f LightGoldenrod2
    444. -
    445. #ffd787 LightGoldenrod2
    446. -
    447. #ffd7af NavajoWhite1
    448. -
    449. #ffd7d7 MistyRose1
    450. -
    451. #ffd7ff Thistle1
    452. -
    453. #ffff00 Yellow1
    454. -
    455. #ffff5f LightGoldenrod1
    456. -
    457. #ffff87 Khaki1
    458. -
    459. #ffffaf Wheat1
    460. -
    461. #ffffd7 Cornsilk1
    462. -
    463. #ffffff Grey100
    464. -
    465. #080808 Grey3
    466. -
    467. #121212 Grey7
    468. -
    469. #1c1c1c Grey11
    470. -
    471. #262626 Grey15
    472. -
    473. #303030 Grey19
    474. -
    475. #3a3a3a Grey23
    476. -
    477. #444444 Grey27
    478. -
    479. #4e4e4e Grey30
    480. -
    481. #585858 Grey35
    482. -
    483. #626262 Grey39
    484. -
    485. #6c6c6c Grey42
    486. -
    487. #767676 Grey46
    488. -
    489. #808080 Grey50
    490. -
    491. #8a8a8a Grey54
    492. -
    493. #949494 Grey58
    494. -
    495. #9e9e9e Grey62
    496. -
    497. #a8a8a8 Grey66
    498. -
    499. #b2b2b2 Grey70
    500. -
    501. #bcbcbc Grey74
    502. -
    503. #c6c6c6 Grey78
    504. -
    505. #d0d0d0 Grey82
    506. -
    507. #dadada Grey85
    508. -
    509. #e4e4e4 Grey89
    510. -
    511. #eeeeee Grey93
    512. -
    + :file: _color_list.html The Classes ----------- diff --git a/plumbum/color/color.py b/plumbum/color/color.py index b9f5e8924..c7854cc3f 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -182,11 +182,11 @@ def __exit__(self, type, value, traceback): # Adding the color name shortcuts for item in _simple_colors: - setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item))) + setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item), doc='Shortcut for '+item)) -class COLOR(ColorCollection): +class Colors(ColorCollection): - """Holds font styles, FG and BG objects representing colors, and + """Singleton. Holds font styles, FG and BG objects representing colors, and imitates the FG object to some degree.""" def __init__(self): @@ -203,10 +203,10 @@ def __call__(self, color): for item in _simple_attributes: - setattr(COLOR, item.upper(), property(partial(_get_style_attribute, item))) + setattr(Colors, item.upper(), property(partial(_get_style_attribute, item), doc='Shortcut for '+item)) -COLOR = COLOR() +COLOR = Colors() @contextmanager diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 6e64db469..cd5a3bf1b 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -6,7 +6,6 @@ rgb values with r=int(html[n][1:3],16), etc. ''' - _named_colors = '''\ 0,black,#000000 1,red,#800000 @@ -292,3 +291,14 @@ hidden=8 ) +def print_html_table(): + """Prints html names for documentation""" + print(r'
      ') + for i in range(256): + name = camel_names[i] + val = html[i] + print(r'
    1. ' + val + + r' ' + name + + r'
    2. ') + print(r'
    ') From 2d75bc02b41c048687681afa528c9425713bdb3d Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Thu, 16 Jul 2015 15:53:25 -0500 Subject: [PATCH 12/80] Adding new colors backend (not finished yet) --- plumbum/color/base.py | 209 +++++++++++++++++++++++++++++++++++++++ plumbum/color/color.py | 19 ++-- plumbum/color/names.py | 69 +++++++++---- tests/test_basecolors.py | 71 +++++++++++++ 4 files changed, 344 insertions(+), 24 deletions(-) create mode 100644 plumbum/color/base.py create mode 100644 tests/test_basecolors.py diff --git a/plumbum/color/base.py b/plumbum/color/base.py new file mode 100644 index 000000000..9f1e8ba3a --- /dev/null +++ b/plumbum/color/base.py @@ -0,0 +1,209 @@ +""" +Color-related utilities. Feel free to use any color library on +the ``with_color`` statement. + +The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, +and attributes like bold and +underlined text. It also provides ``RESET`` to recover the normal font. + +With the ``Style`` class, any color can be +directly called or given to a with statement. +""" + +from __future__ import print_function +import sys +import os +from contextlib import contextmanager +from plumbum.color.names import color_names_full, color_html_full +from plumbum.color.names import color_names_simple, color_html_simple, attributes_simple, from_html +from plumbum.color.names import find_nearest_color, find_nearest_simple_color +from functools import partial + +_lower_camel_names = [n.replace('_', '') for n in color_names_full] + +class ColorNotFound(AttributeError): + pass + +class BaseColor(object): + """This class stores the idea of a color, rather than a specific implementation. + It provides as many different representations as possible, and can be subclassed. + It is not meant to be mutible. .from_any provides a quick-init shortcut. + + Possible colors:: + + blue = ColorBase(0,0,255) # Red, Green, Blue + green = ColorBase.from_full("green") # Case insensitive name, from large colorset + red = ColorBase.from_full(1) # Color number + white = ColorBase.from_html("#FFFFFF") # HTML supported + yellow = ColorBase.from_simple("red") # Simple colorset + + + The attributes are: + self.fg: Foreground if True, background if not + self.reset: True it this is a reset color (following atts don't matter if True) + self.rgb: The red/green/blue tuple for this color + self.simple: Simple 6 color mode + self.number: The color number given the mode, closest to rgb if not exact + + """ + + use_color = sys.stdout.isatty() and os.name == "posix" + stdout = sys.stdout + + def __init__(self, r_or_color=None, g=None, b=None, fg=True): + """This only init's from color values, or tries to load non-simple ones.""" + + self.fg = fg + self.reset = True # Starts as reset color + self.rgb = (0,0,0) + self.simple = False + + if r_or_color is not None and None in (g,b): + try: + self._from_full(r_or_color) + except ColorNotFound: + self._from_html(r_or_color) + + + elif None not in (r_or_color, g, b): + self.rgb = (r_or_color,g,b) + self._init_number() + + def _init_number(self): + """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" + self.number = find_nearest_simple_color(*self.rgb) if self.simple else find_nearest_color(*self.rgb) + self.reset = False + + @classmethod + def from_simple(cls, color, fg=True): + """Creates a color from simple name or color number""" + self = cls(fg=fg) + self._from_simple(color) + return self + + def _from_simple(self, color): + """Internal loader for from_simple.""" + try: + color = color.lower() + except AttributeError: + pass + + if color == 'reset' or color==9: + return + + elif color in color_names_simple: + self.rgb = from_html(color_html_simple[color_names_simple.index(color)]) + self.simple = True + + elif isinstance(color, int) and 0 <= color <= 7: + self.rgb = from_html(color_html_simple[color]) + self.simple = True + + else: + raise ColorNotFound("Did not find color: " + repr(color)) + + self._init_number() + + @classmethod + def from_full(cls, color, fg=True): + """Creates a color from full name or color number""" + self = cls(fg=fg) + self._from_full(color) + return self + + def _from_full(self, color): + """Creates a color from full name or color number""" + try: + color = color.lower() + color = color.replace(' ','_') + except AttributeError: + pass + + if color == 'reset': + return + + elif color in color_names_full: + self.rgb = from_html(color_html_full[color_names_full.index(color)]) + + elif color in _lower_camel_names: + self.rgb = from_html(color_html_full[_lower_camel_names.index(color)]) + + elif isinstance(color, int) and 0 <= color <= 255: + self.rgb = from_html(color_html_full[color]) + + else: + raise ColorNotFound("Did not find color: " + repr(color)) + + self._init_number() + + @classmethod + def from_html(cls, color, fg=True): + """Converts #123456 values to colors.""" + + self = cls(fg=fg) + self._from_html(color) + return self + + def _from_html(self, color): + try: + self.rgb = from_html(color) + except (TypeError, ValueError): + raise ColorNotFound("Did not find htmlcode: " + repr(color)) + + self._init_number() + + + @property + def r(self): + return self.rgb[0] + + @property + def g(self): + return self.rgb[1] + + @property + def b(self): + return self.rgb[2] + + @property + def name(self): + if self.reset: + return 'reset' + elif self.simple: + return color_names_simple[self.number] + else: + color_names_full[self.number] + + def __repr__(self): + return "<{0}: {1} {2}>".format(self.__class__.__name__, self.name, self.rgb) + + def __str__(self): + return self.name + + def __eq__(self, other): + if self.reset: + return other.reset + else: + return self.number == other.number and self.rgb == other.rgb and self.simple == other.simple + + +class ANSIColor(BaseColor): + @property + def ansi_sequence(self): + if not self.__class__.use_color: + return '' + + ansi_addition = 30 if self.fg else 40 + + if self.reset: + return '\033[' + str(ansi_addition+9) + 'm' + elif self.simple: + return '\033[' + str(self.number+ansi_addition) + 'm' + else: + return '\033[' +str(ansi_addition+8) + ';5;' + str(self.number) + 'm' + + def __str__(self): + return self.ansi_sequence + + +Color = ANSIColor diff --git a/plumbum/color/color.py b/plumbum/color/color.py index c7854cc3f..93dc182d9 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -10,14 +10,15 @@ directly called or given to a with statement. """ -from __future__ import with_statement, print_function +from __future__ import print_function import sys import os from contextlib import contextmanager -from plumbum.color.names import names, html, camel_names -from plumbum.color.names import _simple_colors, _simple_attributes +from plumbum.color.names import color_names_full as names, color_html_full as html +from plumbum.color.names import color_names_simple as simple_colors, attributes_simple as simple_attributes from functools import partial +camel_names = [n.replace('_',' ').title().replace(' ','') for n in names] class Style(str): """This class allows the color change strings to be called directly @@ -114,12 +115,12 @@ def extended_ansi_color(cls, n, begin=38): def _get_style_color(color, ob): 'Gets a color, intended to be used in discriptor protocol' - return Style.ansi_color(_simple_colors[color]+ob.val) + return Style.ansi_color((simple_colors.index(color) if color != 'reset' else 9) +ob.val) def _get_style_attribute(attribute, ob): 'Gets an attribute, intended to be used in discriptor protocol' - return Style.ansi_color(_simple_attributes[attribute]) + return Style.ansi_color(simple_attributes[attribute]) class ColorCollection(object): @@ -130,6 +131,10 @@ class ColorCollection(object): def __init__(self, val): self.val = val + @property + def RESET(self): + return _get_style_color('reset',self) + def from_name(self, name): 'Gets the index of a name, raises key error if not found' if name in names: @@ -181,7 +186,7 @@ def __exit__(self, type, value, traceback): return False # Adding the color name shortcuts -for item in _simple_colors: +for item in simple_colors: setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item), doc='Shortcut for '+item)) class Colors(ColorCollection): @@ -202,7 +207,7 @@ def __call__(self, color): return Style(color) -for item in _simple_attributes: +for item in simple_attributes: setattr(Colors, item.upper(), property(partial(_get_style_attribute, item), doc='Shortcut for '+item)) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index cd5a3bf1b..f6509a2eb 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -265,23 +265,58 @@ 255,grey_93,#eeeeee ''' -names = [n.split(',')[1] for n in _named_colors.split()] -html = [n.split(',')[2] for n in _named_colors.split()] -camel_names = [n.replace('_', ' ').title().replace(' ','') for n in names] +color_names_full = [n.split(',')[1] for n in _named_colors.split()] +color_html_full = [n.split(',')[2] for n in _named_colors.split()] -_simple_colors = dict( - black=0, - red=1, - green=2, - yellow=3, - blue=4, - magenta=5, - cyan=6, - white=7, - reset=9 -) +main_named_colors = slice(0,16) +normal_colors = slice(16,232) +grey_colors = slice(232,256) -_simple_attributes = dict( +def _distance_to_color(r, g, b, color): + rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) + """This computes the distance to a color, should be minimized""" + return (r-rgb[0])**2 + (g-rgb[1])**2 + (b-rgb[2])**2 + + +def find_nearest_color(r, g, b): + """This is a slow way to find the nearest color.""" + distances = [_distance_to_color(r, g, b, color) for color in color_html_full] + return min(range(len(distances)), key=distances.__getitem__) + +def find_nearest_simple_color(r, g, b): + """This will only return simple colors! + Breaks the colorspace into cubes, returns color""" + midlevel = 0x40 # Since bright is not included + + # The colors are originised so that it is a + # 3D cube, black at 0,0,0, white at 1,1,1 + # Compressed to linear_integers r,g,b + # [[[0,1],[2,3]],[[4,5],[6,7]]] + # r*1 + g*2 + b*4 + return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 + + +def from_html(color): + if len(color) != 7: + raise ValueError("Invalid length of html code") + return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) + +color_names_simple = [ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', + 'white', +] +"""Simple colors, remember that reset is #9""" + +color_html_simple = color_html_full[:7] + [color_html_full[15]] + + +attributes_simple = dict( reset=0, bold=1, dim=2, @@ -295,8 +330,8 @@ def print_html_table(): """Prints html names for documentation""" print(r'
      ') for i in range(256): - name = camel_names[i] - val = html[i] + name = color_names_full[i] + val = color_html_full[i] print(r'
    1. ' + val + r' ' + name diff --git a/tests/test_basecolors.py b/tests/test_basecolors.py new file mode 100644 index 000000000..164b9e161 --- /dev/null +++ b/tests/test_basecolors.py @@ -0,0 +1,71 @@ +from __future__ import with_statement, print_function +import unittest +from plumbum.color.base import BaseColor, Color +from plumbum.color.names import find_nearest_color, color_html_full, find_nearest_simple_color + + +class TestNearestColor(unittest.TestCase): + def test_exact(self): + self.assertEqual(find_nearest_color(0,0,0),0) + for n,color in enumerate(color_html_full): + # Ignoring duplicates + if n not in {16, 21, 46, 51, 196, 201, 226, 231, 244}: + rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) + self.assertEqual(find_nearest_color(*rgb),n) + + def test_nearby(self): + self.assertEqual(find_nearest_color(1,2,2),0) + self.assertEqual(find_nearest_color(7,7,9),232) + + def test_simplecolor(self): + self.assertEqual(find_nearest_simple_color(1,2,4), 0) + self.assertEqual(find_nearest_simple_color(0,255,0), 2) + self.assertEqual(find_nearest_simple_color(100,100,0), 3) + self.assertEqual(find_nearest_simple_color(140,140,140), 7) + + + +class TestColorLoad(unittest.TestCase): + def setUp(self): + Color.use_color = True + + def test_rgb(self): + blue = BaseColor(0,0,255) # Red, Green, Blue + self.assertEqual(blue.r, 0) + self.assertEqual(blue.g, 0) + self.assertEqual(blue.b, 255) + + def test_simple_name(self): + green = BaseColor.from_simple('green') + self.assertEqual(green.number, 2) + + def test_different_names(self): + self.assertEqual(BaseColor('Dark Blue'), + BaseColor('Dark_Blue')) + self.assertEqual(BaseColor('Dark_blue'), + BaseColor('Dark_Blue')) + self.assertEqual(BaseColor('DARKBLUE'), + BaseColor('Dark_Blue')) + self.assertEqual(BaseColor('DarkBlue'), + BaseColor('Dark_Blue')) + self.assertEqual(BaseColor('Dark Green'), + BaseColor('Dark_Green')) + + def test_loading_methods(self): + self.assertEqual(BaseColor("Yellow"), + BaseColor.from_full("Yellow")) + self.assertNotEqual(BaseColor.from_full("yellow"), + BaseColor.from_simple("yellow")) + + +class TestANSIColor(unittest.TestCase): + def setUp(self): + Color.use_color = True + + def test_ansi(self): + self.assertEqual(str(Color('reset')), '\033[39m') + self.assertEqual(str(Color('green')), '\033[38;5;2m') + self.assertEqual(str(Color.from_simple('red')), '\033[31m') + +if __name__ == '__main__': + unittest.main() From ff444db676a4c1c468dbf0d08ca8d6fc87b5e69d Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Thu, 16 Jul 2015 16:53:55 -0500 Subject: [PATCH 13/80] Python 2.6 fix, several fixes to base and color --- plumbum/color/base.py | 79 ++++++++++++++++++++++++++++++++-------- plumbum/color/color.py | 9 +++-- plumbum/color/names.py | 1 - tests/test_basecolors.py | 45 ++++++++++++++--------- 4 files changed, 96 insertions(+), 38 deletions(-) diff --git a/plumbum/color/base.py b/plumbum/color/base.py index 9f1e8ba3a..66717c341 100644 --- a/plumbum/color/base.py +++ b/plumbum/color/base.py @@ -13,20 +13,18 @@ from __future__ import print_function import sys import os -from contextlib import contextmanager from plumbum.color.names import color_names_full, color_html_full from plumbum.color.names import color_names_simple, color_html_simple, attributes_simple, from_html from plumbum.color.names import find_nearest_color, find_nearest_simple_color -from functools import partial _lower_camel_names = [n.replace('_', '') for n in color_names_full] class ColorNotFound(AttributeError): pass -class BaseColor(object): - """This class stores the idea of a color, rather than a specific implementation. - It provides as many different representations as possible, and can be subclassed. +class Color(object): + """This class stores the idea of a color, rather than a specific implementation (though it's __str__ is ANSI). + It provides as many different tools for representations as possible, and can be subclassed. It is not meant to be mutible. .from_any provides a quick-init shortcut. Possible colors:: @@ -177,33 +175,84 @@ def name(self): def __repr__(self): return "<{0}: {1} {2}>".format(self.__class__.__name__, self.name, self.rgb) - def __str__(self): - return self.name - def __eq__(self, other): if self.reset: return other.reset else: - return self.number == other.number and self.rgb == other.rgb and self.simple == other.simple + return (self.number == other.number + and self.rgb == other.rgb + and self.simple == other.simple) - -class ANSIColor(BaseColor): @property def ansi_sequence(self): if not self.__class__.use_color: return '' + else: + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + @property + def ansi_codes(self): ansi_addition = 30 if self.fg else 40 if self.reset: - return '\033[' + str(ansi_addition+9) + 'm' + return (ansi_addition+9,) elif self.simple: - return '\033[' + str(self.number+ansi_addition) + 'm' + return (self.number+ansi_addition,) else: - return '\033[' +str(ansi_addition+8) + ';5;' + str(self.number) + 'm' + return (ansi_addition+8, 5, self.number) + def __str__(self): return self.ansi_sequence -Color = ANSIColor + +class Style(object): + + def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): + self.attributes = attributes if attributes is not None else dict() + self.fg = fgcolor + self.bg = bgcolor + self.reset = reset + + @property + def ansi_codes(self): + if self.reset: + return [0] + + codes = [] + for attribute in self.attributes: + if self.attributes[attribute]: + codes.append(attributes_simple[attribute]) + else: + codes.append(20+attributes_simple[attribute]) + + if self.fg: + codes.extend(self.fg.ansi_codes) + + if self.bg: + self.bg.fg = False + codes.extend(self.bg.ansi_codes) + + return codes + + @property + def ansi_sequence(self): + if not Color.use_color: + return '' + else: + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + + def __repr__(self): + return "<{0}>".format(self.__class__.__name__) + + def __eq__(self, other): + if self.reset: + return other.reset + else: + return (self.attributes == other.attributes + and self.fg == other.fg + and self.bg == other.bg) + + def __str__(self): + return self.ansi_sequence diff --git a/plumbum/color/color.py b/plumbum/color/color.py index 93dc182d9..b01fdbb73 100644 --- a/plumbum/color/color.py +++ b/plumbum/color/color.py @@ -120,7 +120,7 @@ def _get_style_color(color, ob): def _get_style_attribute(attribute, ob): 'Gets an attribute, intended to be used in discriptor protocol' - return Style.ansi_color(simple_attributes[attribute]) + return Style.ansi_color(simple_attributes[attribute] if attribute != 'reset' else 0) class ColorCollection(object): @@ -189,7 +189,7 @@ def __exit__(self, type, value, traceback): for item in simple_colors: setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item), doc='Shortcut for '+item)) -class Colors(ColorCollection): +class ColorFactory(ColorCollection): """Singleton. Holds font styles, FG and BG objects representing colors, and imitates the FG object to some degree.""" @@ -208,10 +208,11 @@ def __call__(self, color): for item in simple_attributes: - setattr(Colors, item.upper(), property(partial(_get_style_attribute, item), doc='Shortcut for '+item)) + setattr(ColorFactory, item.upper(), property(partial(_get_style_attribute, item), doc='Shortcut for '+item)) +setattr(ColorFactory, 'reset'.upper(), property(partial(_get_style_attribute, 'reset'), doc='Shortcut for reset')) -COLOR = Colors() +COLOR = ColorFactory() @contextmanager diff --git a/plumbum/color/names.py b/plumbum/color/names.py index f6509a2eb..324145806 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -317,7 +317,6 @@ def from_html(color): attributes_simple = dict( - reset=0, bold=1, dim=2, underline=4, diff --git a/tests/test_basecolors.py b/tests/test_basecolors.py index 164b9e161..e239d98cf 100644 --- a/tests/test_basecolors.py +++ b/tests/test_basecolors.py @@ -1,6 +1,6 @@ from __future__ import with_statement, print_function import unittest -from plumbum.color.base import BaseColor, Color +from plumbum.color.base import Style, Color from plumbum.color.names import find_nearest_color, color_html_full, find_nearest_simple_color @@ -9,7 +9,7 @@ def test_exact(self): self.assertEqual(find_nearest_color(0,0,0),0) for n,color in enumerate(color_html_full): # Ignoring duplicates - if n not in {16, 21, 46, 51, 196, 201, 226, 231, 244}: + if n not in (16, 21, 46, 51, 196, 201, 226, 231, 244): rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) self.assertEqual(find_nearest_color(*rgb),n) @@ -30,32 +30,32 @@ def setUp(self): Color.use_color = True def test_rgb(self): - blue = BaseColor(0,0,255) # Red, Green, Blue + blue = Color(0,0,255) # Red, Green, Blue self.assertEqual(blue.r, 0) self.assertEqual(blue.g, 0) self.assertEqual(blue.b, 255) def test_simple_name(self): - green = BaseColor.from_simple('green') + green = Color.from_simple('green') self.assertEqual(green.number, 2) def test_different_names(self): - self.assertEqual(BaseColor('Dark Blue'), - BaseColor('Dark_Blue')) - self.assertEqual(BaseColor('Dark_blue'), - BaseColor('Dark_Blue')) - self.assertEqual(BaseColor('DARKBLUE'), - BaseColor('Dark_Blue')) - self.assertEqual(BaseColor('DarkBlue'), - BaseColor('Dark_Blue')) - self.assertEqual(BaseColor('Dark Green'), - BaseColor('Dark_Green')) + self.assertEqual(Color('Dark Blue'), + Color('Dark_Blue')) + self.assertEqual(Color('Dark_blue'), + Color('Dark_Blue')) + self.assertEqual(Color('DARKBLUE'), + Color('Dark_Blue')) + self.assertEqual(Color('DarkBlue'), + Color('Dark_Blue')) + self.assertEqual(Color('Dark Green'), + Color('Dark_Green')) def test_loading_methods(self): - self.assertEqual(BaseColor("Yellow"), - BaseColor.from_full("Yellow")) - self.assertNotEqual(BaseColor.from_full("yellow"), - BaseColor.from_simple("yellow")) + self.assertEqual(Color("Yellow"), + Color.from_full("Yellow")) + self.assertNotEqual(Color.from_full("yellow"), + Color.from_simple("yellow")) class TestANSIColor(unittest.TestCase): @@ -67,5 +67,14 @@ def test_ansi(self): self.assertEqual(str(Color('green')), '\033[38;5;2m') self.assertEqual(str(Color.from_simple('red')), '\033[31m') +class TestStyle(unittest.TestCase): + def setUp(self): + Color.use_color = True + + def test_style(self): + pass + + + if __name__ == '__main__': unittest.main() From 49f0781a03ba784fea5dd757d368fc3b139c04d9 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Thu, 16 Jul 2015 21:11:35 -0500 Subject: [PATCH 14/80] Merging master branch --- CHANGELOG.rst | 7 +------ docs/_news.rst | 2 +- docs/index.rst | 2 +- docs/local_commands.rst | 2 +- setup.py | 3 --- 5 files changed, 4 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d7189f36..2e2987b93 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,11 +1,6 @@ 1.5.0 ----- -* Color: new module for working with ANSI color codes for colored output -* CLI: Added color support - -1.4.3 ------ -* CLI: add ``COLOR``, which allows simple access to ANSI color sequences +* Removed support for Python 2.5. (Travis CI does not support it anymore) * CLI: add ``invoke``, which allows you to programmatically run applications (`#149 `_) * CLI: add ``--help-all`` and various cosmetic fixes: (`#125 `_), (`#126 `_), (`#127 `_) diff --git a/docs/_news.rst b/docs/_news.rst index c54bfc363..9a493e4eb 100644 --- a/docs/_news.rst +++ b/docs/_news.rst @@ -1,4 +1,4 @@ -* **planned**: Version 1.4.3 coming soon. This release is a collection of smaller features and bug fixes. +* **planned**: Version 1.5.0 coming soon. This release is a collection of small features and bug fixes. .. note:: ``setenv`` has been renamed ``with_env`` diff --git a/docs/index.rst b/docs/index.rst index 70d415d40..88fe541ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -78,7 +78,7 @@ you encounter or to request features. The library is released under the permissi Requirements ------------ -Plumbum supports **Python 2.5-3.4** and has been +Plumbum supports **Python 2.6-3.4** and has been tested on **Linux** and **Windows** machines. Any Unix-like machine should work fine out of the box, but on Windows, you'll probably want to install a decent `coreutils `_ environment and add it to your ``PATH``. I can recommend `mingw `_ (which comes diff --git a/docs/local_commands.rst b/docs/local_commands.rst index 490fe8722..35aa78073 100644 --- a/docs/local_commands.rst +++ b/docs/local_commands.rst @@ -155,7 +155,7 @@ objects that can be applied to your command to run it and get or test the retcod ``FG=True`` to ``TF`` or ``RETCODE``. For instance, ``cat["non/existing.file"] & TF(1,FG=True)`` -.. versionadded:: 1.4.3 +.. versionadded:: 1.5.0 Run and Popen ------------- diff --git a/setup.py b/setup.py index 33d95c73a..ffaa25b37 100644 --- a/setup.py +++ b/setup.py @@ -28,12 +28,9 @@ "License :: OSI Approved :: MIT License", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.0", - "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", From 7e624eab7da7854882aa85755e1e6c2d9ada9fa5 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Fri, 17 Jul 2015 12:11:02 -0500 Subject: [PATCH 15/80] Moved completely to new color system. Only visible effect is color(stuff) is now color[stuff]. Docs update next. --- plumbum/cli/application.py | 8 +- plumbum/color/__init__.py | 11 +- plumbum/color/base.py | 258 --------------------- plumbum/color/color.py | 233 ------------------- plumbum/color/factories.py | 97 ++++++++ plumbum/color/names.py | 57 +++-- plumbum/color/styles.py | 457 +++++++++++++++++++++++++++++++++++++ tests/test_basecolors.py | 17 +- tests/test_color.py | 19 +- 9 files changed, 619 insertions(+), 538 deletions(-) delete mode 100644 plumbum/color/base.py delete mode 100644 plumbum/color/color.py create mode 100644 plumbum/color/factories.py create mode 100644 plumbum/color/styles.py diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 102249b80..bb07d081c 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -502,7 +502,7 @@ def help(self): # @ReservedAssignment self.version() print("") if self.DESCRIPTION: - print(self.COLOR_DISCRIPTION(self.DESCRIPTION.strip() + '\n')) + print(self.COLOR_DISCRIPTION[self.DESCRIPTION.strip() + '\n']) m_args, m_varargs, _, m_defaults = inspect.getargspec(self.main) tailargs = m_args[1:] # skip self @@ -514,7 +514,7 @@ def help(self): # @ReservedAssignment tailargs = " ".join(tailargs) with self.COLOR_USAGE: - print(self.COLOR_HEADING("Usage:")) + print(self.COLOR_HEADING["Usage:"]) if not self.USAGE: if self._subcommands: self.USAGE = " %(progname)s [SWITCHES] [SUBCOMMAND [SWITCHES]] %(tailargs)s\n" @@ -610,8 +610,8 @@ def _get_prog_version(self): def version(self): """Prints the program's version and quits""" ver = self._get_prog_version() - ver_name = self.COLOR_VERSION(ver if ver is not None else "(version not set)") - program_name = self.COLOR_PROGNAME(self.PROGNAME) + ver_name = self.COLOR_VERSION[ver if ver is not None else "(version not set)"] + program_name = self.COLOR_PROGNAME[self.PROGNAME] print('%s %s' % (program_name, ver_name)) diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py index a2cdd1486..cff57a1ff 100644 --- a/plumbum/color/__init__.py +++ b/plumbum/color/__init__.py @@ -1,2 +1,11 @@ -from plumbum.color.color import COLOR, with_color, Style +"""\ +The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, +and attributes like bold and +underlined text. It also provides ``RESET`` to recover the normal font. +""" + +from plumbum.color.factories import StyleFactory +from plumbum.color.styles import Style, ANSIStyle + +COLOR = StyleFactory(ANSIStyle) diff --git a/plumbum/color/base.py b/plumbum/color/base.py deleted file mode 100644 index 66717c341..000000000 --- a/plumbum/color/base.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Color-related utilities. Feel free to use any color library on -the ``with_color`` statement. - -The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, -and attributes like bold and -underlined text. It also provides ``RESET`` to recover the normal font. - -With the ``Style`` class, any color can be -directly called or given to a with statement. -""" - -from __future__ import print_function -import sys -import os -from plumbum.color.names import color_names_full, color_html_full -from plumbum.color.names import color_names_simple, color_html_simple, attributes_simple, from_html -from plumbum.color.names import find_nearest_color, find_nearest_simple_color - -_lower_camel_names = [n.replace('_', '') for n in color_names_full] - -class ColorNotFound(AttributeError): - pass - -class Color(object): - """This class stores the idea of a color, rather than a specific implementation (though it's __str__ is ANSI). - It provides as many different tools for representations as possible, and can be subclassed. - It is not meant to be mutible. .from_any provides a quick-init shortcut. - - Possible colors:: - - blue = ColorBase(0,0,255) # Red, Green, Blue - green = ColorBase.from_full("green") # Case insensitive name, from large colorset - red = ColorBase.from_full(1) # Color number - white = ColorBase.from_html("#FFFFFF") # HTML supported - yellow = ColorBase.from_simple("red") # Simple colorset - - - The attributes are: - self.fg: Foreground if True, background if not - self.reset: True it this is a reset color (following atts don't matter if True) - self.rgb: The red/green/blue tuple for this color - self.simple: Simple 6 color mode - self.number: The color number given the mode, closest to rgb if not exact - - """ - - use_color = sys.stdout.isatty() and os.name == "posix" - stdout = sys.stdout - - def __init__(self, r_or_color=None, g=None, b=None, fg=True): - """This only init's from color values, or tries to load non-simple ones.""" - - self.fg = fg - self.reset = True # Starts as reset color - self.rgb = (0,0,0) - self.simple = False - - if r_or_color is not None and None in (g,b): - try: - self._from_full(r_or_color) - except ColorNotFound: - self._from_html(r_or_color) - - - elif None not in (r_or_color, g, b): - self.rgb = (r_or_color,g,b) - self._init_number() - - def _init_number(self): - """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" - self.number = find_nearest_simple_color(*self.rgb) if self.simple else find_nearest_color(*self.rgb) - self.reset = False - - @classmethod - def from_simple(cls, color, fg=True): - """Creates a color from simple name or color number""" - self = cls(fg=fg) - self._from_simple(color) - return self - - def _from_simple(self, color): - """Internal loader for from_simple.""" - try: - color = color.lower() - except AttributeError: - pass - - if color == 'reset' or color==9: - return - - elif color in color_names_simple: - self.rgb = from_html(color_html_simple[color_names_simple.index(color)]) - self.simple = True - - elif isinstance(color, int) and 0 <= color <= 7: - self.rgb = from_html(color_html_simple[color]) - self.simple = True - - else: - raise ColorNotFound("Did not find color: " + repr(color)) - - self._init_number() - - @classmethod - def from_full(cls, color, fg=True): - """Creates a color from full name or color number""" - self = cls(fg=fg) - self._from_full(color) - return self - - def _from_full(self, color): - """Creates a color from full name or color number""" - try: - color = color.lower() - color = color.replace(' ','_') - except AttributeError: - pass - - if color == 'reset': - return - - elif color in color_names_full: - self.rgb = from_html(color_html_full[color_names_full.index(color)]) - - elif color in _lower_camel_names: - self.rgb = from_html(color_html_full[_lower_camel_names.index(color)]) - - elif isinstance(color, int) and 0 <= color <= 255: - self.rgb = from_html(color_html_full[color]) - - else: - raise ColorNotFound("Did not find color: " + repr(color)) - - self._init_number() - - @classmethod - def from_html(cls, color, fg=True): - """Converts #123456 values to colors.""" - - self = cls(fg=fg) - self._from_html(color) - return self - - def _from_html(self, color): - try: - self.rgb = from_html(color) - except (TypeError, ValueError): - raise ColorNotFound("Did not find htmlcode: " + repr(color)) - - self._init_number() - - - @property - def r(self): - return self.rgb[0] - - @property - def g(self): - return self.rgb[1] - - @property - def b(self): - return self.rgb[2] - - @property - def name(self): - if self.reset: - return 'reset' - elif self.simple: - return color_names_simple[self.number] - else: - color_names_full[self.number] - - def __repr__(self): - return "<{0}: {1} {2}>".format(self.__class__.__name__, self.name, self.rgb) - - def __eq__(self, other): - if self.reset: - return other.reset - else: - return (self.number == other.number - and self.rgb == other.rgb - and self.simple == other.simple) - - @property - def ansi_sequence(self): - if not self.__class__.use_color: - return '' - else: - return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' - - @property - def ansi_codes(self): - ansi_addition = 30 if self.fg else 40 - - if self.reset: - return (ansi_addition+9,) - elif self.simple: - return (self.number+ansi_addition,) - else: - return (ansi_addition+8, 5, self.number) - - - def __str__(self): - return self.ansi_sequence - - - -class Style(object): - - def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): - self.attributes = attributes if attributes is not None else dict() - self.fg = fgcolor - self.bg = bgcolor - self.reset = reset - - @property - def ansi_codes(self): - if self.reset: - return [0] - - codes = [] - for attribute in self.attributes: - if self.attributes[attribute]: - codes.append(attributes_simple[attribute]) - else: - codes.append(20+attributes_simple[attribute]) - - if self.fg: - codes.extend(self.fg.ansi_codes) - - if self.bg: - self.bg.fg = False - codes.extend(self.bg.ansi_codes) - - return codes - - @property - def ansi_sequence(self): - if not Color.use_color: - return '' - else: - return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' - - def __repr__(self): - return "<{0}>".format(self.__class__.__name__) - - def __eq__(self, other): - if self.reset: - return other.reset - else: - return (self.attributes == other.attributes - and self.fg == other.fg - and self.bg == other.bg) - - def __str__(self): - return self.ansi_sequence diff --git a/plumbum/color/color.py b/plumbum/color/color.py deleted file mode 100644 index b01fdbb73..000000000 --- a/plumbum/color/color.py +++ /dev/null @@ -1,233 +0,0 @@ -""" -Color-related utilities. Feel free to use any color library on -the ``with_color`` statement. - -The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, -and attributes like bold and -underlined text. It also provides ``RESET`` to recover the normal font. - -With the ``Style`` string subclass being used, any color can be -directly called or given to a with statement. -""" - -from __future__ import print_function -import sys -import os -from contextlib import contextmanager -from plumbum.color.names import color_names_full as names, color_html_full as html -from plumbum.color.names import color_names_simple as simple_colors, attributes_simple as simple_attributes -from functools import partial - -camel_names = [n.replace('_',' ').title().replace(' ','') for n in names] - -class Style(str): - """This class allows the color change strings to be called directly - to write them to stdout, and can be called in a with statement. - The use_color property causes this to return '' for colors, but only on - new instances of the object.""" - - use_color = sys.stdout.isatty() and os.name == "posix" - stdout = sys.stdout - - def negate(self): - """This negates the effect of the current color""" - return self.__class__(self._remove() if self.__class__.use_color else '') - - def now(self, wrap_this=None): - self.stdout.write(self.wrap(wrap_this)) - - def wrap(self, wrap_this=None): - if wrap_this is None: - return self if self.__class__.use_color else self.__class__('') - else: - if self.__class__.use_color: - return self + wrap_this - self - else: - return wrap_this - - - def __call__(self, wrap_this=None): - """This sets the color (no arguments) or wraps a str (1 arg)""" - if wrap_this is None: - self.now() - else: - return self.wrap(wrap_this) - - def __neg__(self): - """This negates the effect of the current color""" - return self.negate() - - def __sub__(self, other): - """Implemented to make muliple Style objects work""" - return self + (-other) - - def __rsub__(self, other): - """Implemented to make using negatives easier""" - return other + (-self) - - def __add__(self, other): - return self.__class__(super(Style, self).__add__(other)) - - def _remove(self): - """Don't use directly. Will find best match for negative of current color.""" - if self == '': - return '' - try: - if self[:2] == '\033[' and self[-1] == 'm': - v = self[2:-1].split(';') - n = list(map(int, v)) - if len(n) == 1 or (len(n) == 3 and n[1] == 5 and n[0] in (38, 48)): - if 30 <= n[0] <= 39: - return '\033[39m' - elif 40 <= n[0] <= 49: - return '\033[49m' - elif n[0] in (1, 2, 4, 5, 7, 8): - return '\033['+str(n[0]+20)+'m' - elif n[0] in (21, 22, 24, 25, 27, 28): - return '\033['+str(n[0]-20)+'m' - else: - return '' - except ValueError: - pass - return '\033[0m' - - def __enter__(self): - if self.__class__.use_color: - self.stdout.write(self) - return self - - def __exit__(self, type, value, traceback): - if self.__class__.use_color: - self.stdout.write(-self) - return False - - - @classmethod - def ansi_color(cls, n): - """Return an ANSI escape sequence given a number.""" - return cls('\033[' + str(n) + 'm') if cls.use_color else cls('') - - @classmethod - def extended_ansi_color(cls, n, begin=38): - """Return an ANSI extended color given number.""" - return cls('\033[' + str(begin) + ';5;' + str(n) + 'm') if cls.use_color else cls('') - - -def _get_style_color(color, ob): - 'Gets a color, intended to be used in discriptor protocol' - return Style.ansi_color((simple_colors.index(color) if color != 'reset' else 9) +ob.val) - - -def _get_style_attribute(attribute, ob): - 'Gets an attribute, intended to be used in discriptor protocol' - return Style.ansi_color(simple_attributes[attribute] if attribute != 'reset' else 0) - - -class ColorCollection(object): - - """This creates color names given a modifier value (FG, BG)""" - - - def __init__(self, val): - self.val = val - - @property - def RESET(self): - return _get_style_color('reset',self) - - def from_name(self, name): - 'Gets the index of a name, raises key error if not found' - if name in names: - return Style.extended_ansi_color(names.index(name), self.val + 8) - if name in camel_names: - return Style.extended_ansi_color(camel_names.index(name), self.val + 8) - if name in html: - return Style.extended_ansi_color(html.index(name, 1), self.val + 8) # Assuming second #000000 is better - raise KeyError(name) - - def regular(self, val): - """Access colors by value.""" - return Style.ansi_color(val + self.val) - - def extended(self, val): - """Return the extended color scheme color for a value.""" - return Style.extended_ansi_color(val, self.val + 8) - - def __call__(self, val): - """Shortcut to provide way to access colors by number""" - return self.regular(val) - - def __getitem__(self, val): - """Shortcut to provide way to access extended colors by number, name, or html hex code""" - try: - return self.from_name(val) - except KeyError: - return self.extended(val) - - def __iter__(self): - """Iterates through all colors in extended colorset.""" - return (self.extended(i) for i in range(256)) - - def __neg__(self): - """Allows clearing a color""" - return self.RESET - - def __rsub__(self, other): - return other + (-self) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - """This resets a FG/BG color or all colors, - due to different definition of RESET.""" - - Style.stdout.write(self.RESET) - return False - -# Adding the color name shortcuts -for item in simple_colors: - setattr(ColorCollection, item.upper(), property(partial(_get_style_color, item), doc='Shortcut for '+item)) - -class ColorFactory(ColorCollection): - - """Singleton. Holds font styles, FG and BG objects representing colors, and - imitates the FG object to some degree.""" - - def __init__(self): - self.val = 30 # This acts like FG color - - self.FG = ColorCollection(30) - self.BG = ColorCollection(40) - - self.DO_NOTHING = Style('') - - def __call__(self, color): - """Calling COLOR is a shortcut for Style(color)""" - return Style(color) - - -for item in simple_attributes: - setattr(ColorFactory, item.upper(), property(partial(_get_style_attribute, item), doc='Shortcut for '+item)) -setattr(ColorFactory, 'reset'.upper(), property(partial(_get_style_attribute, 'reset'), doc='Shortcut for reset')) - - -COLOR = ColorFactory() - - -@contextmanager -def with_color(color='', out=None): - """Sets the color to a given color or style, - resets when done, - even if an exception is thrown. Optional ``out`` to give a - different output channel (defaults to sys.stdout).""" - - if out is None: - out = sys.stdout - out.write(str(color)) - try: - yield - finally: - out.write(Style.ansi_color(0)) - -__all__ = ['COLOR', 'ColorCollection', 'with_color', 'Style'] diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py new file mode 100644 index 000000000..b3c67e9c6 --- /dev/null +++ b/plumbum/color/factories.py @@ -0,0 +1,97 @@ +""" +Color-related factories. They produce Styles. + +""" + +from __future__ import print_function +import sys +import os +from functools import partial +from contextlib import contextmanager +from plumbum.color.names import color_names_simple + +__all__ = ['ColorFactory', 'StyleFactory'] + + +class ColorFactory(object): + + """This creates color names given fg = True/False. It usually will + be called as part of a StyleFactory.""" + + def __init__(self, fg, style): + self._fg = fg + self._style = style + self.RESET = style.from_color(style.color_class(fg=fg)) + + # Adding the color name shortcuts for forground colors + for item in color_names_simple: + setattr(self, item.upper(), style.from_color(style.color_class.from_simple(item, fg=fg))) + + + def full(self, name): + """Gets the style for a color, using standard name procedure: either full + color name, html code, or number.""" +# TODO: add html to int conversion, so that all HTML colors work + return self._style.from_color(self._style.color_class(name, fg=self._fg)) + + def simple(self, name): + """Return the extended color scheme color for a value or name.""" + return self._style.from_color(self._style.color_class.from_simple(name, fg=self._fg)) + + def rgb(self, r, g, b): + """Return the extended color scheme color for a value.""" + return self._style.from_color(self._style.color_class(r, g, b, fg=self._fg)) + + + def __getitem__(self, val): + """Shortcut to provide way to access extended colors.""" + return self.full(val) + + def __call__(self, val): + """Shortcut to provide way to access simple colors.""" + return self.simple(val) + + def __iter__(self): + """Iterates through all colors in extended colorset.""" + return (self.full(i) for i in range(256)) + + def __neg__(self): + """Allows clearing a color""" + return self.RESET + + def __rsub__(self, other): + return other + (-self) + + def __enter__(self): + """This will reset the color on leaving the with statement.""" + return self + + def __exit__(self, type, value, traceback): + """This resets a FG/BG color or all styles, + due to different definition of RESET for the + factories.""" + + self._style.stdout.write(str(self.RESET)) + return False + + def __repr__(self): + return "<{0}>".format(self.__class__.__name__) + +class StyleFactory(ColorFactory): + + """Factory for styles. Holds font styles, FG and BG objects representing colors, and + imitates the FG ColorFactory to a large degree.""" + + def __init__(self, style): + super(StyleFactory,self).__init__(True, style) + + self.FG = ColorFactory(True, style) + self.BG = ColorFactory(False, style) + + self.DO_NOTHING = style() + self.RESET = style(reset=True) + + for item in style.attribute_names: + setattr(self, item.upper(), style(attributes={item:True})) + setattr(self, "NON_"+item.upper(), style(attributes={item:False})) + diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 324145806..59a149497 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -268,10 +268,46 @@ color_names_full = [n.split(',')[1] for n in _named_colors.split()] color_html_full = [n.split(',')[2] for n in _named_colors.split()] +color_names_simple = [ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'magenta', + 'cyan', + 'white', +] +"""Simple colors, remember that reset is #9""" + +color_html_simple = color_html_full[:7] + [color_html_full[15]] + +# Common segments of colors in full table main_named_colors = slice(0,16) normal_colors = slice(16,232) grey_colors = slice(232,256) +# Attributes + +valid_attributes = set(( + 'bold', + 'dim', + 'underline', + 'blink', + 'reverse', + 'hidden' + )) + +attributes_ansi = dict( + bold=1, + dim=2, + underline=4, + blink=5, + reverse=7, + hidden=8 + ) + +#Functions to be used for color name operations def _distance_to_color(r, g, b, color): rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) """This computes the distance to a color, should be minimized""" @@ -301,29 +337,8 @@ def from_html(color): raise ValueError("Invalid length of html code") return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) -color_names_simple = [ - 'black', - 'red', - 'green', - 'yellow', - 'blue', - 'magenta', - 'cyan', - 'white', -] -"""Simple colors, remember that reset is #9""" - -color_html_simple = color_html_full[:7] + [color_html_full[15]] -attributes_simple = dict( - bold=1, - dim=2, - underline=4, - blink=5, - reverse=7, - hidden=8 - ) def print_html_table(): """Prints html names for documentation""" diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py new file mode 100644 index 000000000..4f0f968dc --- /dev/null +++ b/plumbum/color/styles.py @@ -0,0 +1,457 @@ +""" +With the ``Style`` class, any color can be directly called or given to a with statement. +""" + +from __future__ import print_function +import sys +import os +import re +from copy import copy +from plumbum.color.names import color_names_full, color_html_full +from plumbum.color.names import color_names_simple, color_html_simple, valid_attributes, from_html +from plumbum.color.names import find_nearest_color, find_nearest_simple_color, attributes_ansi + +__all__ = ['Color', 'Style', 'ANSIStyle', 'ColorNotFound', 'AttributeNotFound'] + + +_lower_camel_names = [n.replace('_', '') for n in color_names_full] + + +class ColorNotFound(Exception): + pass + + +class AttributeNotFound(Exception): + pass + + +class Color(object): + """This class stores the idea of a color, rather than a specific implementation. + It provides as many different tools for representations as possible, and can be subclassed + to add more represenations. ``.from_any`` provides a quick-init shortcut. + + Possible colors:: + + blue = ColorBase(0,0,255) # Red, Green, Blue + green = ColorBase.from_full("green") # Case insensitive name, from large colorset + red = ColorBase.from_full(1) # Color number + white = ColorBase.from_html("#FFFFFF") # HTML supported + yellow = ColorBase.from_simple("red") # Simple colorset + + + The attributes are: + self.fg: Foreground if True, background if not + self.reset: True it this is a reset color (following atts don't matter if True) + self.rgb: The red/green/blue tuple for this color + self.simple: Simple 6 color mode + self.number: The color number given the mode, closest to rgb if not exact + + """ + + + def __init__(self, r_or_color=None, g=None, b=None, fg=True): + """This works from color values, or tries to load non-simple ones.""" + + self.fg = fg + self.reset = True # Starts as reset color + self.rgb = (0,0,0) + self.simple = False + self.exact = True # Sets to false if interpolation done + + if r_or_color is not None and None in (g,b): + try: + self._from_full(r_or_color) + except ColorNotFound: + self._from_html(r_or_color) + + + elif None not in (r_or_color, g, b): + self.rgb = (r_or_color,g,b) + self._init_number() + + def _init_number(self): + """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" + if self.simple: + self.number = find_nearest_simple_color(*self.rgb) + self.exact = self.rgb == from_html(color_html_simple[self.number]) + else: + self.number = find_nearest_color(*self.rgb) + self.exact = self.rgb == from_html(color_html_full[self.number]) + + self.reset = False + + @classmethod + def from_simple(cls, color, fg=True): + """Creates a color from simple name or color number""" + self = cls(fg=fg) + self._from_simple(color) + return self + + def _from_simple(self, color): + try: + color = color.lower() + except AttributeError: + pass + + if color == 'reset' or color==9: + return + + elif color in color_names_simple: + self.rgb = from_html(color_html_simple[color_names_simple.index(color)]) + self.simple = True + + elif isinstance(color, int) and 0 <= color <= 7: + self.rgb = from_html(color_html_simple[color]) + self.simple = True + + else: + raise ColorNotFound("Did not find color: " + repr(color)) + + self._init_number() + + @classmethod + def from_full(cls, color, fg=True): + """Creates a color from full name or color number""" + self = cls(fg=fg) + self._from_full(color) + return self + + def _from_full(self, color): + try: + color = color.lower() + color = color.replace(' ','_') + except AttributeError: + pass + + if color == 'reset': + return + + elif color in color_names_full: + self.rgb = from_html(color_html_full[color_names_full.index(color)]) + + elif color in _lower_camel_names: + self.rgb = from_html(color_html_full[_lower_camel_names.index(color)]) + + elif isinstance(color, int) and 0 <= color <= 255: + self.rgb = from_html(color_html_full[color]) + + else: + raise ColorNotFound("Did not find color: " + repr(color)) + + self._init_number() + + @classmethod + def from_html(cls, color, fg=True): + """Converts #123456 values to colors.""" + + self = cls(fg=fg) + self._from_html(color) + return self + + def _from_html(self, color): + try: + self.rgb = from_html(color) + except (TypeError, ValueError): + raise ColorNotFound("Did not find htmlcode: " + repr(color)) + + self._init_number() + + @property + def r(self): + return self.rgb[0] + + @property + def g(self): + return self.rgb[1] + + @property + def b(self): + return self.rgb[2] + + @property + def name(self): + """The (closest) name of the current color""" + if self.reset: + return 'reset' + elif self.simple: + return color_names_simple[self.number] + else: + return color_names_full[self.number] + + def __repr__(self): + name = ' Simple' if self.simple else '' + name += '' if self.fg else ' Background' + name += ' ' + self.name.replace('_',' ').title() + name += '' if self.exact else (" "+str(self.rgb)) + return name[1:] + + def __eq__(self, other): + if self.reset: + return other.reset + else: + return (self.number == other.number + and self.rgb == other.rgb + and self.simple == other.simple) + + @property + def ansi_sequence(self): + if not self.__class__.use_color: + return '' + else: + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + + @property + def ansi_codes(self): + ansi_addition = 30 if self.fg else 40 + + if self.reset: + return (ansi_addition+9,) + elif self.simple: + return (self.number+ansi_addition,) + else: + return (ansi_addition+8, 5, self.number) + + @property + def html_hex_code(self): + if reset: + return '#000000' + return color_html_simple[self.number] if self.simple else color_html_full[self.number] + + def __str__(self): + return self.name + + + +class Style(object): + """This class allows the color changes to be called directly + to write them to stdout, ``[]`` calls to wrap colors (or the ``.wrap`` method) + and can be called in a with statement. + The ``use_color`` property causes this to return '' for colors""" + + use_color = sys.stdout.isatty() and os.name == "posix" + stdout = sys.stdout + color_class = Color + attribute_names = valid_attributes # a set of valid names + + def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): + self.attributes = attributes if attributes is not None else dict() + self.fg = fgcolor + self.bg = bgcolor + self.reset = reset + invalid_attributes = set(self.attributes) - self.attribute_names + if len(invalid_attributes) > 0: + raise AttributeNotFound("Attribute(s) not valid: " + ", ".join(invalid_attributes)) + + @classmethod + def from_color(cls, color): + if color.fg: + self = cls(fgcolor=color) + else: + self = cls(bgcolor=color) + return self + + + def invert(self): + """This resets current color(s) and flips the value of all + attributes present""" + + other = self.__class__() + + # Opposite of reset is reset + if self.reset: + other.reset = True + return other + + # Flip all attributes + for attribute in self.attributes: + other.attributes[attribute] = not self.attributes[attribute] + + # Reset only if color present + if self.fg: + other.fg = self.fg.__class__() + + if self.bg: + other.bg = self.bg.__class__() + + return other + + def __copy__(self): + """Copy is supported, will make dictionary and colors unique.""" + result = self.__class__() + result.reset = self.reset + result.fg = copy(self.fg) + result.bg = copy(self.bg) + result.attributes = copy(self.attributes) + return result + + def __neg__(self): + """This negates the effect of the current color""" + return self.invert() + + def __sub__(self, other): + """Implemented to make muliple Style objects work""" + return self + (-other) + + def __rsub__(self, other): + """Implemented to make using negatives easier""" + return other + (-self) + + def __add__(self, other): + """Adding two matching Styles results in a new style with + the combination of both. Adding with a string results in + the string concatiation of a style. + + Addition is non-communitive, with the rightmost Style property + being taken if both have the same property.""" + if type(self) == type(other): + result = copy(other) + + result.reset = self.reset or other.reset + for attribute in self.attributes: + if attribute not in result.attributes: + result.attributes[attribute] = self.attributes[attribute] + if not result.fg: + result.fg = self.fg + if not result.bg: + result.bg = self.bg + return result + else: + return other.__class__(self) + other + + def __radd__(self, other): + """This only gets called if the string is on the left side.""" + return other + other.__class__(self) + + def wrap(self, wrap_this): + return self + wrap_this - self + + def __call__(self, printable=None): + if printable: + self.stdout.write(self.wrap(printable)) + else: + self.stdout.write(str(self)) + + def __getitem__(self, wrap_this): + """The [] syntax is supported for wrapping""" + return self.wrap(wrap_this) + + def __enter__(self): + """Context manager support""" + self.stdout.write(str(self)) + + def __exit__(self, type, value, traceback): + """Runs even if exception occured, does not catch it.""" + self.stdout.write(str(-self)) + return False + + @property + def ansi_codes(self): + if self.reset: + return [0] + + codes = [] + for attribute in self.attributes: + if self.attributes[attribute]: + codes.append(attributes_ansi[attribute]) + else: + codes.append(20+attributes_ansi[attribute]) + + if self.fg: + codes.extend(self.fg.ansi_codes) + + if self.bg: + self.bg.fg = False + codes.extend(self.bg.ansi_codes) + + return codes + + @property + def ansi_sequence(self): + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + + def __repr__(self): + name = self.__class__.__name__ + attributes = ', '.join(a for a in self.attributes if self.attributes[a]) + neg_attributes = ', '.join('-'+a for a in self.attributes if not self.attributes[a]) + colors = ', '.join(repr(c) for c in [self.fg, self.bg] if c) + string = '; '.join(s for s in [attributes, neg_attributes, colors] if s) + if self.reset: + string = 'reset' + return "<{0}: {1}>".format(name, string if string else 'empty') + + def __eq__(self, other): + if type(self) == type(other): + if self.reset: + return other.reset + else: + return (self.attributes == other.attributes + and self.fg == other.fg + and self.bg == other.bg) + else: + return str(self) == other + + def __str__(self): + raise NotImplemented("This is a base style, does not have an representation") + + # TODO: Support buffer interface? + + + @classmethod + def from_ansi(cls, ansi_string): + """This generated a style from an ansi string.""" + result = self.__class__() + reg = re.compile('\033' + r'\[([\d;]+)m') + res = reg.search(ansi_string) + for group in res.groups: + sequence = map(int,group.split(';')) + result.add_ansi(sequence) + + def add_ansi(self, sequence): + """Adds a sequence of ansi numbers to the class""" + + values = iter(sequence) + try: + while True: + value = next(value) + if value == 38 or value == 48: + fg = value == 38 + if next(values) != 5: + raise ColorNotFound("the value 5 should follow a 38 or 48") + value = next(values) + if fg: + self.fg = self.color_class.from_full(value) + else: + self.bg = self.color_class.from_full(value, fg=False) + elif value==0: + self.reset = True + elif value in ansi_attributes.values(): + for name in ansi_attributes: + if value == ansi_attributes[name]: + self.attributes[name] = True + elif value in (20+n for n in ansi_attributes.values()): + for name in ansi_attributes: + if value == ansi_attributes[name] + 20: + self.attributes[name] = False + elif 30 <= value <= 37: + self.fg = self.color_class.from_simple(value-30) + elif 40 <= value <= 47: + self.bg = self.color_class.from_simple(value-40, fg=False) + elif value == 39: + self.fg = self.color_class() + elif value == 49: + self.bg = self.color_class(fg=False) + else: + raise ColorNotFound("The code {0} is not recognised".format(value)) + except StopIteration: + return + + +class ANSIStyle(Style): + attribute_names = set(attributes_ansi) + + def __str__(self): + if self.use_color: + return self.ansi_sequence + else: + return '' + diff --git a/tests/test_basecolors.py b/tests/test_basecolors.py index e239d98cf..1fd838a10 100644 --- a/tests/test_basecolors.py +++ b/tests/test_basecolors.py @@ -1,6 +1,6 @@ from __future__ import with_statement, print_function import unittest -from plumbum.color.base import Style, Color +from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound from plumbum.color.names import find_nearest_color, color_html_full, find_nearest_simple_color @@ -26,8 +26,6 @@ def test_simplecolor(self): class TestColorLoad(unittest.TestCase): - def setUp(self): - Color.use_color = True def test_rgb(self): blue = Color(0,0,255) # Red, Green, Blue @@ -60,21 +58,22 @@ def test_loading_methods(self): class TestANSIColor(unittest.TestCase): def setUp(self): - Color.use_color = True + ANSIStyle.use_color = True def test_ansi(self): - self.assertEqual(str(Color('reset')), '\033[39m') - self.assertEqual(str(Color('green')), '\033[38;5;2m') - self.assertEqual(str(Color.from_simple('red')), '\033[31m') + self.assertEqual(str(ANSIStyle(fgcolor=Color('reset'))), '\033[39m') + self.assertEqual(str(ANSIStyle(fgcolor=Color('green'))), '\033[38;5;2m') + self.assertEqual(str(ANSIStyle(fgcolor=Color.from_simple('red'))), '\033[31m') class TestStyle(unittest.TestCase): def setUp(self): - Color.use_color = True + ANSIStyle.use_color = True - def test_style(self): + def test_InvalidAttributes(self): pass + if __name__ == '__main__': unittest.main() diff --git a/tests/test_color.py b/tests/test_color.py index 0e690894a..8170c3831 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,8 +1,7 @@ from __future__ import with_statement, print_function import unittest from plumbum import COLOR -from plumbum.color import with_color -from plumbum.color.color import Style +from plumbum.color.styles import ANSIStyle as Style class TestColor(unittest.TestCase): @@ -28,7 +27,7 @@ def testLoadColorByName(self): def testMultiColor(self): sumcolor = COLOR.BOLD + COLOR.BLUE - self.assertEqual(COLOR.RESET, -sumcolor) + self.assertEqual(COLOR.NON_BOLD + COLOR.FG.RESET, -sumcolor) def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) @@ -37,19 +36,15 @@ def testUndoColor(self): self.assertEqual('\033[49m', ''-COLOR.BG) self.assertEqual('\033[21m', -COLOR.BOLD) self.assertEqual('\033[22m', -COLOR.DIM) - for i in (1, 2, 4, 5, 7, 8): - self.assertEqual('\033[%im' % i, -COLOR('\033[%im' % (20 + i))) - self.assertEqual('\033[%im' % (i + 20), -COLOR('\033[%im' % i)) - for i in range(10): - self.assertEqual('\033[39m', -COLOR('\033[%im' % (30 + i))) - self.assertEqual('\033[49m', -COLOR('\033[%im' % (40 + i))) + for i in range(7): + self.assertEqual('\033[39m', -COLOR(i)) + self.assertEqual('\033[49m', -COLOR.BG(i)) self.assertEqual('\033[39m', -COLOR.FG(i)) self.assertEqual('\033[49m', -COLOR.BG(i)) for i in range(256): self.assertEqual('\033[39m', -COLOR.FG[i]) self.assertEqual('\033[49m', -COLOR.BG[i]) self.assertEqual('\033[0m', -COLOR.RESET) - self.assertEqual('\033[0m', -COLOR('this is random')) def testLackOfColor(self): Style.use_color = False @@ -60,10 +55,10 @@ def testLackOfColor(self): def testVisualColors(self): print() for c in (COLOR.FG(x) for x in range(1, 6)): - with with_color(c): + with c: print('Cycle color test', end=' ') print(' - > back to normal') - with with_color(): + with COLOR: print(COLOR.FG.GREEN + "Green " + COLOR.BOLD + "Bold " - COLOR.BOLD + "Normal") From f6df7225d2cdab4290ed71f561b6ed05d505c39b Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Fri, 17 Jul 2015 14:57:10 -0500 Subject: [PATCH 16/80] Updating hex code, repr. Added way to iterate over simple colors. --- examples/color.py | 4 +++- plumbum/color/factories.py | 5 +++++ plumbum/color/styles.py | 46 +++++++++++++++++++++++++++----------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/examples/color.py b/examples/color.py index 566291e7e..98010060e 100644 --- a/examples/color.py +++ b/examples/color.py @@ -16,4 +16,6 @@ print(color + ' ', end='') COLOR.RESET() print() - print('Colors can be reset ' + COLOR.UNDERLINE + 'Too!') + print('Colors can be reset ' + COLOR.UNDERLINE['Too!']) + for color in COLOR.simple_colorful: + print(color["This is in color!"]) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index b3c67e9c6..a57291f59 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -77,6 +77,11 @@ def __exit__(self, type, value, traceback): def __repr__(self): return "<{0}>".format(self.__class__.__name__) + @property + def simple_colorful(self): + """List over the six simple actual colors.""" + return [self.simple(i) for i in range(1,7)] + class StyleFactory(ColorFactory): """Factory for styles. Holds font styles, FG and BG objects representing colors, and diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 4f0f968dc..3ad87de12 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -32,11 +32,13 @@ class Color(object): Possible colors:: - blue = ColorBase(0,0,255) # Red, Green, Blue - green = ColorBase.from_full("green") # Case insensitive name, from large colorset - red = ColorBase.from_full(1) # Color number - white = ColorBase.from_html("#FFFFFF") # HTML supported - yellow = ColorBase.from_simple("red") # Simple colorset + reset = Color() # The reset color by default + background_reset = Color(fg=False) # Can be a background color + blue = Color(0,0,255) # Red, Green, Blue + green = Color.from_full("green") # Case insensitive name, from large colorset + red = Color.from_full(1) # Color number + white = Color.from_html("#FFFFFF") # HTML supported + yellow = Color.from_simple("red") # Simple colorset The attributes are: @@ -56,7 +58,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): self.reset = True # Starts as reset color self.rgb = (0,0,0) self.simple = False - self.exact = True # Sets to false if interpolation done + self.exact = True # Set to False if interpolation done if r_or_color is not None and None in (g,b): try: @@ -182,7 +184,7 @@ def __repr__(self): name = ' Simple' if self.simple else '' name += '' if self.fg else ' Background' name += ' ' + self.name.replace('_',' ').title() - name += '' if self.exact else (" "+str(self.rgb)) + name += '' if self.exact else ' ' + self.html_hex_code return name[1:] def __eq__(self, other): @@ -212,11 +214,18 @@ def ansi_codes(self): return (ansi_addition+8, 5, self.number) @property - def html_hex_code(self): - if reset: + def html_hex_code_nearest(self): + if self.reset: return '#000000' return color_html_simple[self.number] if self.simple else color_html_full[self.number] + @property + def html_hex_code(self): + if self.reset: + return '#000000' + else: + return '#' + '{0[0]:02X}{0[1]:02X}{0[2]:02X}'.format(self.rgb) + def __str__(self): return self.name @@ -323,14 +332,27 @@ def __radd__(self, other): return other + other.__class__(self) def wrap(self, wrap_this): + """Wrap a sting in this style and its inverse.""" return self + wrap_this - self - def __call__(self, printable=None): + def now(self, *printable): + """This is a shortcut to print color immediatly to the stdout. + If called without arguments, this will change the Style immediatly. + If called with an argument, will print that argument to stdout wrapped + in Style.""" if printable: - self.stdout.write(self.wrap(printable)) + self.stdout.write(self.wrap(' '.join(map(str,printable)))) else: self.stdout.write(str(self)) + def __call__(self, *printable): + """Without arguments, this will change the current stdout color instantly. + With arguments, they will be wrapped in style and returned.""" + if printable: + return self.wrap(*printable) + else: + self.now() + def __getitem__(self, wrap_this): """The [] syntax is supported for wrapping""" return self.wrap(wrap_this) @@ -393,8 +415,6 @@ def __eq__(self, other): def __str__(self): raise NotImplemented("This is a base style, does not have an representation") - # TODO: Support buffer interface? - @classmethod def from_ansi(cls, ansi_string): From 9a013c28b32bd428889851e469830cc714ae6636 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Fri, 17 Jul 2015 14:57:52 -0500 Subject: [PATCH 17/80] Adding ini file for xdist to enable live test monitoring --- tests/pytest.ini | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/pytest.ini diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 000000000..7afc8ca2c --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,3 @@ +# This is for py.test -f with xdist plugin +[pytest] +looponfailroots = . ../plumbum From 11c121d51c556714c5757222f1d558b4dceaa506 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Fri, 17 Jul 2015 19:53:21 -0500 Subject: [PATCH 18/80] Working html color, more docs, .RESET for Styles, removed NON_ notation, camelcase names --- CHANGELOG.rst | 4 ++ docs/api/color.rst | 9 ++- docs/color.rst | 138 +++++++++++++++++++++++++++++-------- plumbum/color/__init__.py | 4 +- plumbum/color/factories.py | 4 +- plumbum/color/styles.py | 81 +++++++++++++++++----- tests/test_color.py | 34 ++++++++- 7 files changed, 224 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2e2987b93..57efdde46 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,7 @@ +In development +-------------- +* Color: Added color module, support for color added to cli + 1.5.0 ----- * Removed support for Python 2.5. (Travis CI does not support it anymore) diff --git a/docs/api/color.rst b/docs/api/color.rst index 32da0bd6f..0cb904527 100644 --- a/docs/api/color.rst +++ b/docs/api/color.rst @@ -1,9 +1,14 @@ Package plumbum.color ===================== -.. automodule:: plumbum.color.color +.. automodule:: plumbum.color :members: -.. automodule:: plumbum.color.names +.. automodule:: plumbum.color.styles + :members: + +.. automodule:: plumbum.color.factories :members: +.. automodule:: plumbum.color.names + :members: diff --git a/docs/color.rst b/docs/color.rst index 936afd192..3968788a8 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -3,17 +3,29 @@ Color tools =========== -.. versionadded:: 1.5.0 +.. versionadded:: 1.6.0 The purpose of the `plumbum.color` library is to make adding -color to your scripts easy and safe. Color is often a great -addition, but not a necessity, and implementing it properly -is tricky. -A built in color module provides quick, clean access to ANSI colors for your scripts. +color to Python easy and safe. Color is often a great +addition to shell scripts, but not a necessity, and implementing it properly +is tricky. It is easy to end up with an unreadable color stuck on your terminal or +with random unreadable symbols around your text. With the color module, you get quick, +safe access to ANSI colors and attributes for your scripts. The module also provides an +API for creating other colorschemes for other systems using escapes. -Colors are -accessed through the ``COLOR`` object, which is a collection of ``Style`` objects. The ``COLOR`` object has the following properties: +.. note:: Enabling color + + The ANSI Style assumes that only a terminal on a posix-identity + system can display color. You can force the use of color globally by setting + ``Style.use_color=True``. + + +Color Factory +============= + +Colors are accessed through the ``COLOR`` object, which is an instance of a StyleFactory. +The ``COLOR`` object has the following properties: ``FG`` and ``BG`` The forground and background colors, reset to default with ``COLOR.FG.RESET`` @@ -23,17 +35,18 @@ accessed through the ``COLOR`` object, which is a collection of ``Style`` object You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` - All the `ANSI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD``, etc. + All the `ANSI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. ``RESET`` The global reset will restore all properties at once. ``DO_NOTHING`` Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. -A color can be used directly as if it was a string, -or called for immediate printing to ``stdout``. Calling a +A color can be used directly as if it was a string, for adding to strings or printing. +Calling a color without an argument will send the color to stdout. Calling a color with an argument will wrap the string in the color and the matching negation. +(to avoid accedintally sending a color to stdout, you can also use `[]` syntax). Any color can be used as the target of a with statement, and normal color -will be restored on exiting the with statement, even with an Exception. +will be restored on exiting the with statement, even when an Exception occurs. An example of the usage of ``COLOR``:: @@ -44,9 +57,12 @@ An example of the usage of ``COLOR``:: print('This is green') print('This is completly restored, even if an exception is thrown!') -We could have used the shortcut ``COLOR.GREEN()`` instead. You can also use ``COLOR`` -directly as a context manager if you only want the restoring ability, and if you call -``COLOR(...)``, you can manually pass in any `ANSI` escape sequence. +We could have used the shortcut ``COLOR.GREEN()`` instead. If we had a non-foreground color +style active, such as ``COLOR.UNDERLINE`` or ``COLOR.BG.YELLOW``, then the context manager +will not reset those properties; it only resets properties it sets. +You can also use ``COLOR`` directly as a context manager if you only want to +restore all color after a block. If you call +``COLOR.from_ansi(...)``, you can manually pass in any `ANSI` escape sequence. Further examples of manipulations possible with the library:: @@ -61,22 +77,13 @@ Further examples of manipulations possible with the library:: COLOR.YELLOW() print('Colors made', COLOR.UNDERLINE + 'very' - COLOR.UNDERLINE, 'easy!') -The name of a script is automatically colored with the ``.COLOR_NAME`` property. Other style properties may be added; they will be set to ``COLOR.DO_NOTHING``, but can be overriden by your class. Any callable can be used to provide formats. - -.. note:: - The color library looks for a tty terminal and posix operating - system and only creates colors if those are found. If you want to manually - enable or disable color, you can set ``plumbum.color.Style.use_color = True`` - (or ``False``); however, this will only - affect new objects or manipulations. All Style color objects are dynamically - generated, so this usually is not a problem. 256 color support ----------------- The library support 256 colors through numbers, names or HEX html codes. You can access them -as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#0000FF']. The supported colors are: +as ``COLOR.FG[12]``, ``COLOR.FG['Light_Blue']``, ``COLOR.FG['LightBlue']``, or ``COLOR.FG['#0000FF']``. The supported colors are: .. raw:: html :file: _color_list.html @@ -84,12 +91,89 @@ as COLOR.FG[12], COLOR.FG['Light_Blue'], COLOR.FG['LightBlue'], or COLOR.FG['#00 The Classes ----------- -The library works through the ``Style`` class. It is a subclass of ``str`` that adds color related methods. It can be called without arguments to print to `Style.stdout`, and with arguments to wrap a string in the current Style and its negation. +The library consists of three primary classes, the ``Color`` class, the ``Style`` class, and the ``StyleFactory`` class. The following +portion of this document is primarily dealing with the working of the system, and is meant to facilitate extensions or work on the system. + +The ``Color`` class provides meaning to the concept of color, and can provide a variety of representations for any color. It +can be initialised from r,g,b values, or hex codes, 256 colornames, or the simple color names via classmethods. If initialized +without arguments, it is the reset color. It also takes an fg True/False argument to indicate which color it is. You probably will +not be interacting with the Color class directly, and you probably will not need to subclass it, though new extensions to the +representations it can produce are welcome. + +The ``Style`` class hold two colors and a dictionary of attributes. It is the workhorse of the system and is what is produced +by the ``COLOR`` factory. It holds ``Color`` as ``.color_class``, which can be overridden by subclasses (again, this usually is not needed). +To create a color representation, you need to subclass ``Style`` and give it a working ``__str__`` definition. ``ANSIStyle`` is derived +from ``Style`` in this way. + +The factories, ``ColorFactory`` and ``StyleFactory``, are factory classes that are meant to provide simple access to 1 style Style classes. To use, +you need to initialize an object of ``StyleFactory`` with your intended Style. For example, ``COLOR`` is created by:: + + COLOR = StyleFactory(ANSIStyle) + +HTML Subclass Example +--------------------- + +For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: -The colors are generated by two ``ColorCollection`` objects, one for forground and one for background. They are part of the main ``COLOR`` object, which also acts like a ``ColorCollection`` (foreground). + class HTMLStyle(Style): + attribute_names = set(('bold','em')) + + def __str__(self): + if self.reset: + raise ResetNotSupported("HTML does not support global resets!") + result = '' + + if self.fg and not self.fg.reset: + result += ''.format(self.fg.html_hex_code) + if self.bg and not self.bg.reset: + result += ''.format(self.fg.html_hex_code) + if 'bold' in self.attributes and self.attributes['bold']: + result += '' + if 'em' in self.attributes and self.attributes['em']: + result += '' + + if self.fg and self.fg.reset: + result += '' + if self.bg and self.bg.reset: + result += '' + if 'bold' in self.attributes and not self.attributes['bold']: + result += '' + if 'em' in self.attributes and not self.attributes['em']: + result += '' + + return result + + + HTMLCOLOR = StyleFactory(HTMLStyle) + +This doesn't support global RESETs, but otherwise is a working implementation. This is an example of how easy it is to add support for other output formats. + +An example of usage:: + + >>> (HTMLCOLOR.BOLD + HTMLCOLOR.RED)("This is colored text") + 'This is colored text' + + +The above colortable can be generated with:: + + with open('_color_list.html', 'wt') as f: + print('
        ', file=f) + for color in HTMLCOLOR: + print("
      1. {0} {1} {2}
      2. " + .format(color("■"), + color.fg.html_hex_code, + color.fg.name_camelcase), file=f) + print('
      ', file=f) + + +.. note:: + + ``HTMLStyle`` is implemented in the library, as well, with the + ``HTMLCOLOR`` object available in ``plumbum.color``. See Also -------- * `colored `_ Another library with 256 color support -* `colorama `_ A library that supports colored text on Windows +* `colorama `_ A library that supports colored text on Windows, + can be combined with plumbum (if you force ``use_color``) diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py index cff57a1ff..977793f5a 100644 --- a/plumbum/color/__init__.py +++ b/plumbum/color/__init__.py @@ -5,7 +5,7 @@ """ from plumbum.color.factories import StyleFactory -from plumbum.color.styles import Style, ANSIStyle +from plumbum.color.styles import Style, ANSIStyle, HTMLStyle COLOR = StyleFactory(ANSIStyle) - +HTMLCOLOR = StyleFactory(HTMLStyle) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index a57291f59..6896b79e4 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -98,5 +98,7 @@ def __init__(self, style): for item in style.attribute_names: setattr(self, item.upper(), style(attributes={item:True})) - setattr(self, "NON_"+item.upper(), style(attributes={item:False})) + def from_ansi(self, ansi_sequence): + """Calling this is a shortcut for creating a style from an ANSI sequence.""" + return self._style.from_ansi(ansi_sequence) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 3ad87de12..04c9ee86d 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -24,6 +24,9 @@ class ColorNotFound(Exception): class AttributeNotFound(Exception): pass +class ResetNotSupported(Exception): + pass + class Color(object): """This class stores the idea of a color, rather than a specific implementation. @@ -180,10 +183,15 @@ def name(self): else: return color_names_full[self.number] + @property + def name_camelcase(self): + """The camelcase name of the color""" + return self.name.replace("_", " ").title().replace(" ","") + def __repr__(self): name = ' Simple' if self.simple else '' name += '' if self.fg else ' Background' - name += ' ' + self.name.replace('_',' ').title() + name += ' ' + self.name_camelcase name += '' if self.exact else ' ' + self.html_hex_code return name[1:] @@ -197,10 +205,7 @@ def __eq__(self, other): @property def ansi_sequence(self): - if not self.__class__.use_color: - return '' - else: - return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' @property def ansi_codes(self): @@ -235,9 +240,8 @@ class Style(object): """This class allows the color changes to be called directly to write them to stdout, ``[]`` calls to wrap colors (or the ``.wrap`` method) and can be called in a with statement. - The ``use_color`` property causes this to return '' for colors""" + """ - use_color = sys.stdout.isatty() and os.name == "posix" stdout = sys.stdout color_class = Color attribute_names = valid_attributes # a set of valid names @@ -284,6 +288,11 @@ def invert(self): return other + @property + def RESET(self): + """Shortcut to access reset as a property.""" + return self.invert() + def __copy__(self): """Copy is supported, will make dictionary and colors unique.""" result = self.__class__() @@ -419,12 +428,13 @@ def __str__(self): @classmethod def from_ansi(cls, ansi_string): """This generated a style from an ansi string.""" - result = self.__class__() + result = cls() reg = re.compile('\033' + r'\[([\d;]+)m') res = reg.search(ansi_string) - for group in res.groups: + for group in res.groups(): sequence = map(int,group.split(';')) result.add_ansi(sequence) + return result def add_ansi(self, sequence): """Adds a sequence of ansi numbers to the class""" @@ -432,7 +442,7 @@ def add_ansi(self, sequence): values = iter(sequence) try: while True: - value = next(value) + value = next(values) if value == 38 or value == 48: fg = value == 38 if next(values) != 5: @@ -444,13 +454,13 @@ def add_ansi(self, sequence): self.bg = self.color_class.from_full(value, fg=False) elif value==0: self.reset = True - elif value in ansi_attributes.values(): - for name in ansi_attributes: - if value == ansi_attributes[name]: + elif value in attributes_ansi.values(): + for name in attributes_ansi: + if value == attributes_ansi[name]: self.attributes[name] = True - elif value in (20+n for n in ansi_attributes.values()): - for name in ansi_attributes: - if value == ansi_attributes[name] + 20: + elif value in (20+n for n in attributes_ansi.values()): + for name in attributes_ansi: + if value == attributes_ansi[name] + 20: self.attributes[name] = False elif 30 <= value <= 37: self.fg = self.color_class.from_simple(value-30) @@ -467,6 +477,14 @@ def add_ansi(self, sequence): class ANSIStyle(Style): + """This is a subclass for ANSI styles. Use it to get + color on sys.stdout tty terminals on posix systems. + + set ``use_color = True/False`` if you want to control color + for anything using this Style.""" + + use_color = sys.stdout.isatty() and os.name == "posix" + attribute_names = set(attributes_ansi) def __str__(self): @@ -475,3 +493,34 @@ def __str__(self): else: return '' +class HTMLStyle(Style): + """This was meant to be a demo of subclassing Style, but + actually can be a handy way to quicky color html text.""" + + attribute_names = set(('bold','em')) + + def __str__(self): + if self.reset: + raise ResetNotSupported("HTML does not support global resets!") + + result = '' + + if self.fg and not self.fg.reset: + result += ''.format(self.fg.html_hex_code) + if self.bg and not self.bg.reset: + result += ''.format(self.fg.html_hex_code) + if 'bold' in self.attributes and self.attributes['bold']: + result += '' + if 'em' in self.attributes and self.attributes['em']: + result += '' + + if self.fg and self.fg.reset: + result += '' + if self.bg and self.bg.reset: + result += '' + if 'bold' in self.attributes and not self.attributes['bold']: + result += '' + if 'em' in self.attributes and not self.attributes['em']: + result += '' + + return result diff --git a/tests/test_color.py b/tests/test_color.py index 8170c3831..ffce2c66c 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -2,7 +2,7 @@ import unittest from plumbum import COLOR from plumbum.color.styles import ANSIStyle as Style - +from plumbum.color import HTMLCOLOR class TestColor(unittest.TestCase): @@ -27,7 +27,30 @@ def testLoadColorByName(self): def testMultiColor(self): sumcolor = COLOR.BOLD + COLOR.BLUE - self.assertEqual(COLOR.NON_BOLD + COLOR.FG.RESET, -sumcolor) + self.assertEqual(COLOR.BOLD.RESET + COLOR.FG.RESET, -sumcolor) + + def testSums(self): + # Sums should not be communitave, last one is used + self.assertEqual(COLOR.RED, COLOR.BLUE + COLOR.RED) + self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) + + def testFromAnsi(self): + for color in COLOR.simple_colorful: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG.simple_colorful: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.BLINK): + self.assertEqual(color, COLOR.from_ansi(str(color))) + + color = COLOR.BOLD + COLOR.FG.GREEN + COLOR.BG.BLUE + COLOR.UNDERLINE + self.assertEqual(color, COLOR.from_ansi(str(color))) + color = COLOR.RESET + self.assertEqual(color, COLOR.from_ansi(str(color))) + def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) @@ -45,6 +68,9 @@ def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG[i]) self.assertEqual('\033[49m', -COLOR.BG[i]) self.assertEqual('\033[0m', -COLOR.RESET) + self.assertEqual(COLOR.DO_NOTHING, -COLOR.DO_NOTHING) + + self.assertEqual(COLOR.BOLD.RESET, -COLOR.BOLD) def testLackOfColor(self): Style.use_color = False @@ -71,5 +97,9 @@ def testToggleColors(self): + "This is on a BG" - COLOR.BG + " and this is not") COLOR.RESET() + def test_html(self): + self.assertEqual(HTMLCOLOR.RED("This is tagged"), + 'This is tagged') + if __name__ == '__main__': unittest.main() From 597de95ce861a864fd9cb1c6761b0740ebc4f551 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Fri, 17 Jul 2015 22:15:06 -0500 Subject: [PATCH 19/80] Fix typo in BG for HTMLStyle --- docs/color.rst | 2 +- plumbum/color/styles.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index 3968788a8..ef4a604ee 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -127,7 +127,7 @@ For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: if self.fg and not self.fg.reset: result += ''.format(self.fg.html_hex_code) if self.bg and not self.bg.reset: - result += ''.format(self.fg.html_hex_code) + result += ''.format(self.bg.html_hex_code) if 'bold' in self.attributes and self.attributes['bold']: result += '' if 'em' in self.attributes and self.attributes['em']: diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 04c9ee86d..fa43f3f92 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -508,7 +508,7 @@ def __str__(self): if self.fg and not self.fg.reset: result += ''.format(self.fg.html_hex_code) if self.bg and not self.bg.reset: - result += ''.format(self.fg.html_hex_code) + result += ''.format(self.bg.html_hex_code) if 'bold' in self.attributes and self.attributes['bold']: result += '' if 'em' in self.attributes and self.attributes['em']: From 74e916c6e89aef6799ec925b59706a31b861317d Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sat, 18 Jul 2015 09:45:02 -0500 Subject: [PATCH 20/80] Updated sys.stdout override to class level function and propery --- plumbum/color/factories.py | 2 +- plumbum/color/styles.py | 25 +++++++++++++++++++-- tests/{test_basecolors.py => test_style.py} | 0 3 files changed, 24 insertions(+), 3 deletions(-) rename tests/{test_basecolors.py => test_style.py} (100%) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 6896b79e4..734ef5624 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -71,7 +71,7 @@ def __exit__(self, type, value, traceback): due to different definition of RESET for the factories.""" - self._style.stdout.write(str(self.RESET)) + self._style().stdout.write(str(self.RESET)) return False def __repr__(self): diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index fa43f3f92..001a44ec0 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -29,7 +29,8 @@ class ResetNotSupported(Exception): class Color(object): - """This class stores the idea of a color, rather than a specific implementation. + """\ + This class stores the idea of a color, rather than a specific implementation. It provides as many different tools for representations as possible, and can be subclassed to add more represenations. ``.from_any`` provides a quick-init shortcut. @@ -242,9 +243,21 @@ class Style(object): and can be called in a with statement. """ - stdout = sys.stdout color_class = Color attribute_names = valid_attributes # a set of valid names + _stdout = None + + @property + def stdout(self): + """\ + This property will allow custom, class level control of stdout. + It will use current sys.stdout if set to None (default). + """ + return self.__class__._stdout if self.__class__._stdout is not None else sys.stdout + + @stdout.setter + def stdout(self, newout): + self.__class__._stdout = newout def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): self.attributes = attributes if attributes is not None else dict() @@ -305,6 +318,7 @@ def __copy__(self): def __neg__(self): """This negates the effect of the current color""" return self.invert() + __invert__ = __neg__ def __sub__(self, other): """Implemented to make muliple Style objects work""" @@ -344,6 +358,13 @@ def wrap(self, wrap_this): """Wrap a sting in this style and its inverse.""" return self + wrap_this - self + def __lshift__(self, other): + """This class support "String:" << color << color2 syntax""" + return self + other + + __rlshift__ = wrap + + def now(self, *printable): """This is a shortcut to print color immediatly to the stdout. If called without arguments, this will change the Style immediatly. diff --git a/tests/test_basecolors.py b/tests/test_style.py similarity index 100% rename from tests/test_basecolors.py rename to tests/test_style.py From ee7f5ed5b79de260836c207b02ddd699f9cb98fe Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sat, 18 Jul 2015 10:04:28 -0500 Subject: [PATCH 21/80] Adding classmethods for stdout --- plumbum/color/styles.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 001a44ec0..1f3d10dc3 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -254,10 +254,19 @@ def stdout(self): It will use current sys.stdout if set to None (default). """ return self.__class__._stdout if self.__class__._stdout is not None else sys.stdout + @classmethod + def set_stdout(cls, stdout = None): + """This is basically a class property, but I can't really + do a true class propery without metaclasses, which have different + syntax in Python2 and Python3.""" + cls._stdout = stdout @stdout.setter def stdout(self, newout): self.__class__._stdout = newout + @classmethod + def get_stdout(cls): + return cls._stdout if cls._stdout is not None else sys.stdout def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): self.attributes = attributes if attributes is not None else dict() From f85d69b11f64ea93a3b50512279f00b491fc4c35 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sat, 18 Jul 2015 10:08:37 -0500 Subject: [PATCH 22/80] Dropping the classmethods for stdout for now. Hard to do properly without metaclasses. --- plumbum/color/factories.py | 2 +- plumbum/color/styles.py | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 734ef5624..4dd2543f6 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -71,7 +71,7 @@ def __exit__(self, type, value, traceback): due to different definition of RESET for the factories.""" - self._style().stdout.write(str(self.RESET)) + self.RESET.now() return False def __repr__(self): diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 1f3d10dc3..96ed3fc66 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -252,21 +252,12 @@ def stdout(self): """\ This property will allow custom, class level control of stdout. It will use current sys.stdout if set to None (default). + Unfortunatly, it only works on an instance.. """ return self.__class__._stdout if self.__class__._stdout is not None else sys.stdout - @classmethod - def set_stdout(cls, stdout = None): - """This is basically a class property, but I can't really - do a true class propery without metaclasses, which have different - syntax in Python2 and Python3.""" - cls._stdout = stdout - @stdout.setter def stdout(self, newout): self.__class__._stdout = newout - @classmethod - def get_stdout(cls): - return cls._stdout if cls._stdout is not None else sys.stdout def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): self.attributes = attributes if attributes is not None else dict() From 815b5a7e07c5c85d435dd39c44b26de5ac0003d0 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sat, 18 Jul 2015 13:22:17 -0500 Subject: [PATCH 23/80] Added more ways to wrap strings, html output example improved. --- docs/color.rst | 50 +++++++++++++++------------------- plumbum/color/factories.py | 9 +++++++ plumbum/color/styles.py | 55 ++++++++++++++++++++++++++------------ tests/test_color.py | 23 ++++++++++++++-- 4 files changed, 90 insertions(+), 47 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index ef4a604ee..357892adb 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -44,7 +44,8 @@ The ``COLOR`` object has the following properties: A color can be used directly as if it was a string, for adding to strings or printing. Calling a color without an argument will send the color to stdout. Calling a color with an argument will wrap the string in the color and the matching negation. -(to avoid accedintally sending a color to stdout, you can also use `[]` syntax). +(to avoid accedintally sending a color to stdout, you can also use `[]` syntax). You +can also wrap a string in a color using ``<<`` or ``*`` syntax. Any color can be used as the target of a with statement, and normal color will be restored on exiting the with statement, even when an Exception occurs. @@ -116,34 +117,28 @@ HTML Subclass Example For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: class HTMLStyle(Style): - - attribute_names = set(('bold','em')) + attribute_names = dict(bold='b', em='em', li='li', code='code') def __str__(self): - if self.reset: - raise ResetNotSupported("HTML does not support global resets!") result = '' - - if self.fg and not self.fg.reset: - result += ''.format(self.fg.html_hex_code) + if self.bg and not self.bg.reset: result += ''.format(self.bg.html_hex_code) - if 'bold' in self.attributes and self.attributes['bold']: - result += '' - if 'em' in self.attributes and self.attributes['em']: - result += '' - + if self.fg and not self.fg.reset: + result += ''.format(self.fg.html_hex_code) + for attr in sorted(self.attributes): + if self.attributes[attr]: + result += '<' + self.attribute_names[attr] + '>' + + for attr in reversed(sorted(self.attributes)): + if not self.attributes[attr]: + result += '' if self.fg and self.fg.reset: result += '' if self.bg and self.bg.reset: result += '' - if 'bold' in self.attributes and not self.attributes['bold']: - result += '' - if 'em' in self.attributes and not self.attributes['em']: - result += '' - - return result + return result HTMLCOLOR = StyleFactory(HTMLStyle) @@ -151,20 +146,19 @@ This doesn't support global RESETs, but otherwise is a working implementation. T An example of usage:: - >>> (HTMLCOLOR.BOLD + HTMLCOLOR.RED)("This is colored text") - 'This is colored text' + >>> "This is colored text" << HTMLCOLOR.BOLD + HTMLCOLOR.RED + 'This is colored text' The above colortable can be generated with:: with open('_color_list.html', 'wt') as f: - print('
        ', file=f) - for color in HTMLCOLOR: - print("
      1. {0} {1} {2}
      2. " - .format(color("■"), - color.fg.html_hex_code, - color.fg.name_camelcase), file=f) - print('
      ', file=f) + with HTMLCOLOR.OL: + for color in HTMLCOLOR: + HTMLCOLOR.LI.line( + "■" << color, + color.fg.html_hex_code << HTMLCOLOR.CODE, + color.fg.name_camelcase) .. note:: diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 4dd2543f6..fa3ea2085 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -58,6 +58,7 @@ def __iter__(self): def __neg__(self): """Allows clearing a color""" return self.RESET + __invert__ = __neg__ def __rsub__(self, other): return other + (-self) @@ -102,3 +103,11 @@ def __init__(self, style): def from_ansi(self, ansi_sequence): """Calling this is a shortcut for creating a style from an ANSI sequence.""" return self._style.from_ansi(ansi_sequence) + + @property + def stdout(self): + """This is a shortcut for getting stdout from a class without an instance.""" + return self._style._stdout if self._style._stdout is not None else sys.stdout + @stdout.setter + def stdout(self, newout): + self._style._stdout = newout diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 96ed3fc66..10a40ef3a 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -264,7 +264,7 @@ def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): self.fg = fgcolor self.bg = bgcolor self.reset = reset - invalid_attributes = set(self.attributes) - self.attribute_names + invalid_attributes = set(self.attributes) - set(self.attribute_names) if len(invalid_attributes) > 0: raise AttributeNotFound("Attribute(s) not valid: " + ", ".join(invalid_attributes)) @@ -358,23 +358,45 @@ def wrap(self, wrap_this): """Wrap a sting in this style and its inverse.""" return self + wrap_this - self - def __lshift__(self, other): - """This class support "String:" << color << color2 syntax""" - return self + other + def __mul__(self, other): + """This class supports ``color * color2`` syntax, + and ``color * "String" syntax too.``""" + if type(self) == type(other): + return self + other + else: + return self.wrap(other) + + __lshift__ = __mul__ + """This class supports ``color << color2`` syntax. It also supports + ``"color << "String"`` syntax too. """ __rlshift__ = wrap + """This class supports ``"String:" << color`` syntax""" + __rmul__ = wrap + """This class supports ``"String:" * color`` syntax""" def now(self, *printable): - """This is a shortcut to print color immediatly to the stdout. + """\ + This is a shortcut to print color immediatly to the stdout. If called without arguments, this will change the Style immediatly. If called with an argument, will print that argument to stdout wrapped in Style.""" + if printable: self.stdout.write(self.wrap(' '.join(map(str,printable)))) else: self.stdout.write(str(self)) + def line(self, *printable): + """\ + This will print out a line of colored text. Similar to .now, except for + printing a newline at the end.""" + if printable: + self.stdout.write(self.wrap(' '.join(map(str, printable))) + '\n') + else: + self.stdout.write(str(self) + '\n') + def __call__(self, *printable): """Without arguments, this will change the current stdout color instantly. With arguments, they will be wrapped in style and returned.""" @@ -506,7 +528,7 @@ class ANSIStyle(Style): use_color = sys.stdout.isatty() and os.name == "posix" - attribute_names = set(attributes_ansi) + attribute_names = attributes_ansi def __str__(self): if self.use_color: @@ -518,30 +540,29 @@ class HTMLStyle(Style): """This was meant to be a demo of subclassing Style, but actually can be a handy way to quicky color html text.""" - attribute_names = set(('bold','em')) + attribute_names = dict(bold='b', em='em', li='li', underline='span style="text-decoration: underline;"', code='code', ol='ol start=0') def __str__(self): + if self.reset: raise ResetNotSupported("HTML does not support global resets!") result = '' - if self.fg and not self.fg.reset: - result += ''.format(self.fg.html_hex_code) if self.bg and not self.bg.reset: result += ''.format(self.bg.html_hex_code) - if 'bold' in self.attributes and self.attributes['bold']: - result += '' - if 'em' in self.attributes and self.attributes['em']: - result += '' + if self.fg and not self.fg.reset: + result += ''.format(self.fg.html_hex_code) + for attr in sorted(self.attributes): + if self.attributes[attr]: + result += '<' + self.attribute_names[attr] + '>' + for attr in reversed(sorted(self.attributes)): + if not self.attributes[attr]: + result += '' if self.fg and self.fg.reset: result += '' if self.bg and self.bg.reset: result += '' - if 'bold' in self.attributes and not self.attributes['bold']: - result += '' - if 'em' in self.attributes and not self.attributes['em']: - result += '' return result diff --git a/tests/test_color.py b/tests/test_color.py index ffce2c66c..c9af4c8f6 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -51,9 +51,21 @@ def testFromAnsi(self): color = COLOR.RESET self.assertEqual(color, COLOR.from_ansi(str(color))) + def testWrappedColor(self): + string = 'This is a string' + wrapped = '\033[31mThis is a string\033[39m' + self.assertEqual(COLOR.RED.wrap(string), wrapped) + self.assertEqual(string << COLOR.RED, wrapped) + self.assertEqual(COLOR.RED(string), wrapped) + self.assertEqual(COLOR.RED[string], wrapped) + + newcolor = COLOR.BLUE + COLOR.UNDERLINE + self.assertEqual(newcolor(string), string << newcolor) + self.assertEqual(newcolor(string), string << COLOR.BLUE + COLOR.UNDERLINE) def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) + self.assertEqual('\033[39m', ~COLOR.FG) self.assertEqual('\033[39m', ''-COLOR.FG) self.assertEqual('\033[49m', -COLOR.BG) self.assertEqual('\033[49m', ''-COLOR.BG) @@ -98,8 +110,15 @@ def testToggleColors(self): COLOR.RESET() def test_html(self): - self.assertEqual(HTMLCOLOR.RED("This is tagged"), - 'This is tagged') + red_tagged = 'This is tagged' + self.assertEqual(HTMLCOLOR.RED("This is tagged"), red_tagged) + self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) + self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) + + twin_tagged = 'This is tagged' + self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) + self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) + self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) if __name__ == '__main__': unittest.main() From ed1ef1c616c2b46971fecfdeeb2ef8f72b4dc55a Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sat, 18 Jul 2015 14:49:43 -0500 Subject: [PATCH 24/80] Added documentation, use_color shortcut. --- docs/color.rst | 128 +++++++++++++++++++++++++------------ plumbum/color/factories.py | 9 +++ plumbum/color/styles.py | 2 +- tests/test_color.py | 1 + 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index 357892adb..02a3fb7f3 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -12,13 +12,13 @@ addition to shell scripts, but not a necessity, and implementing it properly is tricky. It is easy to end up with an unreadable color stuck on your terminal or with random unreadable symbols around your text. With the color module, you get quick, safe access to ANSI colors and attributes for your scripts. The module also provides an -API for creating other colorschemes for other systems using escapes. +API for creating other color schemes for other systems using escapes. .. note:: Enabling color The ANSI Style assumes that only a terminal on a posix-identity system can display color. You can force the use of color globally by setting - ``Style.use_color=True``. + ``COLOR.use_color=True``. Color Factory @@ -28,59 +28,101 @@ Colors are accessed through the ``COLOR`` object, which is an instance of a Styl The ``COLOR`` object has the following properties: ``FG`` and ``BG`` - The forground and background colors, reset to default with ``COLOR.FG.RESET`` - or ``-COLOR.FG`` and likewise for ``BG``. (Named forground colors are available + The foreground and background colors, reset to default with ``COLOR.FG.RESET`` + or ``~COLOR.FG`` and likewise for ``BG``. (Named foreground colors are available directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, ``BLUE``, ``MAGENTA``, ``CYAN``, ``WHITE``, as well as ``RESET``, are available. You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` - All the `ANSI` modifiers are available, as well as their negations, sush as ``-COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. + All the `ANSI` modifiers are available, as well as their negations, such as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. ``RESET`` The global reset will restore all properties at once. ``DO_NOTHING`` Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. -A color can be used directly as if it was a string, for adding to strings or printing. -Calling a color without an argument will send the color to stdout. Calling a -color with an argument will wrap the string in the color and the matching negation. -(to avoid accedintally sending a color to stdout, you can also use `[]` syntax). You -can also wrap a string in a color using ``<<`` or ``*`` syntax. -Any color can be used as the target of a with statement, and normal color -will be restored on exiting the with statement, even when an Exception occurs. - -An example of the usage of ``COLOR``:: +The ``COLOR`` object can be used in a with statement, which resets the color on leaving +the statement body. (The ``FG`` and ``BG`` also can be put in with statements, and they +will restore the foreground and background color, respectively). Although it does support +some of the same things as a Style, its primary purpose is to generate Styles. - from plumbum import COLOR - with COLOR.FG.RED: - print('This is in red') - COLOR.FG.GREEN() - print('This is green') - print('This is completly restored, even if an exception is thrown!') -We could have used the shortcut ``COLOR.GREEN()`` instead. If we had a non-foreground color -style active, such as ``COLOR.UNDERLINE`` or ``COLOR.BG.YELLOW``, then the context manager -will not reset those properties; it only resets properties it sets. -You can also use ``COLOR`` directly as a context manager if you only want to -restore all color after a block. If you call -``COLOR.from_ansi(...)``, you can manually pass in any `ANSI` escape sequence. +Unsafe Manipulation +=================== + +Styles have two unsafe operations: Concatenation (with ``+``) and calling ``.now()`` without +arguments (directly calling a style is also a shortcut for ``.now``). These two +operations do not restore normal color to the terminal. To protect their use, +you should always use a context manager around any unsafe operation. + +An example of the usage of unsafe ``COLOR`` manipulations inside a context manager:: -Further examples of manipulations possible with the library:: + from plumbum import COLOR - print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again') with COLOR: - print('It is always a good idea to be in a context manager, to avoid being', - 'left with a colored terminal if there is an exception!') COLOR.FG.RED() - print(COLOR.BOLD("This is red, bold, and exciting!"), "And this is red only.") - print(COLOR.BG.CYAN + "This is red on a cyan background." + COLOR.RESET) - print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) - COLOR.YELLOW() - print('Colors made', COLOR.UNDERLINE + 'very' - COLOR.UNDERLINE, 'easy!') - + print('This is in red') + COLOR.GREEN() + print('This is green ' + COLOR.UNDERLINE + 'and now also underlined!') + print('Underlined' - COLOR.UNDERLINE + ' and not underlined but still red') + print('This is completly restored, even if an exception is thrown!') +Output: -256 color support + .. raw:: html + +

      This is in red
      + This is in green and now also underlined!
      + Underlined and not underlined but still green.
      + This is completly restored, even if an exception is thrown!

      + +We can use ``COLOR`` instead of ``COLOR.FG`` for foreground colors. If we had used ``COLOR.FG`` +as the context manager, then non-foreground properties, such as ``COLOR.UNDERLINE`` or +``COLOR.BG.YELLOW``, would not have reset those properties. Each attribute, +as well as ``FG``, ``BG``, and ``COLOR`` all have inverses in the ANSI standard. They are +accessed with ``~``, ``-``, or ``.RESET``, and can be used to manually make these operations +safer, but there is a better way. + +Safe Manipulation +================= + +All other operations are safe; they restore the color automatically. The first, and hopefully +already obvious one, is using a Style rather than a ``COLOR`` or ``COLOR.FG`` object in a ``with`` statement. +This will set the color (using sys.stdout by default) to that color, and restore color on leaving. + +The second method is to manually wrap a string. This can be done with ``color.wrap("string")``, +``"string" << color``, ``color << "string"``, ``color["string"]``, or ``color("string")``. These +produce strings that can be further manipulated or printed. + +Finally, you can also print a color to stdout directly using ``color.now("string")`` or +``color.line("string")``. The second method will add a newline, like print. Multiple arguments +can be passed; they will be concatenated with spaces. + +An example of safe manipulations:: + + print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again.') + with COLOR.RED: + print('Red color!') + with COLOR.BOLD: + print("This is red and bold.") + print("Not bold, but still red.") + print("Not red color or bold.") + print("This is bold and colorful!" << (COLOR.MAGENTA + COLOR.BOLD), "And this is not.") + +Output: + + .. raw:: html + +

      This is yellow And this is normal again.
      + Red color!
      + This is red and bold.
      +
      Not bold, but still red.
      +
      Not red color or bold.
      + This is bold and colorful! And this is not.

      + +If you call ``COLOR.from_ansi(...)``, you can manually pass in any `ANSI` escape sequence. + +256 Color Support ----------------- The library support 256 colors through numbers, names or HEX html codes. You can access them @@ -96,7 +138,7 @@ The library consists of three primary classes, the ``Color`` class, the ``Style` portion of this document is primarily dealing with the working of the system, and is meant to facilitate extensions or work on the system. The ``Color`` class provides meaning to the concept of color, and can provide a variety of representations for any color. It -can be initialised from r,g,b values, or hex codes, 256 colornames, or the simple color names via classmethods. If initialized +can be initialised from r,g,b values, or hex codes, 256 color names, or the simple color names via classmethods. If initialized without arguments, it is the reset color. It also takes an fg True/False argument to indicate which color it is. You probably will not be interacting with the Color class directly, and you probably will not need to subclass it, though new extensions to the representations it can produce are welcome. @@ -142,7 +184,7 @@ For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: HTMLCOLOR = StyleFactory(HTMLStyle) -This doesn't support global RESETs, but otherwise is a working implementation. This is an example of how easy it is to add support for other output formats. +This doesn't support global RESETs, since that's not how HTML works, but otherwise is a working implementation. This is an example of how easy it is to add support for other output formats. An example of usage:: @@ -150,7 +192,7 @@ An example of usage:: 'This is colored text' -The above colortable can be generated with:: +The above color table can be generated with:: with open('_color_list.html', 'wt') as f: with HTMLCOLOR.OL: @@ -164,10 +206,12 @@ The above colortable can be generated with:: .. note:: ``HTMLStyle`` is implemented in the library, as well, with the - ``HTMLCOLOR`` object available in ``plumbum.color``. + ``HTMLCOLOR`` object available in ``plumbum.color``. It was used + to create the colored output in this document, with small changes + because unsafe manipulations are not supported with HTML. See Also -------- * `colored `_ Another library with 256 color support * `colorama `_ A library that supports colored text on Windows, - can be combined with plumbum (if you force ``use_color``) + can be combined with Plumbum (if you force ``use_color``) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index fa3ea2085..3bd4815ef 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -100,6 +100,15 @@ def __init__(self, style): for item in style.attribute_names: setattr(self, item.upper(), style(attributes={item:True})) + @property + def use_color(self): + """Shortcut for setting color usage on Style""" + return self._style.use_color + + @use_color.setter + def use_color(self, val): + self._style.use_color = val + def from_ansi(self, ansi_sequence): """Calling this is a shortcut for creating a style from an ANSI sequence.""" return self._style.from_ansi(ansi_sequence) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 10a40ef3a..538cca3d8 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -374,7 +374,7 @@ def __mul__(self, other): """This class supports ``"String:" << color`` syntax""" __rmul__ = wrap - """This class supports ``"String:" * color`` syntax""" + """This class supports ``"String:" * color`` syntax. Buggy in some versions of python""" def now(self, *printable): """\ diff --git a/tests/test_color.py b/tests/test_color.py index c9af4c8f6..f4cbd9fc5 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -119,6 +119,7 @@ def test_html(self): self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) + self.assertEqual(HTMLCOLOR.RED << "This should be wrapped", "This should be wrapped" << HTMLCOLOR.RED) if __name__ == '__main__': unittest.main() From 8b58b315ae81ae881c542dea1f1cea8ee520be59 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sun, 19 Jul 2015 14:30:58 -0500 Subject: [PATCH 25/80] Fixed colortable for different size screens. --- docs/_color_list.html | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/_color_list.html b/docs/_color_list.html index d4b399afe..23e930d9c 100644 --- a/docs/_color_list.html +++ b/docs/_color_list.html @@ -1,3 +1,44 @@ + +
      1. #000000 Black
      2. #800000 Red
      3. @@ -256,3 +297,5 @@
      4. #e4e4e4 Grey89
      5. #eeeeee Grey93
      +
      +
      From a39823fdcf046367efe368a2303b6d27d3e43527 Mon Sep 17 00:00:00 2001 From: henryiii Date: Sun, 19 Jul 2015 16:26:52 -0500 Subject: [PATCH 26/80] Adding missing simicolons. Used editorial on iPhone to do find/replace. --- docs/_color_list.html | 512 +++++++++++++++++++++--------------------- 1 file changed, 256 insertions(+), 256 deletions(-) diff --git a/docs/_color_list.html b/docs/_color_list.html index 23e930d9c..504ee37aa 100644 --- a/docs/_color_list.html +++ b/docs/_color_list.html @@ -40,262 +40,262 @@
        -
      1. #000000 Black
      2. -
      3. #800000 Red
      4. -
      5. #008000 Green
      6. -
      7. #808000 Yellow
      8. -
      9. #000080 Blue
      10. -
      11. #800080 Magenta
      12. -
      13. #008080 Cyan
      14. -
      15. #c0c0c0 LightGray
      16. -
      17. #808080 DarkGray
      18. -
      19. #ff0000 LightRed
      20. -
      21. #00ff00 LightGreen
      22. -
      23. #ffff00 LightYellow
      24. -
      25. #0000ff LightBlue
      26. -
      27. #ff00ff LightMagenta
      28. -
      29. #00ffff LightCyan
      30. -
      31. #ffffff White
      32. -
      33. #000000 Grey0
      34. -
      35. #00005f NavyBlue
      36. -
      37. #000087 DarkBlue
      38. -
      39. #0000af Blue3
      40. -
      41. #0000d7 Blue3
      42. -
      43. #0000ff Blue1
      44. -
      45. #005f00 DarkGreen
      46. -
      47. #005f5f DeepSkyBlue4
      48. -
      49. #005f87 DeepSkyBlue4
      50. -
      51. #005faf DeepSkyBlue4
      52. -
      53. #005fd7 DodgerBlue3
      54. -
      55. #005fff DodgerBlue2
      56. -
      57. #008700 Green4
      58. -
      59. #00875f SpringGreen4
      60. -
      61. #008787 Turquoise4
      62. -
      63. #0087af DeepSkyBlue3
      64. -
      65. #0087d7 DeepSkyBlue3
      66. -
      67. #0087ff DodgerBlue1
      68. -
      69. #00af00 Green3
      70. -
      71. #00af5f SpringGreen3
      72. -
      73. #00af87 DarkCyan
      74. -
      75. #00afaf LightSeaGreen
      76. -
      77. #00afd7 DeepSkyBlue2
      78. -
      79. #00afff DeepSkyBlue1
      80. -
      81. #00d700 Green3
      82. -
      83. #00d75f SpringGreen3
      84. -
      85. #00d787 SpringGreen2
      86. -
      87. #00d7af Cyan3
      88. -
      89. #00d7d7 DarkTurquoise
      90. -
      91. #00d7ff Turquoise2
      92. -
      93. #00ff00 Green1
      94. -
      95. #00ff5f SpringGreen2
      96. -
      97. #00ff87 SpringGreen1
      98. -
      99. #00ffaf MediumSpringGreen
      100. -
      101. #00ffd7 Cyan2
      102. -
      103. #00ffff Cyan1
      104. -
      105. #5f0000 DarkRed
      106. -
      107. #5f005f DeepPink4
      108. -
      109. #5f0087 Purple4
      110. -
      111. #5f00af Purple4
      112. -
      113. #5f00d7 Purple3
      114. -
      115. #5f00ff BlueViolet
      116. -
      117. #5f5f00 Orange4
      118. -
      119. #5f5f5f Grey37
      120. -
      121. #5f5f87 MediumPurple4
      122. -
      123. #5f5faf SlateBlue3
      124. -
      125. #5f5fd7 SlateBlue3
      126. -
      127. #5f5fff RoyalBlue1
      128. -
      129. #5f8700 Chartreuse4
      130. -
      131. #5f875f DarkSeaGreen4
      132. -
      133. #5f8787 PaleTurquoise4
      134. -
      135. #5f87af SteelBlue
      136. -
      137. #5f87d7 SteelBlue3
      138. -
      139. #5f87ff CornflowerBlue
      140. -
      141. #5faf00 Chartreuse3
      142. -
      143. #5faf5f DarkSeaGreen4
      144. -
      145. #5faf87 CadetBlue
      146. -
      147. #5fafaf CadetBlue
      148. -
      149. #5fafd7 SkyBlue3
      150. -
      151. #5fafff SteelBlue1
      152. -
      153. #5fd700 Chartreuse3
      154. -
      155. #5fd75f PaleGreen3
      156. -
      157. #5fd787 SeaGreen3
      158. -
      159. #5fd7af Aquamarine3
      160. -
      161. #5fd7d7 MediumTurquoise
      162. -
      163. #5fd7ff SteelBlue1
      164. -
      165. #5fff00 Chartreuse2
      166. -
      167. #5fff5f SeaGreen2
      168. -
      169. #5fff87 SeaGreen1
      170. -
      171. #5fffaf SeaGreen1
      172. -
      173. #5fffd7 Aquamarine1
      174. -
      175. #5fffff DarkSlateGray2
      176. -
      177. #870000 DarkRed
      178. -
      179. #87005f DeepPink4
      180. -
      181. #870087 DarkMagenta
      182. -
      183. #8700af DarkMagenta
      184. -
      185. #8700d7 DarkViolet
      186. -
      187. #8700ff Purple
      188. -
      189. #875f00 Orange4
      190. -
      191. #875f5f LightPink4
      192. -
      193. #875f87 Plum4
      194. -
      195. #875faf MediumPurple3
      196. -
      197. #875fd7 MediumPurple3
      198. -
      199. #875fff SlateBlue1
      200. -
      201. #878700 Yellow4
      202. -
      203. #87875f Wheat4
      204. -
      205. #878787 Grey53
      206. -
      207. #8787af LightSlateGrey
      208. -
      209. #8787d7 MediumPurple
      210. -
      211. #8787ff LightSlateBlue
      212. -
      213. #87af00 Yellow4
      214. -
      215. #87af5f DarkOliveGreen3
      216. -
      217. #87af87 DarkSeaGreen
      218. -
      219. #87afaf LightSkyBlue3
      220. -
      221. #87afd7 LightSkyBlue3
      222. -
      223. #87afff SkyBlue2
      224. -
      225. #87d700 Chartreuse2
      226. -
      227. #87d75f DarkOliveGreen3
      228. -
      229. #87d787 PaleGreen3
      230. -
      231. #87d7af DarkSeaGreen3
      232. -
      233. #87d7d7 DarkSlateGray3
      234. -
      235. #87d7ff SkyBlue1
      236. -
      237. #87ff00 Chartreuse1
      238. -
      239. #87ff5f LightGreen
      240. -
      241. #87ff87 LightGreen
      242. -
      243. #87ffaf PaleGreen1
      244. -
      245. #87ffd7 Aquamarine1
      246. -
      247. #87ffff DarkSlateGray1
      248. -
      249. #af0000 Red3
      250. -
      251. #af005f DeepPink4
      252. -
      253. #af0087 MediumVioletRed
      254. -
      255. #af00af Magenta3
      256. -
      257. #af00d7 DarkViolet
      258. -
      259. #af00ff Purple
      260. -
      261. #af5f00 DarkOrange3
      262. -
      263. #af5f5f IndianRed
      264. -
      265. #af5f87 HotPink3
      266. -
      267. #af5faf MediumOrchid3
      268. -
      269. #af5fd7 MediumOrchid
      270. -
      271. #af5fff MediumPurple2
      272. -
      273. #af8700 DarkGoldenrod
      274. -
      275. #af875f LightSalmon3
      276. -
      277. #af8787 RosyBrown
      278. -
      279. #af87af Grey63
      280. -
      281. #af87d7 MediumPurple2
      282. -
      283. #af87ff MediumPurple1
      284. -
      285. #afaf00 Gold3
      286. -
      287. #afaf5f DarkKhaki
      288. -
      289. #afaf87 NavajoWhite3
      290. -
      291. #afafaf Grey69
      292. -
      293. #afafd7 LightSteelBlue3
      294. -
      295. #afafff LightSteelBlue
      296. -
      297. #afd700 Yellow3
      298. -
      299. #afd75f DarkOliveGreen3
      300. -
      301. #afd787 DarkSeaGreen3
      302. -
      303. #afd7af DarkSeaGreen2
      304. -
      305. #afd7d7 LightCyan3
      306. -
      307. #afd7ff LightSkyBlue1
      308. -
      309. #afff00 GreenYellow
      310. -
      311. #afff5f DarkOliveGreen2
      312. -
      313. #afff87 PaleGreen1
      314. -
      315. #afffaf DarkSeaGreen2
      316. -
      317. #afffd7 DarkSeaGreen1
      318. -
      319. #afffff PaleTurquoise1
      320. -
      321. #d70000 Red3
      322. -
      323. #d7005f DeepPink3
      324. -
      325. #d70087 DeepPink3
      326. -
      327. #d700af Magenta3
      328. -
      329. #d700d7 Magenta3
      330. -
      331. #d700ff Magenta2
      332. -
      333. #d75f00 DarkOrange3
      334. -
      335. #d75f5f IndianRed
      336. -
      337. #d75f87 HotPink3
      338. -
      339. #d75faf HotPink2
      340. -
      341. #d75fd7 Orchid
      342. -
      343. #d75fff MediumOrchid1
      344. -
      345. #d78700 Orange3
      346. -
      347. #d7875f LightSalmon3
      348. -
      349. #d78787 LightPink3
      350. -
      351. #d787af Pink3
      352. -
      353. #d787d7 Plum3
      354. -
      355. #d787ff Violet
      356. -
      357. #d7af00 Gold3
      358. -
      359. #d7af5f LightGoldenrod3
      360. -
      361. #d7af87 Tan
      362. -
      363. #d7afaf MistyRose3
      364. -
      365. #d7afd7 Thistle3
      366. -
      367. #d7afff Plum2
      368. -
      369. #d7d700 Yellow3
      370. -
      371. #d7d75f Khaki3
      372. -
      373. #d7d787 LightGoldenrod2
      374. -
      375. #d7d7af LightYellow3
      376. -
      377. #d7d7d7 Grey84
      378. -
      379. #d7d7ff LightSteelBlue1
      380. -
      381. #d7ff00 Yellow2
      382. -
      383. #d7ff5f DarkOliveGreen1
      384. -
      385. #d7ff87 DarkOliveGreen1
      386. -
      387. #d7ffaf DarkSeaGreen1
      388. -
      389. #d7ffd7 Honeydew2
      390. -
      391. #d7ffff LightCyan1
      392. -
      393. #ff0000 Red1
      394. -
      395. #ff005f DeepPink2
      396. -
      397. #ff0087 DeepPink1
      398. -
      399. #ff00af DeepPink1
      400. -
      401. #ff00d7 Magenta2
      402. -
      403. #ff00ff Magenta1
      404. -
      405. #ff5f00 OrangeRed1
      406. -
      407. #ff5f5f IndianRed1
      408. -
      409. #ff5f87 IndianRed1
      410. -
      411. #ff5faf HotPink
      412. -
      413. #ff5fd7 HotPink
      414. -
      415. #ff5fff MediumOrchid1
      416. -
      417. #ff8700 DarkOrange
      418. -
      419. #ff875f Salmon1
      420. -
      421. #ff8787 LightCoral
      422. -
      423. #ff87af PaleVioletRed1
      424. -
      425. #ff87d7 Orchid2
      426. -
      427. #ff87ff Orchid1
      428. -
      429. #ffaf00 Orange1
      430. -
      431. #ffaf5f SandyBrown
      432. -
      433. #ffaf87 LightSalmon1
      434. -
      435. #ffafaf LightPink1
      436. -
      437. #ffafd7 Pink1
      438. -
      439. #ffafff Plum1
      440. -
      441. #ffd700 Gold1
      442. -
      443. #ffd75f LightGoldenrod2
      444. -
      445. #ffd787 LightGoldenrod2
      446. -
      447. #ffd7af NavajoWhite1
      448. -
      449. #ffd7d7 MistyRose1
      450. -
      451. #ffd7ff Thistle1
      452. -
      453. #ffff00 Yellow1
      454. -
      455. #ffff5f LightGoldenrod1
      456. -
      457. #ffff87 Khaki1
      458. -
      459. #ffffaf Wheat1
      460. -
      461. #ffffd7 Cornsilk1
      462. -
      463. #ffffff Grey100
      464. -
      465. #080808 Grey3
      466. -
      467. #121212 Grey7
      468. -
      469. #1c1c1c Grey11
      470. -
      471. #262626 Grey15
      472. -
      473. #303030 Grey19
      474. -
      475. #3a3a3a Grey23
      476. -
      477. #444444 Grey27
      478. -
      479. #4e4e4e Grey30
      480. -
      481. #585858 Grey35
      482. -
      483. #626262 Grey39
      484. -
      485. #6c6c6c Grey42
      486. -
      487. #767676 Grey46
      488. -
      489. #808080 Grey50
      490. -
      491. #8a8a8a Grey54
      492. -
      493. #949494 Grey58
      494. -
      495. #9e9e9e Grey62
      496. -
      497. #a8a8a8 Grey66
      498. -
      499. #b2b2b2 Grey70
      500. -
      501. #bcbcbc Grey74
      502. -
      503. #c6c6c6 Grey78
      504. -
      505. #d0d0d0 Grey82
      506. -
      507. #dadada Grey85
      508. -
      509. #e4e4e4 Grey89
      510. -
      511. #eeeeee Grey93
      512. +
      513. #000000 Black
      514. +
      515. #800000 Red
      516. +
      517. #008000 Green
      518. +
      519. #808000 Yellow
      520. +
      521. #000080 Blue
      522. +
      523. #800080 Magenta
      524. +
      525. #008080 Cyan
      526. +
      527. #c0c0c0 LightGray
      528. +
      529. #808080 DarkGray
      530. +
      531. #ff0000 LightRed
      532. +
      533. #00ff00 LightGreen
      534. +
      535. #ffff00 LightYellow
      536. +
      537. #0000ff LightBlue
      538. +
      539. #ff00ff LightMagenta
      540. +
      541. #00ffff LightCyan
      542. +
      543. #ffffff White
      544. +
      545. #000000 Grey0
      546. +
      547. #00005f NavyBlue
      548. +
      549. #000087 DarkBlue
      550. +
      551. #0000af Blue3
      552. +
      553. #0000d7 Blue3
      554. +
      555. #0000ff Blue1
      556. +
      557. #005f00 DarkGreen
      558. +
      559. #005f5f DeepSkyBlue4
      560. +
      561. #005f87 DeepSkyBlue4
      562. +
      563. #005faf DeepSkyBlue4
      564. +
      565. #005fd7 DodgerBlue3
      566. +
      567. #005fff DodgerBlue2
      568. +
      569. #008700 Green4
      570. +
      571. #00875f SpringGreen4
      572. +
      573. #008787 Turquoise4
      574. +
      575. #0087af DeepSkyBlue3
      576. +
      577. #0087d7 DeepSkyBlue3
      578. +
      579. #0087ff DodgerBlue1
      580. +
      581. #00af00 Green3
      582. +
      583. #00af5f SpringGreen3
      584. +
      585. #00af87 DarkCyan
      586. +
      587. #00afaf LightSeaGreen
      588. +
      589. #00afd7 DeepSkyBlue2
      590. +
      591. #00afff DeepSkyBlue1
      592. +
      593. #00d700 Green3
      594. +
      595. #00d75f SpringGreen3
      596. +
      597. #00d787 SpringGreen2
      598. +
      599. #00d7af Cyan3
      600. +
      601. #00d7d7 DarkTurquoise
      602. +
      603. #00d7ff Turquoise2
      604. +
      605. #00ff00 Green1
      606. +
      607. #00ff5f SpringGreen2
      608. +
      609. #00ff87 SpringGreen1
      610. +
      611. #00ffaf MediumSpringGreen
      612. +
      613. #00ffd7 Cyan2
      614. +
      615. #00ffff Cyan1
      616. +
      617. #5f0000 DarkRed
      618. +
      619. #5f005f DeepPink4
      620. +
      621. #5f0087 Purple4
      622. +
      623. #5f00af Purple4
      624. +
      625. #5f00d7 Purple3
      626. +
      627. #5f00ff BlueViolet
      628. +
      629. #5f5f00 Orange4
      630. +
      631. #5f5f5f Grey37
      632. +
      633. #5f5f87 MediumPurple4
      634. +
      635. #5f5faf SlateBlue3
      636. +
      637. #5f5fd7 SlateBlue3
      638. +
      639. #5f5fff RoyalBlue1
      640. +
      641. #5f8700 Chartreuse4
      642. +
      643. #5f875f DarkSeaGreen4
      644. +
      645. #5f8787 PaleTurquoise4
      646. +
      647. #5f87af SteelBlue
      648. +
      649. #5f87d7 SteelBlue3
      650. +
      651. #5f87ff CornflowerBlue
      652. +
      653. #5faf00 Chartreuse3
      654. +
      655. #5faf5f DarkSeaGreen4
      656. +
      657. #5faf87 CadetBlue
      658. +
      659. #5fafaf CadetBlue
      660. +
      661. #5fafd7 SkyBlue3
      662. +
      663. #5fafff SteelBlue1
      664. +
      665. #5fd700 Chartreuse3
      666. +
      667. #5fd75f PaleGreen3
      668. +
      669. #5fd787 SeaGreen3
      670. +
      671. #5fd7af Aquamarine3
      672. +
      673. #5fd7d7 MediumTurquoise
      674. +
      675. #5fd7ff SteelBlue1
      676. +
      677. #5fff00 Chartreuse2
      678. +
      679. #5fff5f SeaGreen2
      680. +
      681. #5fff87 SeaGreen1
      682. +
      683. #5fffaf SeaGreen1
      684. +
      685. #5fffd7 Aquamarine1
      686. +
      687. #5fffff DarkSlateGray2
      688. +
      689. #870000 DarkRed
      690. +
      691. #87005f DeepPink4
      692. +
      693. #870087 DarkMagenta
      694. +
      695. #8700af DarkMagenta
      696. +
      697. #8700d7 DarkViolet
      698. +
      699. #8700ff Purple
      700. +
      701. #875f00 Orange4
      702. +
      703. #875f5f LightPink4
      704. +
      705. #875f87 Plum4
      706. +
      707. #875faf MediumPurple3
      708. +
      709. #875fd7 MediumPurple3
      710. +
      711. #875fff SlateBlue1
      712. +
      713. #878700 Yellow4
      714. +
      715. #87875f Wheat4
      716. +
      717. #878787 Grey53
      718. +
      719. #8787af LightSlateGrey
      720. +
      721. #8787d7 MediumPurple
      722. +
      723. #8787ff LightSlateBlue
      724. +
      725. #87af00 Yellow4
      726. +
      727. #87af5f DarkOliveGreen3
      728. +
      729. #87af87 DarkSeaGreen
      730. +
      731. #87afaf LightSkyBlue3
      732. +
      733. #87afd7 LightSkyBlue3
      734. +
      735. #87afff SkyBlue2
      736. +
      737. #87d700 Chartreuse2
      738. +
      739. #87d75f DarkOliveGreen3
      740. +
      741. #87d787 PaleGreen3
      742. +
      743. #87d7af DarkSeaGreen3
      744. +
      745. #87d7d7 DarkSlateGray3
      746. +
      747. #87d7ff SkyBlue1
      748. +
      749. #87ff00 Chartreuse1
      750. +
      751. #87ff5f LightGreen
      752. +
      753. #87ff87 LightGreen
      754. +
      755. #87ffaf PaleGreen1
      756. +
      757. #87ffd7 Aquamarine1
      758. +
      759. #87ffff DarkSlateGray1
      760. +
      761. #af0000 Red3
      762. +
      763. #af005f DeepPink4
      764. +
      765. #af0087 MediumVioletRed
      766. +
      767. #af00af Magenta3
      768. +
      769. #af00d7 DarkViolet
      770. +
      771. #af00ff Purple
      772. +
      773. #af5f00 DarkOrange3
      774. +
      775. #af5f5f IndianRed
      776. +
      777. #af5f87 HotPink3
      778. +
      779. #af5faf MediumOrchid3
      780. +
      781. #af5fd7 MediumOrchid
      782. +
      783. #af5fff MediumPurple2
      784. +
      785. #af8700 DarkGoldenrod
      786. +
      787. #af875f LightSalmon3
      788. +
      789. #af8787 RosyBrown
      790. +
      791. #af87af Grey63
      792. +
      793. #af87d7 MediumPurple2
      794. +
      795. #af87ff MediumPurple1
      796. +
      797. #afaf00 Gold3
      798. +
      799. #afaf5f DarkKhaki
      800. +
      801. #afaf87 NavajoWhite3
      802. +
      803. #afafaf Grey69
      804. +
      805. #afafd7 LightSteelBlue3
      806. +
      807. #afafff LightSteelBlue
      808. +
      809. #afd700 Yellow3
      810. +
      811. #afd75f DarkOliveGreen3
      812. +
      813. #afd787 DarkSeaGreen3
      814. +
      815. #afd7af DarkSeaGreen2
      816. +
      817. #afd7d7 LightCyan3
      818. +
      819. #afd7ff LightSkyBlue1
      820. +
      821. #afff00 GreenYellow
      822. +
      823. #afff5f DarkOliveGreen2
      824. +
      825. #afff87 PaleGreen1
      826. +
      827. #afffaf DarkSeaGreen2
      828. +
      829. #afffd7 DarkSeaGreen1
      830. +
      831. #afffff PaleTurquoise1
      832. +
      833. #d70000 Red3
      834. +
      835. #d7005f DeepPink3
      836. +
      837. #d70087 DeepPink3
      838. +
      839. #d700af Magenta3
      840. +
      841. #d700d7 Magenta3
      842. +
      843. #d700ff Magenta2
      844. +
      845. #d75f00 DarkOrange3
      846. +
      847. #d75f5f IndianRed
      848. +
      849. #d75f87 HotPink3
      850. +
      851. #d75faf HotPink2
      852. +
      853. #d75fd7 Orchid
      854. +
      855. #d75fff MediumOrchid1
      856. +
      857. #d78700 Orange3
      858. +
      859. #d7875f LightSalmon3
      860. +
      861. #d78787 LightPink3
      862. +
      863. #d787af Pink3
      864. +
      865. #d787d7 Plum3
      866. +
      867. #d787ff Violet
      868. +
      869. #d7af00 Gold3
      870. +
      871. #d7af5f LightGoldenrod3
      872. +
      873. #d7af87 Tan
      874. +
      875. #d7afaf MistyRose3
      876. +
      877. #d7afd7 Thistle3
      878. +
      879. #d7afff Plum2
      880. +
      881. #d7d700 Yellow3
      882. +
      883. #d7d75f Khaki3
      884. +
      885. #d7d787 LightGoldenrod2
      886. +
      887. #d7d7af LightYellow3
      888. +
      889. #d7d7d7 Grey84
      890. +
      891. #d7d7ff LightSteelBlue1
      892. +
      893. #d7ff00 Yellow2
      894. +
      895. #d7ff5f DarkOliveGreen1
      896. +
      897. #d7ff87 DarkOliveGreen1
      898. +
      899. #d7ffaf DarkSeaGreen1
      900. +
      901. #d7ffd7 Honeydew2
      902. +
      903. #d7ffff LightCyan1
      904. +
      905. #ff0000 Red1
      906. +
      907. #ff005f DeepPink2
      908. +
      909. #ff0087 DeepPink1
      910. +
      911. #ff00af DeepPink1
      912. +
      913. #ff00d7 Magenta2
      914. +
      915. #ff00ff Magenta1
      916. +
      917. #ff5f00 OrangeRed1
      918. +
      919. #ff5f5f IndianRed1
      920. +
      921. #ff5f87 IndianRed1
      922. +
      923. #ff5faf HotPink
      924. +
      925. #ff5fd7 HotPink
      926. +
      927. #ff5fff MediumOrchid1
      928. +
      929. #ff8700 DarkOrange
      930. +
      931. #ff875f Salmon1
      932. +
      933. #ff8787 LightCoral
      934. +
      935. #ff87af PaleVioletRed1
      936. +
      937. #ff87d7 Orchid2
      938. +
      939. #ff87ff Orchid1
      940. +
      941. #ffaf00 Orange1
      942. +
      943. #ffaf5f SandyBrown
      944. +
      945. #ffaf87 LightSalmon1
      946. +
      947. #ffafaf LightPink1
      948. +
      949. #ffafd7 Pink1
      950. +
      951. #ffafff Plum1
      952. +
      953. #ffd700 Gold1
      954. +
      955. #ffd75f LightGoldenrod2
      956. +
      957. #ffd787 LightGoldenrod2
      958. +
      959. #ffd7af NavajoWhite1
      960. +
      961. #ffd7d7 MistyRose1
      962. +
      963. #ffd7ff Thistle1
      964. +
      965. #ffff00 Yellow1
      966. +
      967. #ffff5f LightGoldenrod1
      968. +
      969. #ffff87 Khaki1
      970. +
      971. #ffffaf Wheat1
      972. +
      973. #ffffd7 Cornsilk1
      974. +
      975. #ffffff Grey100
      976. +
      977. #080808 Grey3
      978. +
      979. #121212 Grey7
      980. +
      981. #1c1c1c Grey11
      982. +
      983. #262626 Grey15
      984. +
      985. #303030 Grey19
      986. +
      987. #3a3a3a Grey23
      988. +
      989. #444444 Grey27
      990. +
      991. #4e4e4e Grey30
      992. +
      993. #585858 Grey35
      994. +
      995. #626262 Grey39
      996. +
      997. #6c6c6c Grey42
      998. +
      999. #767676 Grey46
      1000. +
      1001. #808080 Grey50
      1002. +
      1003. #8a8a8a Grey54
      1004. +
      1005. #949494 Grey58
      1006. +
      1007. #9e9e9e Grey62
      1008. +
      1009. #a8a8a8 Grey66
      1010. +
      1011. #b2b2b2 Grey70
      1012. +
      1013. #bcbcbc Grey74
      1014. +
      1015. #c6c6c6 Grey78
      1016. +
      1017. #d0d0d0 Grey82
      1018. +
      1019. #dadada Grey85
      1020. +
      1021. #e4e4e4 Grey89
      1022. +
      1023. #eeeeee Grey93
      From c8c4254610c897e4f5f6f78e5355566cc91e67ba Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sun, 19 Jul 2015 22:03:54 -0500 Subject: [PATCH 27/80] Added better docs, more shifts, hex shortcut, print, endline support, __main__ restorer --- docs/_cheatsheet.rst | 15 +++++--- docs/color.rst | 79 ++++++++++++++++++++++++++------------ plumbum/color/__main__.py | 12 ++++++ plumbum/color/factories.py | 4 ++ plumbum/color/names.py | 2 +- plumbum/color/styles.py | 78 ++++++++++++++++++++++--------------- tests/test_color.py | 13 +++++++ 7 files changed, 140 insertions(+), 63 deletions(-) create mode 100644 plumbum/color/__main__.py diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index 5f416b715..f114c31bc 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -108,17 +108,22 @@ Sample output:: **Color controls**:: - from plumbum import COLOR with COLOR.RED: - print("Warning! I said " + COLOR.BOLD("WARNING") + "!") - print("That was an", COLOR.GREEN + "important" - COLOR.GREEN, "warning!") + print("This library provides safe, flexible color access.") + print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") + print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') + print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") Sample output: .. raw:: html
      -
      Warning! I said WARNING!
      -    That was an important warning!
      + +
      This library provides safe color access.
      +    Color (and styles in general) are easy!
      +    The simple 8 colors or All 265 can be used.
      +    Unsafe color access is available too.
      +
      diff --git a/docs/color.rst b/docs/color.rst index 02a3fb7f3..417fd4d51 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -1,13 +1,13 @@ .. _guide-color: Color tools -=========== +----------- .. versionadded:: 1.6.0 The purpose of the `plumbum.color` library is to make adding -color to Python easy and safe. Color is often a great +text styles (such as color) to Python easy and safe. Color is often a great addition to shell scripts, but not a necessity, and implementing it properly is tricky. It is easy to end up with an unreadable color stuck on your terminal or with random unreadable symbols around your text. With the color module, you get quick, @@ -16,15 +16,14 @@ API for creating other color schemes for other systems using escapes. .. note:: Enabling color - The ANSI Style assumes that only a terminal on a posix-identity + ``ANSIStyle`` assumes that only a terminal on a posix-identity system can display color. You can force the use of color globally by setting ``COLOR.use_color=True``. +The Color Factory +================= -Color Factory -============= - -Colors are accessed through the ``COLOR`` object, which is an instance of a StyleFactory. +Styles are accessed through the ``COLOR`` object, which is an instance of a StyleFactory. The ``COLOR`` object has the following properties: ``FG`` and ``BG`` @@ -33,7 +32,8 @@ The ``COLOR`` object has the following properties: directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, ``BLUE``, ``MAGENTA``, ``CYAN``, ``WHITE``, as well as ``RESET``, are available. You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, - and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. + and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. These + are ``ColorFactory`` instances. ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` All the `ANSI` modifiers are available, as well as their negations, such as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. ``RESET`` @@ -46,10 +46,26 @@ the statement body. (The ``FG`` and ``BG`` also can be put in with statements, a will restore the foreground and background color, respectively). Although it does support some of the same things as a Style, its primary purpose is to generate Styles. +If you call ``COLOR.from_ansi(seq)``, you can manually pass in any `ANSI` escape sequence, +even complex or combined ones. ``COLOR.rgb(r,g,b)`` will find the closest color from an +input red, green, and blue values (integers from 0-255). ``COLOR.hex(code)`` will allow +you to input an html style hex sequence. These work on ``FG`` and ``BG`` too. -Unsafe Manipulation +Style manipulations =================== +Safe color manipulations refer to changes that reset themselves at some point. Unsafe manipulations +must be manually reset, and can leave your terminal color in an unreadable state if you forget +to reset the color or encounter an exception. If you do get the color unset on a terminal, the +following, typed into the command line, will restore it:: + +.. code:: bash + + $ python -m plumbum.color + +Unsafe Manipulation +^^^^^^^^^^^^^^^^^^^ + Styles have two unsafe operations: Concatenation (with ``+``) and calling ``.now()`` without arguments (directly calling a style is also a shortcut for ``.now``). These two operations do not restore normal color to the terminal. To protect their use, @@ -84,23 +100,33 @@ accessed with ``~``, ``-``, or ``.RESET``, and can be used to manually make thes safer, but there is a better way. Safe Manipulation -================= +^^^^^^^^^^^^^^^^^ All other operations are safe; they restore the color automatically. The first, and hopefully already obvious one, is using a Style rather than a ``COLOR`` or ``COLOR.FG`` object in a ``with`` statement. This will set the color (using sys.stdout by default) to that color, and restore color on leaving. The second method is to manually wrap a string. This can be done with ``color.wrap("string")``, -``"string" << color``, ``color << "string"``, ``color["string"]``, or ``color("string")``. These -produce strings that can be further manipulated or printed. +``"string" << color``, ``color >> "string"``, or ``color["string"]``. +These produce strings that can be further manipulated or printed. + +.. note:: -Finally, you can also print a color to stdout directly using ``color.now("string")`` or -``color.line("string")``. The second method will add a newline, like print. Multiple arguments -can be passed; they will be concatenated with spaces. + ``color * "string"`` is also a valid way to wrap strings and has a well understood order of + operations by most people writing or reading code. Under some conditions, having an operator + that takes preference over concatination is prefered. However, a bug in Python 2.6 causes right + multiplication with a string, such as ``"string" * color``, to be impossible to implement. + This was fixed in all newer Pythons. If you are not planning on `supporting Python + 2.6 http://www.curiousefficiency.org/posts/2015/04/stop-supporting-python26.html>`_, feel + free to use this method. + +Finally, you can also print a color to stdout directly using ``color("string")``. This +has the same syntax as the Python 3 print function. An example of safe manipulations:: - print(COLOR.FG.YELLOW('This is yellow') + ' And this is normal again.') + COLOR.FG.YELLOW('This is yellow', end='') + print(' And this is normal again.') with COLOR.RED: print('Red color!') with COLOR.BOLD: @@ -120,10 +146,10 @@ Output:
      Not red color or bold.
      This is bold and colorful! And this is not.

      -If you call ``COLOR.from_ansi(...)``, you can manually pass in any `ANSI` escape sequence. + 256 Color Support ------------------ +================= The library support 256 colors through numbers, names or HEX html codes. You can access them as ``COLOR.FG[12]``, ``COLOR.FG['Light_Blue']``, ``COLOR.FG['LightBlue']``, or ``COLOR.FG['#0000FF']``. The supported colors are: @@ -131,8 +157,9 @@ as ``COLOR.FG[12]``, ``COLOR.FG['Light_Blue']``, ``COLOR.FG['LightBlue']``, or ` .. raw:: html :file: _color_list.html + The Classes ------------ +=========== The library consists of three primary classes, the ``Color`` class, the ``Style`` class, and the ``StyleFactory`` class. The following portion of this document is primarily dealing with the working of the system, and is meant to facilitate extensions or work on the system. @@ -154,12 +181,13 @@ you need to initialize an object of ``StyleFactory`` with your intended Style. F COLOR = StyleFactory(ANSIStyle) HTML Subclass Example ---------------------- +^^^^^^^^^^^^^^^^^^^^^ For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: class HTMLStyle(Style): attribute_names = dict(bold='b', em='em', li='li', code='code') + end = '
      ' def __str__(self): result = '' @@ -197,8 +225,8 @@ The above color table can be generated with:: with open('_color_list.html', 'wt') as f: with HTMLCOLOR.OL: for color in HTMLCOLOR: - HTMLCOLOR.LI.line( - "■" << color, + HTMLCOLOR.LI( + "■" << color, color.fg.html_hex_code << HTMLCOLOR.CODE, color.fg.name_camelcase) @@ -208,10 +236,11 @@ The above color table can be generated with:: ``HTMLStyle`` is implemented in the library, as well, with the ``HTMLCOLOR`` object available in ``plumbum.color``. It was used to create the colored output in this document, with small changes - because unsafe manipulations are not supported with HTML. + because ``COLOR.RESET`` cannot supported with HTML. See Also --------- +======== + * `colored `_ Another library with 256 color support * `colorama `_ A library that supports colored text on Windows, - can be combined with Plumbum (if you force ``use_color``) + can be combined with Plumbum.color (if you force ``use_color``) diff --git a/plumbum/color/__main__.py b/plumbum/color/__main__.py new file mode 100644 index 000000000..bc9be7940 --- /dev/null +++ b/plumbum/color/__main__.py @@ -0,0 +1,12 @@ +""" +This is provided as a quick way to recover your terminal. Simply run +``python -m plumbum.color`` +to recover terminal color. +""" + + +from plumbum.color import COLOR + +if __name__ == '__main__': + COLOR.use_color=True + COLOR.RESET() diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 3bd4815ef..a24abf530 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -42,6 +42,10 @@ def rgb(self, r, g, b): """Return the extended color scheme color for a value.""" return self._style.from_color(self._style.color_class(r, g, b, fg=self._fg)) + def hex(self, hexcode): + """Return the extended color scheme color for a value.""" + return self._style.from_color(self._style.color_class.from_hex(hexcode, fg=self._fg)) + def __getitem__(self, val): """Shortcut to provide way to access extended colors.""" diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 59a149497..9cbc2caa6 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -333,7 +333,7 @@ def find_nearest_simple_color(r, g, b): def from_html(color): - if len(color) != 7: + if len(color) != 7 or color[0] != '#': raise ValueError("Invalid length of html code") return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 538cca3d8..2b9fc17a8 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -68,7 +68,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): try: self._from_full(r_or_color) except ColorNotFound: - self._from_html(r_or_color) + self._from_hex(r_or_color) elif None not in (r_or_color, g, b): @@ -147,14 +147,14 @@ def _from_full(self, color): self._init_number() @classmethod - def from_html(cls, color, fg=True): + def from_hex(cls, color, fg=True): """Converts #123456 values to colors.""" self = cls(fg=fg) - self._from_html(color) + self._from_hex(color) return self - def _from_html(self, color): + def _from_hex(self, color): try: self.rgb = from_html(color) except (TypeError, ValueError): @@ -246,6 +246,7 @@ class Style(object): color_class = Color attribute_names = valid_attributes # a set of valid names _stdout = None + end = '\n' @property def stdout(self): @@ -319,6 +320,7 @@ def __neg__(self): """This negates the effect of the current color""" return self.invert() __invert__ = __neg__ + """This allows ~color == -color.""" def __sub__(self, other): """Implemented to make muliple Style objects work""" @@ -334,7 +336,8 @@ def __add__(self, other): the string concatiation of a style. Addition is non-communitive, with the rightmost Style property - being taken if both have the same property.""" + being taken if both have the same property. + (Not safe)""" if type(self) == type(other): result = copy(other) @@ -351,7 +354,7 @@ def __add__(self, other): return other.__class__(self) + other def __radd__(self, other): - """This only gets called if the string is on the left side.""" + """This only gets called if the string is on the left side. (Not safe)""" return other + other.__class__(self) def wrap(self, wrap_this): @@ -366,44 +369,54 @@ def __mul__(self, other): else: return self.wrap(other) + __rmul__ = wrap + """This class supports ``"String:" * color`` syntax, excpet in Python 2.6 due to bug with that Python.""" + + __rlshift__ = wrap + """This class supports ``"String:" << color`` syntax""" + __lshift__ = __mul__ """This class supports ``color << color2`` syntax. It also supports ``"color << "String"`` syntax too. """ - __rlshift__ = wrap - """This class supports ``"String:" << color`` syntax""" + __rrshift__ = wrap + """This class supports ``"String:" >> color`` syntax""" + + __rshift__ = __mul__ + """This class supports ``color >> "String"`` syntax. It also supports + ``"color >> color2`` syntax too. """ - __rmul__ = wrap - """This class supports ``"String:" * color`` syntax. Buggy in some versions of python""" - def now(self, *printable): + def __call__(self, wrap_this = None): """\ - This is a shortcut to print color immediatly to the stdout. - If called without arguments, this will change the Style immediatly. - If called with an argument, will print that argument to stdout wrapped - in Style.""" + This is a shortcut to print color immediatly to the stdout. (Not safe) + If called with an argument, will wrap that argument.""" - if printable: - self.stdout.write(self.wrap(' '.join(map(str,printable)))) + if wrap_this is not None: + return self.wrap(wrap_this) else: - self.stdout.write(str(self)) + self.now() + + def now(self): + '''Immediatly writes color to stdout. (Not safe)''' + self.stdout.write(str(self)) - def line(self, *printable): + def print(*printables, **kargs): """\ - This will print out a line of colored text. Similar to .now, except for - printing a newline at the end.""" - if printable: - self.stdout.write(self.wrap(' '.join(map(str, printable))) + '\n') - else: - self.stdout.write(str(self) + '\n') + This acts like print; will print that argument to stdout wrapped + in Style with the same syntax as the print function in 3.4.""" - def __call__(self, *printable): - """Without arguments, this will change the current stdout color instantly. - With arguments, they will be wrapped in style and returned.""" - if printable: - return self.wrap(*printable) - else: - self.now() + end = kargs.get('end', self.end) + sep = kargs.get('sep', ' ') + file = kargs.get('file', self.stdout) + flush = kargs.get('flush', False) + file.write(self.wrap(sep.join(map(str,printables))) + end) + if flush: + file.flush() + + + print_ = print + """Shortcut just in case user not using __future__""" def __getitem__(self, wrap_this): """The [] syntax is supported for wrapping""" @@ -541,6 +554,7 @@ class HTMLStyle(Style): actually can be a handy way to quicky color html text.""" attribute_names = dict(bold='b', em='em', li='li', underline='span style="text-decoration: underline;"', code='code', ol='ol start=0') + end = '
      \n' def __str__(self): diff --git a/tests/test_color.py b/tests/test_color.py index f4cbd9fc5..5f597232b 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -3,6 +3,7 @@ from plumbum import COLOR from plumbum.color.styles import ANSIStyle as Style from plumbum.color import HTMLCOLOR +import sys class TestColor(unittest.TestCase): @@ -19,10 +20,22 @@ def testNegateIsReset(self): self.assertEqual(COLOR.FG.RESET, -COLOR.FG) self.assertEqual(COLOR.BG.RESET, -COLOR.BG) + def testShifts(self): + self.assertEqual("This" << COLOR.RED, "This" >> COLOR.RED) + self.assertEqual("This" << COLOR.RED, "This" << COLOR.RED) + if sys.version_info >= (2, 7): + self.assertEqual("This" << COLOR.RED, "This" * COLOR.RED) + self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") + self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") + self.assertEqual("This" << COLOR.RED, COLOR.RED * "This") + self.assertEqual(COLOR.RED.wrap("This"), "This" << COLOR.RED) + def testLoadColorByName(self): self.assertEqual(COLOR['LightBlue'], COLOR.FG['LightBlue']) self.assertEqual(COLOR.BG['light_green'], COLOR.BG['LightGreen']) self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR.hex('#00afff')) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) def testMultiColor(self): From 02ef52fb74a1ccdd98ab1e63f53ead8f2ce71282 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Sun, 19 Jul 2015 22:05:14 -0500 Subject: [PATCH 28/80] Type fix --- docs/color.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/color.rst b/docs/color.rst index 417fd4d51..93d4e83cc 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -117,7 +117,7 @@ These produce strings that can be further manipulated or printed. that takes preference over concatination is prefered. However, a bug in Python 2.6 causes right multiplication with a string, such as ``"string" * color``, to be impossible to implement. This was fixed in all newer Pythons. If you are not planning on `supporting Python - 2.6 http://www.curiousefficiency.org/posts/2015/04/stop-supporting-python26.html>`_, feel + 2.6 `_, feel free to use this method. Finally, you can also print a color to stdout directly using ``color("string")``. This From 4f5d24a102125bf6194714e70c53e459e0f0c95e Mon Sep 17 00:00:00 2001 From: Henry MuonOne Date: Mon, 20 Jul 2015 09:31:42 -0500 Subject: [PATCH 29/80] Fixed .print error. --- plumbum/color/styles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 2b9fc17a8..f4bd4e81a 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -401,7 +401,7 @@ def now(self): '''Immediatly writes color to stdout. (Not safe)''' self.stdout.write(str(self)) - def print(*printables, **kargs): + def print(self, *printables, **kargs): """\ This acts like print; will print that argument to stdout wrapped in Style with the same syntax as the print function in 3.4.""" From 15e29067b9fe2bc65b669a9c100cd3c921a964b8 Mon Sep 17 00:00:00 2001 From: Henry MuonOne Date: Mon, 20 Jul 2015 09:32:57 -0500 Subject: [PATCH 30/80] Added test, example of color on Windows. The test/example tries to import colorama, doesn't change behavior if not found. --- examples/geet.py | 8 ++++++++ tests/test_color.py | 31 ++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/examples/geet.py b/examples/geet.py index 016024b65..fea6b68ba 100644 --- a/examples/geet.py +++ b/examples/geet.py @@ -44,6 +44,14 @@ # from plumbum.color import Style # Style.use_color = False +try: + import colorama + colorama.init() + from plumbum import COLOR + COLOR.use_color = True +except ImportError: + pass + class Geet(cli.ColorfulApplication): """The l33t version control""" PROGNAME = "geet" diff --git a/tests/test_color.py b/tests/test_color.py index 5f597232b..5cb710564 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -5,10 +5,10 @@ from plumbum.color import HTMLCOLOR import sys -class TestColor(unittest.TestCase): +class TestANSIColor(unittest.TestCase): def setUp(self): - Style.use_color = True + COLOR.use_color = True def testColorStrings(self): self.assertEqual('\033[0m', COLOR.RESET) @@ -103,7 +103,26 @@ def testLackOfColor(self): self.assertEqual('', -COLOR.FG) self.assertEqual('', COLOR.FG['LightBlue']) - def testVisualColors(self): + + +class TestVisualColor(unittest.TestCase): + + def setUp(self): + try: + import colorama + colorama.init() + self.colorama = colorama + COLOR.use_color = True + print() + print("Colorama initialized") + except ImportError: + self.colorama = None + + def tearDown(self): + if self.colorama: + self.colorama.deinit() + + def testVisualColors(self): print() for c in (COLOR.FG(x) for x in range(1, 6)): with c: @@ -114,14 +133,16 @@ def testVisualColors(self): + COLOR.BOLD + "Bold " - COLOR.BOLD + "Normal") print("Reset all") - + def testToggleColors(self): print() - print(COLOR.FG.RED("this is in red"), "but this is not") + print(COLOR.FG.RED("This is in red"), "but this is not") print(COLOR.FG.GREEN + "Hi, " + COLOR.BG[23] + "This is on a BG" - COLOR.BG + " and this is not") + COLOR.YELLOW.print("This is printed from color.") COLOR.RESET() +class TestHTMLColor(unittest.TestCase): def test_html(self): red_tagged = 'This is tagged' self.assertEqual(HTMLCOLOR.RED("This is tagged"), red_tagged) From 570d88bac3d468b4271d3e79e0545c1ea7827ae1 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 09:38:26 -0500 Subject: [PATCH 31/80] Making examples executable directly. --- examples/alignment.py | 7 ++++--- examples/color.py | 1 + examples/filecopy.py | 1 + examples/geet.py | 1 + examples/make_figures.py | 0 examples/simple_cli.py | 7 ++++--- 6 files changed, 11 insertions(+), 6 deletions(-) mode change 100644 => 100755 examples/alignment.py mode change 100644 => 100755 examples/color.py mode change 100644 => 100755 examples/filecopy.py mode change 100644 => 100755 examples/geet.py mode change 100644 => 100755 examples/make_figures.py mode change 100644 => 100755 examples/simple_cli.py diff --git a/examples/alignment.py b/examples/alignment.py old mode 100644 new mode 100755 index 26720dd76..a3bed747a --- a/examples/alignment.py +++ b/examples/alignment.py @@ -1,17 +1,18 @@ +#!/usr/bin/env python from plumbum import cli class App(cli.Application): #VERSION = "1.2.3" #x = cli.SwitchAttr("--lala") y = cli.Flag("-f") - + def main(self, x, y): pass - + @App.subcommand("bar") class Bar(cli.Application): z = cli.Flag("-z") - + def main(self, z, w): pass diff --git a/examples/color.py b/examples/color.py old mode 100644 new mode 100755 index 98010060e..4782fa47d --- a/examples/color.py +++ b/examples/color.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python from __future__ import with_statement, print_function from plumbum import COLOR diff --git a/examples/filecopy.py b/examples/filecopy.py old mode 100644 new mode 100755 index c264871a5..1f26546ee --- a/examples/filecopy.py +++ b/examples/filecopy.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python import logging from plumbum import cli, local from plumbum.path.utils import delete, copy diff --git a/examples/geet.py b/examples/geet.py old mode 100644 new mode 100755 index fea6b68ba..7213817c1 --- a/examples/geet.py +++ b/examples/geet.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ Examples:: diff --git a/examples/make_figures.py b/examples/make_figures.py old mode 100644 new mode 100755 diff --git a/examples/simple_cli.py b/examples/simple_cli.py old mode 100644 new mode 100755 index 7b22a0569..bda2c1db6 --- a/examples/simple_cli.py +++ b/examples/simple_cli.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python """ $ python simple_cli.py --help simple_cli.py v1.0 @@ -40,15 +41,15 @@ class MyCompiler(cli.Application): verbose = cli.Flag(["-v", "--verbose"], help = "Enable verbose mode") include_dirs = cli.SwitchAttr("-I", list = True, help = "Specify include directories") - + @cli.switch("-loglevel", int) def set_log_level(self, level): """Sets the log-level of the logger""" logging.root.setLevel(level) - + def main(self, *srcfiles): print "Verbose:", self.verbose - print "Include dirs:", self.include_dirs + print "Include dirs:", self.include_dirs print "Compiling:", srcfiles From 5b8f86e95e602343edf3546c10f5e52ab3a7b86f Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 10:54:56 -0500 Subject: [PATCH 32/80] Better tests, updated docs to match current status of library --- README.rst | 9 ++++++ docs/_cheatsheet.rst | 9 +++--- docs/color.rst | 24 ++++++++------ tests/test_color.py | 64 ++++++++++++++++---------------------- tests/test_visual_color.py | 44 ++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 51 deletions(-) create mode 100644 tests/test_visual_color.py diff --git a/README.rst b/README.rst index 3437a15ac..b3f2e6169 100644 --- a/README.rst +++ b/README.rst @@ -129,6 +129,15 @@ Sample output:: Include dirs: ['foo/bar', 'spam/eggs'] Compiling: ('x.cpp', 'y.cpp', 'z.cpp') +**Color controls** :: + + from plumbum import COLOR + with COLOR.RED: + print("This library provides safe, flexible color access.") + print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") + print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') + print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") + .. image:: https://d2weczhvl823v0.cloudfront.net/tomerfiliba/plumbum/trend.png diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index f114c31bc..90eb12813 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -106,13 +106,14 @@ Sample output:: Include dirs: ['foo/bar', 'spam/eggs'] Compiling: ('x.cpp', 'y.cpp', 'z.cpp') -**Color controls**:: +**Color controls** :: + from plumbum import COLOR with COLOR.RED: print("This library provides safe, flexible color access.") - print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') - print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") + print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") + print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') + print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") Sample output: diff --git a/docs/color.rst b/docs/color.rst index 93d4e83cc..f698f8fb7 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -57,7 +57,7 @@ Style manipulations Safe color manipulations refer to changes that reset themselves at some point. Unsafe manipulations must be manually reset, and can leave your terminal color in an unreadable state if you forget to reset the color or encounter an exception. If you do get the color unset on a terminal, the -following, typed into the command line, will restore it:: +following, typed into the command line, will restore it: .. code:: bash @@ -66,9 +66,9 @@ following, typed into the command line, will restore it:: Unsafe Manipulation ^^^^^^^^^^^^^^^^^^^ -Styles have two unsafe operations: Concatenation (with ``+``) and calling ``.now()`` without +Styles have two unsafe operations: Concatenation (with ``+`` and a string) and calling ``.now()`` without arguments (directly calling a style is also a shortcut for ``.now``). These two -operations do not restore normal color to the terminal. To protect their use, +operations do not restore normal color to the terminal by themselves. To protect their use, you should always use a context manager around any unsafe operation. An example of the usage of unsafe ``COLOR`` manipulations inside a context manager:: @@ -120,8 +120,10 @@ These produce strings that can be further manipulated or printed. 2.6 `_, feel free to use this method. -Finally, you can also print a color to stdout directly using ``color("string")``. This -has the same syntax as the Python 3 print function. +Finally, you can also print a color to stdout directly using ``color.print("string")``. This +has the same syntax as the Python 3 print function. In Python 2, if you do not have +``from __future__ import print_function`` enabled, ``color.print_("string")`` is provided as +an alternative, following the PyQT convention for method names that match reserved Python syntax. An example of safe manipulations:: @@ -146,7 +148,11 @@ Output:
      Not red color or bold.
      This is bold and colorful! And this is not.

      +Style Combinations +^^^^^^^^^^^^^^^^^^ +You can combine styles with ``+``, ``*``, ``<<``, or ``>>``, and they will create a new combined Style object. Colors will not be "summed" or otherwise combined; the rightmost color will be used (this matches the expected effect of +applying the Styles individually to the strings). However, combined Styles are intelligent and know how to reset just the properties that they contain. As you have seen in the example above, the combined style ``(COLOR.MAGENTA + COLOR.BOLD)`` can be used in any way a normal Style can. 256 Color Support ================= @@ -180,14 +186,14 @@ you need to initialize an object of ``StyleFactory`` with your intended Style. F COLOR = StyleFactory(ANSIStyle) -HTML Subclass Example -^^^^^^^^^^^^^^^^^^^^^ +Subclassing Style +^^^^^^^^^^^^^^^^^ For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: class HTMLStyle(Style): attribute_names = dict(bold='b', em='em', li='li', code='code') - end = '
      ' + end = '
      \n' def __str__(self): result = '' @@ -236,7 +242,7 @@ The above color table can be generated with:: ``HTMLStyle`` is implemented in the library, as well, with the ``HTMLCOLOR`` object available in ``plumbum.color``. It was used to create the colored output in this document, with small changes - because ``COLOR.RESET`` cannot supported with HTML. + because ``COLOR.RESET`` cannot be supported with HTML. See Also ======== diff --git a/tests/test_color.py b/tests/test_color.py index 5cb710564..a01755b3e 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,7 +1,8 @@ +#!/usr/bin/env python from __future__ import with_statement, print_function import unittest from plumbum import COLOR -from plumbum.color.styles import ANSIStyle as Style +from plumbum.color.styles import ANSIStyle as Style, ColorNotFound from plumbum.color import HTMLCOLOR import sys @@ -103,44 +104,31 @@ def testLackOfColor(self): self.assertEqual('', -COLOR.FG) self.assertEqual('', COLOR.FG['LightBlue']) + def testFromHex(self): + self.assertEqual(str(COLOR.hex('#020201')),str(COLOR.hex('#020202'))) + self.assertRaises(ColorNotFound, lambda: COLOR.hex('asdf')) + self.assertRaises(ColorNotFound, lambda: COLOR.hex('#1234Z2')) + self.assertRaises(ColorNotFound, lambda: COLOR.hex(12)) - -class TestVisualColor(unittest.TestCase): + def testDirectCall(self): + COLOR.BLUE() + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str(COLOR.BLUE)) + + + def testPrint(self): + COLOR.YELLOW.print('This is printed to stdout') + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str(COLOR.YELLOW('This is printed to stdout'))) - def setUp(self): - try: - import colorama - colorama.init() - self.colorama = colorama - COLOR.use_color = True - print() - print("Colorama initialized") - except ImportError: - self.colorama = None - - def tearDown(self): - if self.colorama: - self.colorama.deinit() - - def testVisualColors(self): - print() - for c in (COLOR.FG(x) for x in range(1, 6)): - with c: - print('Cycle color test', end=' ') - print(' - > back to normal') - with COLOR: - print(COLOR.FG.GREEN + "Green " - + COLOR.BOLD + "Bold " - - COLOR.BOLD + "Normal") - print("Reset all") - - def testToggleColors(self): - print() - print(COLOR.FG.RED("This is in red"), "but this is not") - print(COLOR.FG.GREEN + "Hi, " + COLOR.BG[23] - + "This is on a BG" - COLOR.BG + " and this is not") - COLOR.YELLOW.print("This is printed from color.") - COLOR.RESET() class TestHTMLColor(unittest.TestCase): def test_html(self): @@ -156,4 +144,4 @@ def test_html(self): self.assertEqual(HTMLCOLOR.RED << "This should be wrapped", "This should be wrapped" << HTMLCOLOR.RED) if __name__ == '__main__': - unittest.main() + unittest.main(buffer=True) diff --git a/tests/test_visual_color.py b/tests/test_visual_color.py new file mode 100644 index 000000000..7da468981 --- /dev/null +++ b/tests/test_visual_color.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +from __future__ import with_statement, print_function +import unittest +from plumbum import COLOR + +class TestVisualColor(unittest.TestCase): + + def setUp(self): + try: + import colorama + colorama.init() + self.colorama = colorama + COLOR.use_color = True + print() + print("Colorama initialized") + except ImportError: + self.colorama = None + + def tearDown(self): + if self.colorama: + self.colorama.deinit() + + def testVisualColors(self): + print() + for c in (COLOR.FG(x) for x in range(1, 6)): + with c: + print('Cycle color test', end=' ') + print(' - > back to normal') + with COLOR: + print(COLOR.FG.GREEN + "Green " + + COLOR.BOLD + "Bold " + - COLOR.BOLD + "Normal") + print("Reset all") + + def testToggleColors(self): + print() + print(COLOR.FG.RED("This is in red"), "but this is not") + print(COLOR.FG.GREEN + "Hi, " + COLOR.BG[23] + + "This is on a BG" - COLOR.BG + " and this is not") + COLOR.YELLOW.print("This is printed from color.") + COLOR.RESET() + +if __name__ == '__main__': + unittest.main() From b9378f8be4f323e1cd6e5bea712cf66d302c8e2b Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 14:19:56 -0500 Subject: [PATCH 33/80] Added IPython extension --- plumbum/color/__init__.py | 11 +++++++++++ plumbum/color/_ipython_ext.py | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 plumbum/color/_ipython_ext.py diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py index 977793f5a..201fb8d20 100644 --- a/plumbum/color/__init__.py +++ b/plumbum/color/__init__.py @@ -9,3 +9,14 @@ COLOR = StyleFactory(ANSIStyle) HTMLCOLOR = StyleFactory(HTMLStyle) + +def load_ipython_extension(ipython): + try: + from plumbum.color._ipython_ext import OutputMagics + except ImportError: + print("IPython required for the IPython extension to be loaded.") + raise + + ipython.push({"COLOR":HTMLCOLOR}) + ipython.register_magics(OutputMagics) + diff --git a/plumbum/color/_ipython_ext.py b/plumbum/color/_ipython_ext.py new file mode 100644 index 000000000..dd33149c7 --- /dev/null +++ b/plumbum/color/_ipython_ext.py @@ -0,0 +1,36 @@ +from IPython.core.magic import (Magics, magics_class, + cell_magic, needs_local_scope) +import IPython.display + +try: + from io import StringIO +except ImportError: + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO +import sys + +valid_choices = [x[8:] for x in dir(IPython.display) if 'display_' == x[:8]] + +@magics_class +class OutputMagics(Magics): + + @needs_local_scope + @cell_magic + def to(self, line, cell, local_ns=None): + choice = line.strip() + assert choice in valid_choices, "Valid choices for '%%to' are: "+str(valid_choices) + display_fn = getattr(IPython.display, "display_"+choice) + + "Captures stdout and renders it in the notebook with some ." + with StringIO() as out: + old_out = sys.stdout + try: + sys.stdout = out + exec(cell, self.shell.user_ns, local_ns) + out.seek(0) + display_fn(out.getvalue(), raw=True) + finally: + sys.stdout = old_out + From 16c5ba7aa9ec1a5da238d4aa053d1b17b06131d4 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 16:29:22 -0500 Subject: [PATCH 34/80] Added more accurate, 16 color inclusive color for simple color, extended to use 24 bit ANSI color when available. --- plumbum/color/factories.py | 15 +++++--- plumbum/color/names.py | 67 +++++++++++----------------------- plumbum/color/styles.py | 75 +++++++++++++++++++++----------------- tests/test_color.py | 7 ++-- 4 files changed, 76 insertions(+), 88 deletions(-) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index a24abf530..9ec327e47 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -8,7 +8,7 @@ import os from functools import partial from contextlib import contextmanager -from plumbum.color.names import color_names_simple +from plumbum.color.names import color_names __all__ = ['ColorFactory', 'StyleFactory'] @@ -24,14 +24,13 @@ def __init__(self, fg, style): self.RESET = style.from_color(style.color_class(fg=fg)) # Adding the color name shortcuts for forground colors - for item in color_names_simple: + for item in color_names[:16]: setattr(self, item.upper(), style.from_color(style.color_class.from_simple(item, fg=fg))) def full(self, name): """Gets the style for a color, using standard name procedure: either full color name, html code, or number.""" -# TODO: add html to int conversion, so that all HTML colors work return self._style.from_color(self._style.color_class(name, fg=self._fg)) def simple(self, name): @@ -49,11 +48,17 @@ def hex(self, hexcode): def __getitem__(self, val): """Shortcut to provide way to access extended colors.""" - return self.full(val) + try: + return self.full(val) + except ColorNotFound: + return self.hex(val) def __call__(self, val): """Shortcut to provide way to access simple colors.""" - return self.simple(val) + try: + return self.simple(val) + except ColorNotFound: + return self.hex(val) def __iter__(self): """Iterates through all colors in extended colorset.""" diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 9cbc2caa6..22ab0d21a 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -1,19 +1,19 @@ ''' Names for the standard and extended color set. -Extended set is similar to http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim, https://pypi.python.org/pypi/colored, etc. +Extended set is similar to `vim wiki `_, `colored `_, etc. Colors based on `wikipedia `_. You can access the index of the colors with names.index(name). You can access the -rgb values with r=int(html[n][1:3],16), etc. +rgb values with ``r=int(html[n][1:3],16)``, etc. ''' _named_colors = '''\ 0,black,#000000 -1,red,#800000 -2,green,#008000 -3,yellow,#808000 -4,blue,#000080 -5,magenta,#800080 -6,cyan,#008080 +1,red,#c00000 +2,green,#00c000 +3,yellow,#c0c000 +4,blue,#0000c0 +5,magenta,#c000c0 +6,cyan,#00c0c0 7,light_gray,#c0c0c0 8,dark_gray,#808080 9,light_red,#ff0000 @@ -265,46 +265,22 @@ 255,grey_93,#eeeeee ''' -color_names_full = [n.split(',')[1] for n in _named_colors.split()] -color_html_full = [n.split(',')[2] for n in _named_colors.split()] +color_names = [n.split(',')[1] for n in _named_colors.split()] +color_html = [n.split(',')[2] for n in _named_colors.split()] -color_names_simple = [ - 'black', - 'red', - 'green', - 'yellow', - 'blue', - 'magenta', - 'cyan', - 'white', -] -"""Simple colors, remember that reset is #9""" +color_codes_simple = list(range(8)) + list(range(60,68)) +"""Simple colors, remember that reset is #9, second half is non as common.""" -color_html_simple = color_html_full[:7] + [color_html_full[15]] - -# Common segments of colors in full table -main_named_colors = slice(0,16) -normal_colors = slice(16,232) -grey_colors = slice(232,256) # Attributes - -valid_attributes = set(( - 'bold', - 'dim', - 'underline', - 'blink', - 'reverse', - 'hidden' - )) - attributes_ansi = dict( bold=1, dim=2, + italics=3, underline=4, - blink=5, reverse=7, - hidden=8 + hidden=8, + strikeout=9, ) #Functions to be used for color name operations @@ -314,9 +290,9 @@ def _distance_to_color(r, g, b, color): return (r-rgb[0])**2 + (g-rgb[1])**2 + (b-rgb[2])**2 -def find_nearest_color(r, g, b): +def find_nearest_color(r, g, b, color_slice=slice(None, None, None)): """This is a slow way to find the nearest color.""" - distances = [_distance_to_color(r, g, b, color) for color in color_html_full] + distances = [_distance_to_color(r, g, b, color) for color in color_html[color_slice]] return min(range(len(distances)), key=distances.__getitem__) def find_nearest_simple_color(r, g, b): @@ -331,6 +307,9 @@ def find_nearest_simple_color(r, g, b): # r*1 + g*2 + b*4 return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 +def find_nearest_colorblock(*rgb): + r, g, b = (round(v / 256. * 5) for v in rgb) + return (16 + 36 * r + 6 * g + b) def from_html(color): if len(color) != 7 or color[0] != '#': @@ -338,14 +317,12 @@ def from_html(color): return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) - - def print_html_table(): """Prints html names for documentation""" print(r'
        ') for i in range(256): - name = color_names_full[i] - val = color_html_full[i] + name = color_names[i] + val = color_html[i] print(r'
      1. ' + val + r' ' + name diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index f4bd4e81a..564852fa0 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -7,20 +7,19 @@ import os import re from copy import copy -from plumbum.color.names import color_names_full, color_html_full -from plumbum.color.names import color_names_simple, color_html_simple, valid_attributes, from_html +from plumbum.color.names import color_names, color_html +from plumbum.color.names import color_codes_simple, from_html from plumbum.color.names import find_nearest_color, find_nearest_simple_color, attributes_ansi __all__ = ['Color', 'Style', 'ANSIStyle', 'ColorNotFound', 'AttributeNotFound'] -_lower_camel_names = [n.replace('_', '') for n in color_names_full] +_lower_camel_names = [n.replace('_', '') for n in color_names] class ColorNotFound(Exception): pass - class AttributeNotFound(Exception): pass @@ -49,8 +48,8 @@ class Color(object): self.fg: Foreground if True, background if not self.reset: True it this is a reset color (following atts don't matter if True) self.rgb: The red/green/blue tuple for this color - self.simple: Simple 6 color mode - self.number: The color number given the mode, closest to rgb if not exact + self.simple: If true will stay to 16 color mode. + self.number: The color number given the mode, closest to rgb if not exact, gives closest name in full mode. """ @@ -79,10 +78,10 @@ def _init_number(self): """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" if self.simple: self.number = find_nearest_simple_color(*self.rgb) - self.exact = self.rgb == from_html(color_html_simple[self.number]) + self.exact = self.rgb == from_html(color_html[self.number]) else: self.number = find_nearest_color(*self.rgb) - self.exact = self.rgb == from_html(color_html_full[self.number]) + self.exact = self.rgb == from_html(color_html[self.number]) self.reset = False @@ -99,15 +98,15 @@ def _from_simple(self, color): except AttributeError: pass - if color == 'reset' or color==9: + if color == 'reset': return - elif color in color_names_simple: - self.rgb = from_html(color_html_simple[color_names_simple.index(color)]) + elif color in color_names[:16]: + self.rgb = from_html(color_html[color_names.index(color)]) self.simple = True - elif isinstance(color, int) and 0 <= color <= 7: - self.rgb = from_html(color_html_simple[color]) + elif isinstance(color, int) and 0 <= color < 16: + self.rgb = from_html(color_html[color]) self.simple = True else: @@ -132,14 +131,14 @@ def _from_full(self, color): if color == 'reset': return - elif color in color_names_full: - self.rgb = from_html(color_html_full[color_names_full.index(color)]) + elif color in color_names: + self.rgb = from_html(color_html[color_names.index(color)]) elif color in _lower_camel_names: - self.rgb = from_html(color_html_full[_lower_camel_names.index(color)]) + self.rgb = from_html(color_html[_lower_camel_names.index(color)]) elif isinstance(color, int) and 0 <= color <= 255: - self.rgb = from_html(color_html_full[color]) + self.rgb = from_html(color_html[color]) else: raise ColorNotFound("Did not find color: " + repr(color)) @@ -179,10 +178,8 @@ def name(self): """The (closest) name of the current color""" if self.reset: return 'reset' - elif self.simple: - return color_names_simple[self.number] else: - return color_names_full[self.number] + return color_names[self.number] @property def name_camelcase(self): @@ -215,15 +212,11 @@ def ansi_codes(self): if self.reset: return (ansi_addition+9,) elif self.simple: - return (self.number+ansi_addition,) - else: + return (color_codes_simple[self.number]+ansi_addition,) + elif self.exact: return (ansi_addition+8, 5, self.number) - - @property - def html_hex_code_nearest(self): - if self.reset: - return '#000000' - return color_html_simple[self.number] if self.simple else color_html_full[self.number] + else: + return (ansi_addition+8, 2, self.rgb[0], self.rgb[1], self.rgb[2]) @property def html_hex_code(self): @@ -244,7 +237,7 @@ class Style(object): """ color_class = Color - attribute_names = valid_attributes # a set of valid names + attribute_names = None # should be a dict of valid names _stdout = None end = '\n' @@ -501,13 +494,23 @@ def add_ansi(self, sequence): value = next(values) if value == 38 or value == 48: fg = value == 38 - if next(values) != 5: - raise ColorNotFound("the value 5 should follow a 38 or 48") value = next(values) - if fg: - self.fg = self.color_class.from_full(value) + if value == 5: + value = next(values) + if fg: + self.fg = self.color_class.from_full(value) + else: + self.bg = self.color_class.from_full(value, fg=False) + elif value == 2: + r = next(values) + g = next(values) + b = next(values) + if fg: + self.fg = self.color_class(r, g, b) + else: + self.bg = self.color_class(r, g, b, fg=False) else: - self.bg = self.color_class.from_full(value, fg=False) + raise ColorNotFound("the value 5 or 2 should follow a 38 or 48") elif value==0: self.reset = True elif value in attributes_ansi.values(): @@ -522,6 +525,10 @@ def add_ansi(self, sequence): self.fg = self.color_class.from_simple(value-30) elif 40 <= value <= 47: self.bg = self.color_class.from_simple(value-40, fg=False) + elif 90 <= value <= 97: + self.fg = self.color_class.from_simple(value-90) + elif 100 <= value <= 107: + self.bg = self.color_class.from_simple(value-100, fg=False) elif value == 39: self.fg = self.color_class() elif value == 49: diff --git a/tests/test_color.py b/tests/test_color.py index a01755b3e..2cbfbcdca 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -57,7 +57,7 @@ def testFromAnsi(self): self.assertEqual(color, COLOR.from_ansi(str(color))) for color in COLOR.BG: self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.BLINK): + for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.ITALICS): self.assertEqual(color, COLOR.from_ansi(str(color))) color = COLOR.BOLD + COLOR.FG.GREEN + COLOR.BG.BLUE + COLOR.UNDERLINE @@ -105,7 +105,6 @@ def testLackOfColor(self): self.assertEqual('', COLOR.FG['LightBlue']) def testFromHex(self): - self.assertEqual(str(COLOR.hex('#020201')),str(COLOR.hex('#020202'))) self.assertRaises(ColorNotFound, lambda: COLOR.hex('asdf')) self.assertRaises(ColorNotFound, lambda: COLOR.hex('#1234Z2')) self.assertRaises(ColorNotFound, lambda: COLOR.hex(12)) @@ -132,12 +131,12 @@ def testPrint(self): class TestHTMLColor(unittest.TestCase): def test_html(self): - red_tagged = 'This is tagged' + red_tagged = 'This is tagged' self.assertEqual(HTMLCOLOR.RED("This is tagged"), red_tagged) self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) - twin_tagged = 'This is tagged' + twin_tagged = 'This is tagged' self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) From b112530f79774a2b7ac6249ece55ea27d5682fe0 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 16:48:29 -0500 Subject: [PATCH 35/80] Adding color slicing --- plumbum/color/factories.py | 23 +++++++++++++++-------- tests/test_color.py | 8 ++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 9ec327e47..bcd4112a3 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -5,10 +5,8 @@ from __future__ import print_function import sys -import os -from functools import partial -from contextlib import contextmanager from plumbum.color.names import color_names +from plumbum.color.styles import ColorNotFound __all__ = ['ColorFactory', 'StyleFactory'] @@ -47,18 +45,27 @@ def hex(self, hexcode): def __getitem__(self, val): - """Shortcut to provide way to access extended colors.""" + """\ + Shortcut to provide way to access colors numerically or by slice. + If end <= 16, will stay to simple ansi version.""" try: + (start, stop, stride) = val.indices(256) + if stop <= 16: + return [self.simple(v) for v in range(start, stop, stride)] + else: + return [self.full(v) for v in range(start, stop, stride)] + except AttributeError: return self.full(val) - except ColorNotFound: - return self.hex(val) def __call__(self, val): - """Shortcut to provide way to access simple colors.""" + """Shortcut to provide way to access colors.""" try: return self.simple(val) except ColorNotFound: - return self.hex(val) + try: + return self.full(val) + except ColorNotFound: + return self.hex(val) def __iter__(self): """Iterates through all colors in extended colorset.""" diff --git a/tests/test_color.py b/tests/test_color.py index 2cbfbcdca..1156f281b 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -11,6 +11,14 @@ class TestANSIColor(unittest.TestCase): def setUp(self): COLOR.use_color = True + def testColorSlice(self): + vals = COLOR[:8] + self.assertEqual(len(vals),8) + self.assertEqual(vals[1], COLOR.RED) + vals = COLOR[40:50] + self.assertEqual(len(vals),10) + self.assertEqual(vals[1], COLOR.full(41)) + def testColorStrings(self): self.assertEqual('\033[0m', COLOR.RESET) self.assertEqual('\033[1m', COLOR.BOLD) From 21ca902bd17a43e1cd417e93eb1451914d3ed7a3 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 16:48:37 -0500 Subject: [PATCH 36/80] Fixing a test. --- tests/test_style.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_style.py b/tests/test_style.py index 1fd838a10..8b199ebd7 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -1,13 +1,13 @@ from __future__ import with_statement, print_function import unittest from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound -from plumbum.color.names import find_nearest_color, color_html_full, find_nearest_simple_color +from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color class TestNearestColor(unittest.TestCase): def test_exact(self): self.assertEqual(find_nearest_color(0,0,0),0) - for n,color in enumerate(color_html_full): + for n,color in enumerate(color_html): # Ignoring duplicates if n not in (16, 21, 46, 51, 196, 201, 226, 231, 244): rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) From 17661fe3c46b9b61bfce595d2d391f8d42d8c1ac Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 17:03:02 -0500 Subject: [PATCH 37/80] Fixes for tests failing, moving to slicing for tests --- plumbum/color/factories.py | 4 ++-- plumbum/color/styles.py | 6 +++--- tests/test_color.py | 8 ++++++-- tests/test_visual_color.py | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index bcd4112a3..b1c4d7385 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -21,7 +21,7 @@ def __init__(self, fg, style): self._style = style self.RESET = style.from_color(style.color_class(fg=fg)) - # Adding the color name shortcuts for forground colors + # Adding the color name shortcuts for foreground colors for item in color_names[:16]: setattr(self, item.upper(), style.from_color(style.color_class.from_simple(item, fg=fg))) @@ -47,7 +47,7 @@ def hex(self, hexcode): def __getitem__(self, val): """\ Shortcut to provide way to access colors numerically or by slice. - If end <= 16, will stay to simple ansi version.""" + If end <= 16, will stay to simple ANSI version.""" try: (start, stop, stride) = val.indices(256) if stop <= 16: diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 564852fa0..cdf1c1dbc 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -77,7 +77,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): def _init_number(self): """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" if self.simple: - self.number = find_nearest_simple_color(*self.rgb) + self.number = find_nearest_color(self.rgb[0], self.rgb[1], self.rgb[2], slice(0,16)) self.exact = self.rgb == from_html(color_html[self.number]) else: self.number = find_nearest_color(*self.rgb) @@ -526,9 +526,9 @@ def add_ansi(self, sequence): elif 40 <= value <= 47: self.bg = self.color_class.from_simple(value-40, fg=False) elif 90 <= value <= 97: - self.fg = self.color_class.from_simple(value-90) + self.fg = self.color_class.from_simple(value-90+8) elif 100 <= value <= 107: - self.bg = self.color_class.from_simple(value-100, fg=False) + self.bg = self.color_class.from_simple(value-100+8, fg=False) elif value == 39: self.fg = self.color_class() elif value == 49: diff --git a/tests/test_color.py b/tests/test_color.py index 1156f281b..511413a95 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -57,14 +57,18 @@ def testSums(self): self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) def testFromAnsi(self): - for color in COLOR.simple_colorful: + for color in COLOR[1:7]: self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG.simple_colorful: + for color in COLOR.BG[1:7]: self.assertEqual(color, COLOR.from_ansi(str(color))) for color in COLOR: self.assertEqual(color, COLOR.from_ansi(str(color))) for color in COLOR.BG: self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR[:16]: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG[:16]: + self.assertEqual(color, COLOR.from_ansi(str(color))) for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.ITALICS): self.assertEqual(color, COLOR.from_ansi(str(color))) diff --git a/tests/test_visual_color.py b/tests/test_visual_color.py index 7da468981..cb9d4f6aa 100644 --- a/tests/test_visual_color.py +++ b/tests/test_visual_color.py @@ -22,7 +22,7 @@ def tearDown(self): def testVisualColors(self): print() - for c in (COLOR.FG(x) for x in range(1, 6)): + for c in COLOR.FG[:16]: with c: print('Cycle color test', end=' ') print(' - > back to normal') From 6dfc10607f87959149428cf4e9c7b1bac2e82389 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 17:21:24 -0500 Subject: [PATCH 38/80] Now __call__ always prints --- examples/color.py | 6 +++--- plumbum/color/styles.py | 13 ++++++------- tests/test_color.py | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/examples/color.py b/examples/color.py index 4782fa47d..58fbde2bf 100755 --- a/examples/color.py +++ b/examples/color.py @@ -13,10 +13,10 @@ print(COLOR.BG.CYAN + "This is on a cyan background." + COLOR.RESET) print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) print() - for color in COLOR.BG: - print(color + ' ', end='') + for color in COLOR: + print(color + u'\u2588', end='') COLOR.RESET() print() print('Colors can be reset ' + COLOR.UNDERLINE['Too!']) - for color in COLOR.simple_colorful: + for color in COLOR[:16]: print(color["This is in color!"]) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index cdf1c1dbc..08582ca3c 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -380,13 +380,13 @@ def __mul__(self, other): ``"color >> color2`` syntax too. """ - def __call__(self, wrap_this = None): + def __call__(self, *printables, **kargs): """\ This is a shortcut to print color immediatly to the stdout. (Not safe) - If called with an argument, will wrap that argument.""" + If called with an argument, will wrap that argument and print (safe)""" - if wrap_this is not None: - return self.wrap(wrap_this) + if printables or kargs: + return self.print(*printables, **kargs) else: self.now() @@ -411,9 +411,8 @@ def print(self, *printables, **kargs): print_ = print """Shortcut just in case user not using __future__""" - def __getitem__(self, wrap_this): - """The [] syntax is supported for wrapping""" - return self.wrap(wrap_this) + __getitem__ = wrap + """The [] syntax is supported for wrapping""" def __enter__(self): """Context manager support""" diff --git a/tests/test_color.py b/tests/test_color.py index 511413a95..fb5421ea2 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -82,12 +82,12 @@ def testWrappedColor(self): wrapped = '\033[31mThis is a string\033[39m' self.assertEqual(COLOR.RED.wrap(string), wrapped) self.assertEqual(string << COLOR.RED, wrapped) - self.assertEqual(COLOR.RED(string), wrapped) + self.assertEqual(COLOR.RED*string, wrapped) self.assertEqual(COLOR.RED[string], wrapped) newcolor = COLOR.BLUE + COLOR.UNDERLINE - self.assertEqual(newcolor(string), string << newcolor) - self.assertEqual(newcolor(string), string << COLOR.BLUE + COLOR.UNDERLINE) + self.assertEqual(newcolor[string], string << newcolor) + self.assertEqual(newcolor.wrap(string), string << COLOR.BLUE + COLOR.UNDERLINE) def testUndoColor(self): self.assertEqual('\033[39m', -COLOR.FG) @@ -130,6 +130,14 @@ def testDirectCall(self): output = sys.stdout.getvalue().strip() self.assertEquals(output,str(COLOR.BLUE)) + def testDirectCallArgs(self): + COLOR.BLUE("This is") + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str("This is" << COLOR.BLUE)) def testPrint(self): COLOR.YELLOW.print('This is printed to stdout') @@ -138,13 +146,13 @@ def testPrint(self): self.fail("Need to run in buffered mode!") output = sys.stdout.getvalue().strip() - self.assertEquals(output,str(COLOR.YELLOW('This is printed to stdout'))) + self.assertEquals(output,str(COLOR.YELLOW.wrap('This is printed to stdout'))) class TestHTMLColor(unittest.TestCase): def test_html(self): red_tagged = 'This is tagged' - self.assertEqual(HTMLCOLOR.RED("This is tagged"), red_tagged) + self.assertEqual(HTMLCOLOR.RED["This is tagged"], red_tagged) self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) From 43dc09791e4fb2acf3fa1dc38b2c20634793c16b Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 17:40:11 -0500 Subject: [PATCH 39/80] Updated docs for 16 colors and 24 bit colors --- README.rst | 3 ++- docs/_cheatsheet.rst | 5 +++-- docs/color.rst | 34 ++++++++++++++++++++++------------ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index b3f2e6169..ce90459b0 100644 --- a/README.rst +++ b/README.rst @@ -135,7 +135,8 @@ Sample output:: with COLOR.RED: print("This library provides safe, flexible color access.") print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') + print("The simple 16 colors or", '256 named colors,' << COLOR['Orchid'] + COLOR.UNDERLINE, + "or full hex colors" << COLOR("#129240"), 'can be used.') print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index 90eb12813..e44ca964f 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -112,7 +112,8 @@ Sample output:: with COLOR.RED: print("This library provides safe, flexible color access.") print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 8 colors or", 'All 265' << COLOR['Orchid'] + COLOR.UNDERLINE, 'can be used.') + print("The simple 16 colors or", '256 named colors,' << COLOR['Orchid'] + COLOR.UNDERLINE, + "or full hex colors" << COLOR("#129240"), 'can be used.') print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") Sample output: @@ -123,7 +124,7 @@ Sample output:
        This library provides safe color access.
             Color (and styles in general) are easy!
        -    The simple 8 colors or All 265 can be used.
        +    The simple 16 colors, 256 named colors, or full hex colors can be used.
             Unsafe color access is available too.
        diff --git a/docs/color.rst b/docs/color.rst index f698f8fb7..738820763 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -29,12 +29,12 @@ The ``COLOR`` object has the following properties: ``FG`` and ``BG`` The foreground and background colors, reset to default with ``COLOR.FG.RESET`` or ``~COLOR.FG`` and likewise for ``BG``. (Named foreground colors are available - directly as well). The primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, - ``BLUE``, ``MAGENTA``, ``CYAN``, ``WHITE``, as well as ``RESET``, are available. - You can also access colors numerically with ``COLOR.FG(n)``, for the standard colors, - and ``COLOR.FG[n]`` for the extended 256 color codes, and likewise for ``BG``. These + directly as well). The first 16 primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, + ``BLUE``, ``MAGENTA``, ``CYAN``, etc, as well as ``RESET``, are available. + You can also access colors numerically with ``COLOR.FG(n)`` or ``COLOR.FG(n)`` + with the extended 256 color codes. These are ``ColorFactory`` instances. - ``BOLD``, ``DIM``, ``UNDERLINE``, ``BLINK``, ``REVERSE``, and ``HIDDEN`` + ``BOLD``, ``DIM``, ``UNDERLINE``, ``ITALICS``, ``REVERSE``, ``STRIKEOUT``, and ``HIDDEN`` All the `ANSI` modifiers are available, as well as their negations, such as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. ``RESET`` The global reset will restore all properties at once. @@ -47,9 +47,11 @@ will restore the foreground and background color, respectively). Although it doe some of the same things as a Style, its primary purpose is to generate Styles. If you call ``COLOR.from_ansi(seq)``, you can manually pass in any `ANSI` escape sequence, -even complex or combined ones. ``COLOR.rgb(r,g,b)`` will find the closest color from an +even complex or combined ones. ``COLOR.rgb(r,g,b)`` will create a color from an input red, green, and blue values (integers from 0-255). ``COLOR.hex(code)`` will allow -you to input an html style hex sequence. These work on ``FG`` and ``BG`` too. +you to input an html style hex sequence. These work on ``FG`` and ``BG`` too. The ``repr`` of +styles is smart and will show you the closest color to the one you selected if you didn't exactly +select a color through RGB. Style manipulations =================== @@ -67,7 +69,7 @@ Unsafe Manipulation ^^^^^^^^^^^^^^^^^^^ Styles have two unsafe operations: Concatenation (with ``+`` and a string) and calling ``.now()`` without -arguments (directly calling a style is also a shortcut for ``.now``). These two +arguments (directly calling a style without arguments is also a shortcut for ``.now()``). These two operations do not restore normal color to the terminal by themselves. To protect their use, you should always use a context manager around any unsafe operation. @@ -120,7 +122,9 @@ These produce strings that can be further manipulated or printed. 2.6 `_, feel free to use this method. -Finally, you can also print a color to stdout directly using ``color.print("string")``. This +Finally, you can also print a color to stdout directly using ``color("string")`` or +``color.print("string")``. Since the first can be an unsafe operation if you forget an arguement, +you may prefer the latter. This has the same syntax as the Python 3 print function. In Python 2, if you do not have ``from __future__ import print_function`` enabled, ``color.print_("string")`` is provided as an alternative, following the PyQT convention for method names that match reserved Python syntax. @@ -157,8 +161,14 @@ applying the Styles individually to the strings). However, combined Styles are i 256 Color Support ================= -The library support 256 colors through numbers, names or HEX html codes. You can access them -as ``COLOR.FG[12]``, ``COLOR.FG['Light_Blue']``, ``COLOR.FG['LightBlue']``, or ``COLOR.FG['#0000FF']``. The supported colors are: +While this library supports full 24 bit colors through escape sequences, +the library has speciall support for the "full" 256 colorset through numbers, +names or HEX html codes. Even if you use 24 bit color, the closest name is displayed +in the ``repr``. You can access the colors as +as ``COLOR.FG(12)``, ``COLOR.FG('Light_Blue')``, ``COLOR.FG('LightBlue')``, or ``COLOR.FG('#0000FF')``. +You can also iterate or slice the ``COLOR``, ``COLOR.FG``, or ``COLOR.BG`` objects. Slicing even +intelegently downgrades to the simple version of the codes if it is within the first 16 elements. +The supported colors are: .. raw:: html :file: _color_list.html @@ -249,4 +259,4 @@ See Also * `colored `_ Another library with 256 color support * `colorama `_ A library that supports colored text on Windows, - can be combined with Plumbum.color (if you force ``use_color``) + can be combined with Plumbum.color (if you force ``use_color``, doesn't support all extended colors) From 1aeb25823a16d15e21c19d9e73bd4f6231173232 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 17:51:31 -0500 Subject: [PATCH 40/80] Dropped the (unneeded) colorful list, as slicing works now. Added full color example. --- examples/color.py | 3 +++ examples/fullcolor.py | 10 ++++++++++ plumbum/color/factories.py | 5 ----- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100755 examples/fullcolor.py diff --git a/examples/color.py b/examples/color.py index 58fbde2bf..a8fe3d080 100755 --- a/examples/color.py +++ b/examples/color.py @@ -5,7 +5,9 @@ with COLOR.FG.RED: print('This is in red') + print('This is completly restored, even if an exception is thrown!') + with COLOR: print('It is always a good idea to be in a context manager, to avoid being', 'left with a colored terminal if there is an exception!') @@ -20,3 +22,4 @@ print('Colors can be reset ' + COLOR.UNDERLINE['Too!']) for color in COLOR[:16]: print(color["This is in color!"]) + diff --git a/examples/fullcolor.py b/examples/fullcolor.py new file mode 100755 index 000000000..e3ac9b0ad --- /dev/null +++ b/examples/fullcolor.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from __future__ import with_statement, print_function +from plumbum import COLOR + +with COLOR: + print("Do you believe in color, punk? DO YOU?") + for i in range(0,255,10): + for j in range(0,255,10): + print(u''.join(COLOR.rgb(i,j,k)[u'\u2588'] for k in range(0,255,10))) + diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index b1c4d7385..c58376955 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -94,11 +94,6 @@ def __exit__(self, type, value, traceback): def __repr__(self): return "<{0}>".format(self.__class__.__name__) - @property - def simple_colorful(self): - """List over the six simple actual colors.""" - return [self.simple(i) for i in range(1,7)] - class StyleFactory(ColorFactory): """Factory for styles. Holds font styles, FG and BG objects representing colors, and From 0b0e056c381d039a951c1dcbffa792f4e8b91fc6 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Mon, 20 Jul 2015 17:57:16 -0500 Subject: [PATCH 41/80] Spelling typo. --- docs/color.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/color.rst b/docs/color.rst index 738820763..e834bdf70 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -162,7 +162,7 @@ applying the Styles individually to the strings). However, combined Styles are i ================= While this library supports full 24 bit colors through escape sequences, -the library has speciall support for the "full" 256 colorset through numbers, +the library has special support for the "full" 256 colorset through numbers, names or HEX html codes. Even if you use 24 bit color, the closest name is displayed in the ``repr``. You can access the colors as as ``COLOR.FG(12)``, ``COLOR.FG('Light_Blue')``, ``COLOR.FG('LightBlue')``, or ``COLOR.FG('#0000FF')``. From 1e20f95b3a07faca7becd8f97cf390c8d0c22353 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Mon, 20 Jul 2015 21:15:12 -0500 Subject: [PATCH 42/80] Updated API docs to be slightly better. --- docs/api/color.rst | 13 +++++++ plumbum/color/factories.py | 2 ++ plumbum/color/names.py | 5 ++- plumbum/color/styles.py | 72 ++++++++++++++++++++++++++++---------- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/docs/api/color.rst b/docs/api/color.rst index 0cb904527..d6262a458 100644 --- a/docs/api/color.rst +++ b/docs/api/color.rst @@ -3,12 +3,25 @@ Package plumbum.color .. automodule:: plumbum.color :members: + :special-members: + +plumbum.color.styles +-------------------- .. automodule:: plumbum.color.styles :members: + :special-members: + +plumbum.color.factories +----------------------- .. automodule:: plumbum.color.factories :members: + :special-members: + +plumbum.color.names +------------------- .. automodule:: plumbum.color.names :members: + :special-members: diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index c58376955..3aeec343e 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -77,6 +77,7 @@ def __neg__(self): __invert__ = __neg__ def __rsub__(self, other): + """Makes a - COLOR.FG easier""" return other + (-self) def __enter__(self): @@ -92,6 +93,7 @@ def __exit__(self, type, value, traceback): return False def __repr__(self): + """Simple representation of the class by name.""" return "<{0}>".format(self.__class__.__name__) class StyleFactory(ColorFactory): diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 22ab0d21a..726d1dabf 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -285,8 +285,8 @@ #Functions to be used for color name operations def _distance_to_color(r, g, b, color): - rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) """This computes the distance to a color, should be minimized""" + rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) return (r-rgb[0])**2 + (g-rgb[1])**2 + (b-rgb[2])**2 @@ -308,10 +308,13 @@ def find_nearest_simple_color(r, g, b): return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 def find_nearest_colorblock(*rgb): + """This finds the nearest color based on block system, only works + for 17-232 color values.""" r, g, b = (round(v / 256. * 5) for v in rgb) return (16 + 36 * r + 6 * g + b) def from_html(color): + """Convert html hex code to rgb""" if len(color) != 7 or color[0] != '#': raise ValueError("Invalid length of html code") return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 08582ca3c..6ac0572b5 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -1,4 +1,9 @@ """ +This file provides two classes, `Color` and `Style`. + +``Color`` is rarely used directly, +but merely provides the workhorse for finding and manipulating colors. + With the ``Style`` class, any color can be directly called or given to a with statement. """ @@ -18,20 +23,28 @@ class ColorNotFound(Exception): + """Thrown when a color is not valid for a particular method.""" pass class AttributeNotFound(Exception): + """Similar to color not found, only for attributes.""" pass class ResetNotSupported(Exception): + """An exception indicating that Reset is not available + for this Style.""" pass class Color(object): """\ + Loaded with ``(r, g, b, fg)`` or ``(color, fg=)``. The second signature is a short cut + and will try full and hex loading. + This class stores the idea of a color, rather than a specific implementation. It provides as many different tools for representations as possible, and can be subclassed - to add more represenations. ``.from_any`` provides a quick-init shortcut. + to add more represenations, though that should not be needed for most situations. ``.from_`` class methods provide quick ways to create colors given different representations. + You will not usually interact with this class. Possible colors:: @@ -45,11 +58,27 @@ class Color(object): The attributes are: - self.fg: Foreground if True, background if not - self.reset: True it this is a reset color (following atts don't matter if True) - self.rgb: The red/green/blue tuple for this color - self.simple: If true will stay to 16 color mode. - self.number: The color number given the mode, closest to rgb if not exact, gives closest name in full mode. + + .. data:: reset + + True it this is a reset color (following attributes don't matter if True) + + .. data:: rgb + + The red/green/blue tuple for this color + + .. data:: simple + + If true will stay to 16 color mode. + + .. data:: number + + The color number given the mode, closest to rgb + if not rgb not exact, gives position of closest name. + + .. data:: fg + + This is a foreground color if True. Background color if False. """ @@ -161,18 +190,6 @@ def _from_hex(self, color): self._init_number() - @property - def r(self): - return self.rgb[0] - - @property - def g(self): - return self.rgb[1] - - @property - def b(self): - return self.rgb[2] - @property def name(self): """The (closest) name of the current color""" @@ -187,6 +204,7 @@ def name_camelcase(self): return self.name.replace("_", " ").title().replace(" ","") def __repr__(self): + """This class has a smart representation that shows name and color (if not unique).""" name = ' Simple' if self.simple else '' name += '' if self.fg else ' Background' name += ' ' + self.name_camelcase @@ -194,6 +212,7 @@ def __repr__(self): return name[1:] def __eq__(self, other): + """Reset colors are equal, otherwise number, rgb, and simple status have to match.""" if self.reset: return other.reset else: @@ -203,10 +222,12 @@ def __eq__(self, other): @property def ansi_sequence(self): + """This is the ansi seqeunce as a string, ready to use.""" return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' @property def ansi_codes(self): + """This is the full ANSI code, can be reset, simple, 256, or full color.""" ansi_addition = 30 if self.fg else 40 if self.reset: @@ -220,12 +241,14 @@ def ansi_codes(self): @property def html_hex_code(self): + """This is the hex code of the current color, html style notation.""" if self.reset: return '#000000' else: return '#' + '{0[0]:02X}{0[1]:02X}{0[2]:02X}'.format(self.rgb) def __str__(self): + """This just prints it's simple name""" return self.name @@ -237,9 +260,13 @@ class Style(object): """ color_class = Color + """The class of color to use. Never hardcode ``Color`` call when writing a Style + method.""" + attribute_names = None # should be a dict of valid names _stdout = None end = '\n' + """The endline character. Override if needed in subclasses.""" @property def stdout(self): @@ -254,6 +281,7 @@ def stdout(self, newout): self.__class__._stdout = newout def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): + """This is usually initialized from a factory.""" self.attributes = attributes if attributes is not None else dict() self.fg = fgcolor self.bg = bgcolor @@ -425,6 +453,8 @@ def __exit__(self, type, value, traceback): @property def ansi_codes(self): + """Generates the full ANSI code sequence for a Style""" + if self.reset: return [0] @@ -446,6 +476,7 @@ def ansi_codes(self): @property def ansi_sequence(self): + """This is the string ANSI sequence.""" return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' def __repr__(self): @@ -459,6 +490,7 @@ def __repr__(self): return "<{0}: {1}>".format(name, string if string else 'empty') def __eq__(self, other): + """Equality is true only if reset, or if attributes, fg, and bg match.""" if type(self) == type(other): if self.reset: return other.reset @@ -470,6 +502,8 @@ def __eq__(self, other): return str(self) == other def __str__(self): + """Base Style does not implement a __str__ representation. This is the one + required method of a subclass.""" raise NotImplemented("This is a base style, does not have an representation") @@ -542,7 +576,7 @@ class ANSIStyle(Style): """This is a subclass for ANSI styles. Use it to get color on sys.stdout tty terminals on posix systems. - set ``use_color = True/False`` if you want to control color + Set ``use_color = True/False`` if you want to control color for anything using this Style.""" use_color = sys.stdout.isatty() and os.name == "posix" From 7f864abb2d406f1049448fe17a1bf21b89e6b7b6 Mon Sep 17 00:00:00 2001 From: henryiii Date: Mon, 20 Jul 2015 22:21:27 -0500 Subject: [PATCH 43/80] Fix for failing test due to removed Color attributes. --- tests/test_style.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/test_style.py b/tests/test_style.py index 8b199ebd7..6b4e9a89a 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -24,15 +24,12 @@ def test_simplecolor(self): self.assertEqual(find_nearest_simple_color(140,140,140), 7) - class TestColorLoad(unittest.TestCase): def test_rgb(self): blue = Color(0,0,255) # Red, Green, Blue - self.assertEqual(blue.r, 0) - self.assertEqual(blue.g, 0) - self.assertEqual(blue.b, 255) - + self.assertEqual(blue.rgb, (0,0,255)) + def test_simple_name(self): green = Color.from_simple('green') self.assertEqual(green.number, 2) @@ -65,6 +62,7 @@ def test_ansi(self): self.assertEqual(str(ANSIStyle(fgcolor=Color('green'))), '\033[38;5;2m') self.assertEqual(str(ANSIStyle(fgcolor=Color.from_simple('red'))), '\033[31m') + class TestStyle(unittest.TestCase): def setUp(self): ANSIStyle.use_color = True @@ -73,7 +71,5 @@ def test_InvalidAttributes(self): pass - - if __name__ == '__main__': unittest.main() From 7a403bf314f5019b62104ec31303d93e6c9654f7 Mon Sep 17 00:00:00 2001 From: henryiii Date: Mon, 20 Jul 2015 22:39:33 -0500 Subject: [PATCH 44/80] Adding __getattr__ (from iPhone) --- plumbum/color/factories.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 3aeec343e..a7bcd0e5f 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -31,6 +31,13 @@ def full(self, name): color name, html code, or number.""" return self._style.from_color(self._style.color_class(name, fg=self._fg)) + def __getattr__(self, item): + """Full color names work, but do not populate __dir__.""" + try: + return self._style.from_color(self._style.color_class.from_full(name, fg=self._fg)) + except ColorNotFound: + raise AttributeError(item) + def simple(self, name): """Return the extended color scheme color for a value or name.""" return self._style.from_color(self._style.color_class.from_simple(name, fg=self._fg)) From ce77e0e44e05f0bcf1b4e9480c2d540dbed7b2c1 Mon Sep 17 00:00:00 2001 From: henryiii Date: Mon, 20 Jul 2015 22:46:11 -0500 Subject: [PATCH 45/80] Adding attribute tests. --- tests/test_color.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_color.py b/tests/test_color.py index fb5421ea2..207f69f1a 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -46,6 +46,14 @@ def testLoadColorByName(self): self.assertEqual(COLOR['DeepSkyBlue1'], COLOR.hex('#00afff')) self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) + self.assertEqual(COLOR.DeepSkyBlue1, COLOR[39]) + self.assertEqual(COLOR.deepskyblue1, COLOR[39]) + self.assertEqual(COLOR.Deep_Sky_Blue1, COLOR[39]) + + self.asserRaises(AttributeError, lambda: COLOR.Notacoloratall) + + + def testMultiColor(self): sumcolor = COLOR.BOLD + COLOR.BLUE From 3ecd8f9232ac290d6fd268a2b923835a0019646d Mon Sep 17 00:00:00 2001 From: henryiii Date: Mon, 20 Jul 2015 23:12:31 -0500 Subject: [PATCH 46/80] Fix typo --- plumbum/color/factories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index a7bcd0e5f..2c833ed4a 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -34,7 +34,7 @@ def full(self, name): def __getattr__(self, item): """Full color names work, but do not populate __dir__.""" try: - return self._style.from_color(self._style.color_class.from_full(name, fg=self._fg)) + return self._style.from_color(self._style.color_class.from_full(item, fg=self._fg)) except ColorNotFound: raise AttributeError(item) From ec4232f3f0c75379319adeb7d1e6e962da29fd7f Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Tue, 21 Jul 2015 07:37:49 -0500 Subject: [PATCH 47/80] Simplify and fix name loading, tests pass now. --- plumbum/color/names.py | 516 ++++++++++++++++++++-------------------- plumbum/color/styles.py | 28 ++- tests/test_color.py | 2 +- 3 files changed, 276 insertions(+), 270 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 726d1dabf..184877c63 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -7,266 +7,266 @@ ''' _named_colors = '''\ -0,black,#000000 -1,red,#c00000 -2,green,#00c000 -3,yellow,#c0c000 -4,blue,#0000c0 -5,magenta,#c000c0 -6,cyan,#00c0c0 -7,light_gray,#c0c0c0 -8,dark_gray,#808080 -9,light_red,#ff0000 -10,light_green,#00ff00 -11,light_yellow,#ffff00 -12,light_blue,#0000ff -13,light_magenta,#ff00ff -14,light_cyan,#00ffff -15,white,#ffffff -16,grey_0,#000000 -17,navy_blue,#00005f -18,dark_blue,#000087 -19,blue_3,#0000af -20,blue_3,#0000d7 -21,blue_1,#0000ff -22,dark_green,#005f00 -23,deep_sky_blue_4,#005f5f -24,deep_sky_blue_4,#005f87 -25,deep_sky_blue_4,#005faf -26,dodger_blue_3,#005fd7 -27,dodger_blue_2,#005fff -28,green_4,#008700 -29,spring_green_4,#00875f -30,turquoise_4,#008787 -31,deep_sky_blue_3,#0087af -32,deep_sky_blue_3,#0087d7 -33,dodger_blue_1,#0087ff -34,green_3,#00af00 -35,spring_green_3,#00af5f -36,dark_cyan,#00af87 -37,light_sea_green,#00afaf -38,deep_sky_blue_2,#00afd7 -39,deep_sky_blue_1,#00afff -40,green_3,#00d700 -41,spring_green_3,#00d75f -42,spring_green_2,#00d787 -43,cyan_3,#00d7af -44,dark_turquoise,#00d7d7 -45,turquoise_2,#00d7ff -46,green_1,#00ff00 -47,spring_green_2,#00ff5f -48,spring_green_1,#00ff87 -49,medium_spring_green,#00ffaf -50,cyan_2,#00ffd7 -51,cyan_1,#00ffff -52,dark_red,#5f0000 -53,deep_pink_4,#5f005f -54,purple_4,#5f0087 -55,purple_4,#5f00af -56,purple_3,#5f00d7 -57,blue_violet,#5f00ff -58,orange_4,#5f5f00 -59,grey_37,#5f5f5f -60,medium_purple_4,#5f5f87 -61,slate_blue_3,#5f5faf -62,slate_blue_3,#5f5fd7 -63,royal_blue_1,#5f5fff -64,chartreuse_4,#5f8700 -65,dark_sea_green_4,#5f875f -66,pale_turquoise_4,#5f8787 -67,steel_blue,#5f87af -68,steel_blue_3,#5f87d7 -69,cornflower_blue,#5f87ff -70,chartreuse_3,#5faf00 -71,dark_sea_green_4,#5faf5f -72,cadet_blue,#5faf87 -73,cadet_blue,#5fafaf -74,sky_blue_3,#5fafd7 -75,steel_blue_1,#5fafff -76,chartreuse_3,#5fd700 -77,pale_green_3,#5fd75f -78,sea_green_3,#5fd787 -79,aquamarine_3,#5fd7af -80,medium_turquoise,#5fd7d7 -81,steel_blue_1,#5fd7ff -82,chartreuse_2,#5fff00 -83,sea_green_2,#5fff5f -84,sea_green_1,#5fff87 -85,sea_green_1,#5fffaf -86,aquamarine_1,#5fffd7 -87,dark_slate_gray_2,#5fffff -88,dark_red,#870000 -89,deep_pink_4,#87005f -90,dark_magenta,#870087 -91,dark_magenta,#8700af -92,dark_violet,#8700d7 -93,purple,#8700ff -94,orange_4,#875f00 -95,light_pink_4,#875f5f -96,plum_4,#875f87 -97,medium_purple_3,#875faf -98,medium_purple_3,#875fd7 -99,slate_blue_1,#875fff -100,yellow_4,#878700 -101,wheat_4,#87875f -102,grey_53,#878787 -103,light_slate_grey,#8787af -104,medium_purple,#8787d7 -105,light_slate_blue,#8787ff -106,yellow_4,#87af00 -107,dark_olive_green_3,#87af5f -108,dark_sea_green,#87af87 -109,light_sky_blue_3,#87afaf -110,light_sky_blue_3,#87afd7 -111,sky_blue_2,#87afff -112,chartreuse_2,#87d700 -113,dark_olive_green_3,#87d75f -114,pale_green_3,#87d787 -115,dark_sea_green_3,#87d7af -116,dark_slate_gray_3,#87d7d7 -117,sky_blue_1,#87d7ff -118,chartreuse_1,#87ff00 -119,light_green,#87ff5f -120,light_green,#87ff87 -121,pale_green_1,#87ffaf -122,aquamarine_1,#87ffd7 -123,dark_slate_gray_1,#87ffff -124,red_3,#af0000 -125,deep_pink_4,#af005f -126,medium_violet_red,#af0087 -127,magenta_3,#af00af -128,dark_violet,#af00d7 -129,purple,#af00ff -130,dark_orange_3,#af5f00 -131,indian_red,#af5f5f -132,hot_pink_3,#af5f87 -133,medium_orchid_3,#af5faf -134,medium_orchid,#af5fd7 -135,medium_purple_2,#af5fff -136,dark_goldenrod,#af8700 -137,light_salmon_3,#af875f -138,rosy_brown,#af8787 -139,grey_63,#af87af -140,medium_purple_2,#af87d7 -141,medium_purple_1,#af87ff -142,gold_3,#afaf00 -143,dark_khaki,#afaf5f -144,navajo_white_3,#afaf87 -145,grey_69,#afafaf -146,light_steel_blue_3,#afafd7 -147,light_steel_blue,#afafff -148,yellow_3,#afd700 -149,dark_olive_green_3,#afd75f -150,dark_sea_green_3,#afd787 -151,dark_sea_green_2,#afd7af -152,light_cyan_3,#afd7d7 -153,light_sky_blue_1,#afd7ff -154,green_yellow,#afff00 -155,dark_olive_green_2,#afff5f -156,pale_green_1,#afff87 -157,dark_sea_green_2,#afffaf -158,dark_sea_green_1,#afffd7 -159,pale_turquoise_1,#afffff -160,red_3,#d70000 -161,deep_pink_3,#d7005f -162,deep_pink_3,#d70087 -163,magenta_3,#d700af -164,magenta_3,#d700d7 -165,magenta_2,#d700ff -166,dark_orange_3,#d75f00 -167,indian_red,#d75f5f -168,hot_pink_3,#d75f87 -169,hot_pink_2,#d75faf -170,orchid,#d75fd7 -171,medium_orchid_1,#d75fff -172,orange_3,#d78700 -173,light_salmon_3,#d7875f -174,light_pink_3,#d78787 -175,pink_3,#d787af -176,plum_3,#d787d7 -177,violet,#d787ff -178,gold_3,#d7af00 -179,light_goldenrod_3,#d7af5f -180,tan,#d7af87 -181,misty_rose_3,#d7afaf -182,thistle_3,#d7afd7 -183,plum_2,#d7afff -184,yellow_3,#d7d700 -185,khaki_3,#d7d75f -186,light_goldenrod_2,#d7d787 -187,light_yellow_3,#d7d7af -188,grey_84,#d7d7d7 -189,light_steel_blue_1,#d7d7ff -190,yellow_2,#d7ff00 -191,dark_olive_green_1,#d7ff5f -192,dark_olive_green_1,#d7ff87 -193,dark_sea_green_1,#d7ffaf -194,honeydew_2,#d7ffd7 -195,light_cyan_1,#d7ffff -196,red_1,#ff0000 -197,deep_pink_2,#ff005f -198,deep_pink_1,#ff0087 -199,deep_pink_1,#ff00af -200,magenta_2,#ff00d7 -201,magenta_1,#ff00ff -202,orange_red_1,#ff5f00 -203,indian_red_1,#ff5f5f -204,indian_red_1,#ff5f87 -205,hot_pink,#ff5faf -206,hot_pink,#ff5fd7 -207,medium_orchid_1,#ff5fff -208,dark_orange,#ff8700 -209,salmon_1,#ff875f -210,light_coral,#ff8787 -211,pale_violet_red_1,#ff87af -212,orchid_2,#ff87d7 -213,orchid_1,#ff87ff -214,orange_1,#ffaf00 -215,sandy_brown,#ffaf5f -216,light_salmon_1,#ffaf87 -217,light_pink_1,#ffafaf -218,pink_1,#ffafd7 -219,plum_1,#ffafff -220,gold_1,#ffd700 -221,light_goldenrod_2,#ffd75f -222,light_goldenrod_2,#ffd787 -223,navajo_white_1,#ffd7af -224,misty_rose_1,#ffd7d7 -225,thistle_1,#ffd7ff -226,yellow_1,#ffff00 -227,light_goldenrod_1,#ffff5f -228,khaki_1,#ffff87 -229,wheat_1,#ffffaf -230,cornsilk_1,#ffffd7 -231,grey_10_0,#ffffff -232,grey_3,#080808 -233,grey_7,#121212 -234,grey_11,#1c1c1c -235,grey_15,#262626 -236,grey_19,#303030 -237,grey_23,#3a3a3a -238,grey_27,#444444 -239,grey_30,#4e4e4e -240,grey_35,#585858 -241,grey_39,#626262 -242,grey_42,#6c6c6c -243,grey_46,#767676 -244,grey_50,#808080 -245,grey_54,#8a8a8a -246,grey_58,#949494 -247,grey_62,#9e9e9e -248,grey_66,#a8a8a8 -249,grey_70,#b2b2b2 -250,grey_74,#bcbcbc -251,grey_78,#c6c6c6 -252,grey_82,#d0d0d0 -253,grey_85,#dadada -254,grey_89,#e4e4e4 -255,grey_93,#eeeeee +black,#000000 +red,#c00000 +green,#00c000 +yellow,#c0c000 +blue,#0000c0 +magenta,#c000c0 +cyan,#00c0c0 +light_gray,#c0c0c0 +dark_gray,#808080 +light_red,#ff0000 +light_green,#00ff00 +light_yellow,#ffff00 +light_blue,#0000ff +light_magenta,#ff00ff +light_cyan,#00ffff +white,#ffffff +grey_0,#000000 +navy_blue,#00005f +dark_blue,#000087 +blue_3,#0000af +blue_3,#0000d7 +blue_1,#0000ff +dark_green,#005f00 +deep_sky_blue_4,#005f5f +deep_sky_blue_4,#005f87 +deep_sky_blue_4,#005faf +dodger_blue_3,#005fd7 +dodger_blue_2,#005fff +green_4,#008700 +spring_green_4,#00875f +turquoise_4,#008787 +deep_sky_blue_3,#0087af +deep_sky_blue_3,#0087d7 +dodger_blue_1,#0087ff +green_3,#00af00 +spring_green_3,#00af5f +dark_cyan,#00af87 +light_sea_green,#00afaf +deep_sky_blue_2,#00afd7 +deep_sky_blue_1,#00afff +green_3,#00d700 +spring_green_3,#00d75f +spring_green_2,#00d787 +cyan_3,#00d7af +dark_turquoise,#00d7d7 +turquoise_2,#00d7ff +green_1,#00ff00 +spring_green_2,#00ff5f +spring_green_1,#00ff87 +medium_spring_green,#00ffaf +cyan_2,#00ffd7 +cyan_1,#00ffff +dark_red,#5f0000 +deep_pink_4,#5f005f +purple_4,#5f0087 +purple_4,#5f00af +purple_3,#5f00d7 +blue_violet,#5f00ff +orange_4,#5f5f00 +grey_37,#5f5f5f +medium_purple_4,#5f5f87 +slate_blue_3,#5f5faf +slate_blue_3,#5f5fd7 +royal_blue_1,#5f5fff +chartreuse_4,#5f8700 +dark_sea_green_4,#5f875f +pale_turquoise_4,#5f8787 +steel_blue,#5f87af +steel_blue_3,#5f87d7 +cornflower_blue,#5f87ff +chartreuse_3,#5faf00 +dark_sea_green_4,#5faf5f +cadet_blue,#5faf87 +cadet_blue,#5fafaf +sky_blue_3,#5fafd7 +steel_blue_1,#5fafff +chartreuse_3,#5fd700 +pale_green_3,#5fd75f +sea_green_3,#5fd787 +aquamarine_3,#5fd7af +medium_turquoise,#5fd7d7 +steel_blue_1,#5fd7ff +chartreuse_2,#5fff00 +sea_green_2,#5fff5f +sea_green_1,#5fff87 +sea_green_1,#5fffaf +aquamarine_1,#5fffd7 +dark_slate_gray_2,#5fffff +dark_red,#870000 +deep_pink_4,#87005f +dark_magenta,#870087 +dark_magenta,#8700af +dark_violet,#8700d7 +purple,#8700ff +orange_4,#875f00 +light_pink_4,#875f5f +plum_4,#875f87 +medium_purple_3,#875faf +medium_purple_3,#875fd7 +slate_blue_1,#875fff +yellow_4,#878700 +wheat_4,#87875f +grey_53,#878787 +light_slate_grey,#8787af +medium_purple,#8787d7 +light_slate_blue,#8787ff +yellow_4,#87af00 +dark_olive_green_3,#87af5f +dark_sea_green,#87af87 +light_sky_blue_3,#87afaf +light_sky_blue_3,#87afd7 +sky_blue_2,#87afff +chartreuse_2,#87d700 +dark_olive_green_3,#87d75f +pale_green_3,#87d787 +dark_sea_green_3,#87d7af +dark_slate_gray_3,#87d7d7 +sky_blue_1,#87d7ff +chartreuse_1,#87ff00 +light_green,#87ff5f +light_green,#87ff87 +pale_green_1,#87ffaf +aquamarine_1,#87ffd7 +dark_slate_gray_1,#87ffff +red_3,#af0000 +deep_pink_4,#af005f +medium_violet_red,#af0087 +magenta_3,#af00af +dark_violet,#af00d7 +purple,#af00ff +dark_orange_3,#af5f00 +indian_red,#af5f5f +hot_pink_3,#af5f87 +medium_orchid_3,#af5faf +medium_orchid,#af5fd7 +medium_purple_2,#af5fff +dark_goldenrod,#af8700 +light_salmon_3,#af875f +rosy_brown,#af8787 +grey_63,#af87af +medium_purple_2,#af87d7 +medium_purple_1,#af87ff +gold_3,#afaf00 +dark_khaki,#afaf5f +navajo_white_3,#afaf87 +grey_69,#afafaf +light_steel_blue_3,#afafd7 +light_steel_blue,#afafff +yellow_3,#afd700 +dark_olive_green_3,#afd75f +dark_sea_green_3,#afd787 +dark_sea_green_2,#afd7af +light_cyan_3,#afd7d7 +light_sky_blue_1,#afd7ff +green_yellow,#afff00 +dark_olive_green_2,#afff5f +pale_green_1,#afff87 +dark_sea_green_2,#afffaf +dark_sea_green_1,#afffd7 +pale_turquoise_1,#afffff +red_3,#d70000 +deep_pink_3,#d7005f +deep_pink_3,#d70087 +magenta_3,#d700af +magenta_3,#d700d7 +magenta_2,#d700ff +dark_orange_3,#d75f00 +indian_red,#d75f5f +hot_pink_3,#d75f87 +hot_pink_2,#d75faf +orchid,#d75fd7 +medium_orchid_1,#d75fff +orange_3,#d78700 +light_salmon_3,#d7875f +light_pink_3,#d78787 +pink_3,#d787af +plum_3,#d787d7 +violet,#d787ff +gold_3,#d7af00 +light_goldenrod_3,#d7af5f +tan,#d7af87 +misty_rose_3,#d7afaf +thistle_3,#d7afd7 +plum_2,#d7afff +yellow_3,#d7d700 +khaki_3,#d7d75f +light_goldenrod_2,#d7d787 +light_yellow_3,#d7d7af +grey_84,#d7d7d7 +light_steel_blue_1,#d7d7ff +yellow_2,#d7ff00 +dark_olive_green_1,#d7ff5f +dark_olive_green_1,#d7ff87 +dark_sea_green_1,#d7ffaf +honeydew_2,#d7ffd7 +light_cyan_1,#d7ffff +red_1,#ff0000 +deep_pink_2,#ff005f +deep_pink_1,#ff0087 +deep_pink_1,#ff00af +magenta_2,#ff00d7 +magenta_1,#ff00ff +orange_red_1,#ff5f00 +indian_red_1,#ff5f5f +indian_red_1,#ff5f87 +hot_pink,#ff5faf +hot_pink,#ff5fd7 +medium_orchid_1,#ff5fff +dark_orange,#ff8700 +salmon_1,#ff875f +light_coral,#ff8787 +pale_violet_red_1,#ff87af +orchid_2,#ff87d7 +orchid_1,#ff87ff +orange_1,#ffaf00 +sandy_brown,#ffaf5f +light_salmon_1,#ffaf87 +light_pink_1,#ffafaf +pink_1,#ffafd7 +plum_1,#ffafff +gold_1,#ffd700 +light_goldenrod_2,#ffd75f +light_goldenrod_2,#ffd787 +navajo_white_1,#ffd7af +misty_rose_1,#ffd7d7 +thistle_1,#ffd7ff +yellow_1,#ffff00 +light_goldenrod_1,#ffff5f +khaki_1,#ffff87 +wheat_1,#ffffaf +cornsilk_1,#ffffd7 +grey_10_0,#ffffff +grey_3,#080808 +grey_7,#121212 +grey_11,#1c1c1c +grey_15,#262626 +grey_19,#303030 +grey_23,#3a3a3a +grey_27,#444444 +grey_30,#4e4e4e +grey_35,#585858 +grey_39,#626262 +grey_42,#6c6c6c +grey_46,#767676 +grey_50,#808080 +grey_54,#8a8a8a +grey_58,#949494 +grey_62,#9e9e9e +grey_66,#a8a8a8 +grey_70,#b2b2b2 +grey_74,#bcbcbc +grey_78,#c6c6c6 +grey_82,#d0d0d0 +grey_85,#dadada +grey_89,#e4e4e4 +grey_93,#eeeeee ''' -color_names = [n.split(',')[1] for n in _named_colors.split()] -color_html = [n.split(',')[2] for n in _named_colors.split()] +color_names = [n.split(',')[0] for n in _named_colors.split()] +color_html = [n.split(',')[1] for n in _named_colors.split()] color_codes_simple = list(range(8)) + list(range(60,68)) """Simple colors, remember that reset is #9, second half is non as common.""" diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 6ac0572b5..d9d9f4cf8 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -91,6 +91,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): self.rgb = (0,0,0) self.simple = False self.exact = True # Set to False if interpolation done + self.number = None if r_or_color is not None and None in (g,b): try: @@ -105,12 +106,15 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): def _init_number(self): """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" - if self.simple: - self.number = find_nearest_color(self.rgb[0], self.rgb[1], self.rgb[2], slice(0,16)) - self.exact = self.rgb == from_html(color_html[self.number]) + if self.number is not None: + self.exact = True else: - self.number = find_nearest_color(*self.rgb) - self.exact = self.rgb == from_html(color_html[self.number]) + if self.simple: + self.number = find_nearest_color(self.rgb[0], self.rgb[1], self.rgb[2], slice(0,16)) + self.exact = self.rgb == from_html(color_html[self.number]) + else: + self.number = find_nearest_color(*self.rgb) + self.exact = self.rgb == from_html(color_html[self.number]) self.reset = False @@ -131,10 +135,12 @@ def _from_simple(self, color): return elif color in color_names[:16]: - self.rgb = from_html(color_html[color_names.index(color)]) + self.number = color_names.index(color) + self.rgb = from_html(color_html[self.number]) self.simple = True elif isinstance(color, int) and 0 <= color < 16: + self.number = color self.rgb = from_html(color_html[color]) self.simple = True @@ -153,20 +159,20 @@ def from_full(cls, color, fg=True): def _from_full(self, color): try: color = color.lower() - color = color.replace(' ','_') + color = color.replace(' ','') + color = color.replace('_','') except AttributeError: pass if color == 'reset': return - elif color in color_names: - self.rgb = from_html(color_html[color_names.index(color)]) - elif color in _lower_camel_names: - self.rgb = from_html(color_html[_lower_camel_names.index(color)]) + self.number = _lower_camel_names.index(color) + self.rgb = from_html(color_html[self.number]) elif isinstance(color, int) and 0 <= color <= 255: + self.number = color self.rgb = from_html(color_html[color]) else: diff --git a/tests/test_color.py b/tests/test_color.py index 207f69f1a..fb88fb5c9 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -50,7 +50,7 @@ def testLoadColorByName(self): self.assertEqual(COLOR.deepskyblue1, COLOR[39]) self.assertEqual(COLOR.Deep_Sky_Blue1, COLOR[39]) - self.asserRaises(AttributeError, lambda: COLOR.Notacoloratall) + self.assertRaises(AttributeError, lambda: COLOR.Notacoloratall) From 86247900b3c7054f8c254bffff90374364bbe02a Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Tue, 21 Jul 2015 09:19:53 -0500 Subject: [PATCH 48/80] Reworked html portion of color names, cleaner, auto-generated. --- plumbum/color/names.py | 529 +++++++++++++++++++++-------------------- 1 file changed, 269 insertions(+), 260 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 184877c63..bcf2bd4d6 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -6,267 +6,276 @@ rgb values with ``r=int(html[n][1:3],16)``, etc. ''' -_named_colors = '''\ -black,#000000 -red,#c00000 -green,#00c000 -yellow,#c0c000 -blue,#0000c0 -magenta,#c000c0 -cyan,#00c0c0 -light_gray,#c0c0c0 -dark_gray,#808080 -light_red,#ff0000 -light_green,#00ff00 -light_yellow,#ffff00 -light_blue,#0000ff -light_magenta,#ff00ff -light_cyan,#00ffff -white,#ffffff -grey_0,#000000 -navy_blue,#00005f -dark_blue,#000087 -blue_3,#0000af -blue_3,#0000d7 -blue_1,#0000ff -dark_green,#005f00 -deep_sky_blue_4,#005f5f -deep_sky_blue_4,#005f87 -deep_sky_blue_4,#005faf -dodger_blue_3,#005fd7 -dodger_blue_2,#005fff -green_4,#008700 -spring_green_4,#00875f -turquoise_4,#008787 -deep_sky_blue_3,#0087af -deep_sky_blue_3,#0087d7 -dodger_blue_1,#0087ff -green_3,#00af00 -spring_green_3,#00af5f -dark_cyan,#00af87 -light_sea_green,#00afaf -deep_sky_blue_2,#00afd7 -deep_sky_blue_1,#00afff -green_3,#00d700 -spring_green_3,#00d75f -spring_green_2,#00d787 -cyan_3,#00d7af -dark_turquoise,#00d7d7 -turquoise_2,#00d7ff -green_1,#00ff00 -spring_green_2,#00ff5f -spring_green_1,#00ff87 -medium_spring_green,#00ffaf -cyan_2,#00ffd7 -cyan_1,#00ffff -dark_red,#5f0000 -deep_pink_4,#5f005f -purple_4,#5f0087 -purple_4,#5f00af -purple_3,#5f00d7 -blue_violet,#5f00ff -orange_4,#5f5f00 -grey_37,#5f5f5f -medium_purple_4,#5f5f87 -slate_blue_3,#5f5faf -slate_blue_3,#5f5fd7 -royal_blue_1,#5f5fff -chartreuse_4,#5f8700 -dark_sea_green_4,#5f875f -pale_turquoise_4,#5f8787 -steel_blue,#5f87af -steel_blue_3,#5f87d7 -cornflower_blue,#5f87ff -chartreuse_3,#5faf00 -dark_sea_green_4,#5faf5f -cadet_blue,#5faf87 -cadet_blue,#5fafaf -sky_blue_3,#5fafd7 -steel_blue_1,#5fafff -chartreuse_3,#5fd700 -pale_green_3,#5fd75f -sea_green_3,#5fd787 -aquamarine_3,#5fd7af -medium_turquoise,#5fd7d7 -steel_blue_1,#5fd7ff -chartreuse_2,#5fff00 -sea_green_2,#5fff5f -sea_green_1,#5fff87 -sea_green_1,#5fffaf -aquamarine_1,#5fffd7 -dark_slate_gray_2,#5fffff -dark_red,#870000 -deep_pink_4,#87005f -dark_magenta,#870087 -dark_magenta,#8700af -dark_violet,#8700d7 -purple,#8700ff -orange_4,#875f00 -light_pink_4,#875f5f -plum_4,#875f87 -medium_purple_3,#875faf -medium_purple_3,#875fd7 -slate_blue_1,#875fff -yellow_4,#878700 -wheat_4,#87875f -grey_53,#878787 -light_slate_grey,#8787af -medium_purple,#8787d7 -light_slate_blue,#8787ff -yellow_4,#87af00 -dark_olive_green_3,#87af5f -dark_sea_green,#87af87 -light_sky_blue_3,#87afaf -light_sky_blue_3,#87afd7 -sky_blue_2,#87afff -chartreuse_2,#87d700 -dark_olive_green_3,#87d75f -pale_green_3,#87d787 -dark_sea_green_3,#87d7af -dark_slate_gray_3,#87d7d7 -sky_blue_1,#87d7ff -chartreuse_1,#87ff00 -light_green,#87ff5f -light_green,#87ff87 -pale_green_1,#87ffaf -aquamarine_1,#87ffd7 -dark_slate_gray_1,#87ffff -red_3,#af0000 -deep_pink_4,#af005f -medium_violet_red,#af0087 -magenta_3,#af00af -dark_violet,#af00d7 -purple,#af00ff -dark_orange_3,#af5f00 -indian_red,#af5f5f -hot_pink_3,#af5f87 -medium_orchid_3,#af5faf -medium_orchid,#af5fd7 -medium_purple_2,#af5fff -dark_goldenrod,#af8700 -light_salmon_3,#af875f -rosy_brown,#af8787 -grey_63,#af87af -medium_purple_2,#af87d7 -medium_purple_1,#af87ff -gold_3,#afaf00 -dark_khaki,#afaf5f -navajo_white_3,#afaf87 -grey_69,#afafaf -light_steel_blue_3,#afafd7 -light_steel_blue,#afafff -yellow_3,#afd700 -dark_olive_green_3,#afd75f -dark_sea_green_3,#afd787 -dark_sea_green_2,#afd7af -light_cyan_3,#afd7d7 -light_sky_blue_1,#afd7ff -green_yellow,#afff00 -dark_olive_green_2,#afff5f -pale_green_1,#afff87 -dark_sea_green_2,#afffaf -dark_sea_green_1,#afffd7 -pale_turquoise_1,#afffff -red_3,#d70000 -deep_pink_3,#d7005f -deep_pink_3,#d70087 -magenta_3,#d700af -magenta_3,#d700d7 -magenta_2,#d700ff -dark_orange_3,#d75f00 -indian_red,#d75f5f -hot_pink_3,#d75f87 -hot_pink_2,#d75faf -orchid,#d75fd7 -medium_orchid_1,#d75fff -orange_3,#d78700 -light_salmon_3,#d7875f -light_pink_3,#d78787 -pink_3,#d787af -plum_3,#d787d7 -violet,#d787ff -gold_3,#d7af00 -light_goldenrod_3,#d7af5f -tan,#d7af87 -misty_rose_3,#d7afaf -thistle_3,#d7afd7 -plum_2,#d7afff -yellow_3,#d7d700 -khaki_3,#d7d75f -light_goldenrod_2,#d7d787 -light_yellow_3,#d7d7af -grey_84,#d7d7d7 -light_steel_blue_1,#d7d7ff -yellow_2,#d7ff00 -dark_olive_green_1,#d7ff5f -dark_olive_green_1,#d7ff87 -dark_sea_green_1,#d7ffaf -honeydew_2,#d7ffd7 -light_cyan_1,#d7ffff -red_1,#ff0000 -deep_pink_2,#ff005f -deep_pink_1,#ff0087 -deep_pink_1,#ff00af -magenta_2,#ff00d7 -magenta_1,#ff00ff -orange_red_1,#ff5f00 -indian_red_1,#ff5f5f -indian_red_1,#ff5f87 -hot_pink,#ff5faf -hot_pink,#ff5fd7 -medium_orchid_1,#ff5fff -dark_orange,#ff8700 -salmon_1,#ff875f -light_coral,#ff8787 -pale_violet_red_1,#ff87af -orchid_2,#ff87d7 -orchid_1,#ff87ff -orange_1,#ffaf00 -sandy_brown,#ffaf5f -light_salmon_1,#ffaf87 -light_pink_1,#ffafaf -pink_1,#ffafd7 -plum_1,#ffafff -gold_1,#ffd700 -light_goldenrod_2,#ffd75f -light_goldenrod_2,#ffd787 -navajo_white_1,#ffd7af -misty_rose_1,#ffd7d7 -thistle_1,#ffd7ff -yellow_1,#ffff00 -light_goldenrod_1,#ffff5f -khaki_1,#ffff87 -wheat_1,#ffffaf -cornsilk_1,#ffffd7 -grey_10_0,#ffffff -grey_3,#080808 -grey_7,#121212 -grey_11,#1c1c1c -grey_15,#262626 -grey_19,#303030 -grey_23,#3a3a3a -grey_27,#444444 -grey_30,#4e4e4e -grey_35,#585858 -grey_39,#626262 -grey_42,#6c6c6c -grey_46,#767676 -grey_50,#808080 -grey_54,#8a8a8a -grey_58,#949494 -grey_62,#9e9e9e -grey_66,#a8a8a8 -grey_70,#b2b2b2 -grey_74,#bcbcbc -grey_78,#c6c6c6 -grey_82,#d0d0d0 -grey_85,#dadada -grey_89,#e4e4e4 -grey_93,#eeeeee -''' +color_names = '''\ +black +red +green +yellow +blue +magenta +cyan +light_gray +dark_gray +light_red +light_green +light_yellow +light_blue +light_magenta +light_cyan +white +grey_0 +navy_blue +dark_blue +blue_3 +blue_3 +blue_1 +dark_green +deep_sky_blue_4 +deep_sky_blue_4 +deep_sky_blue_4 +dodger_blue_3 +dodger_blue_2 +green_4 +spring_green_4 +turquoise_4 +deep_sky_blue_3 +deep_sky_blue_3 +dodger_blue_1 +green_3 +spring_green_3 +dark_cyan +light_sea_green +deep_sky_blue_2 +deep_sky_blue_1 +green_3 +spring_green_3 +spring_green_2 +cyan_3 +dark_turquoise +turquoise_2 +green_1 +spring_green_2 +spring_green_1 +medium_spring_green +cyan_2 +cyan_1 +dark_red +deep_pink_4 +purple_4 +purple_4 +purple_3 +blue_violet +orange_4 +grey_37 +medium_purple_4 +slate_blue_3 +slate_blue_3 +royal_blue_1 +chartreuse_4 +dark_sea_green_4 +pale_turquoise_4 +steel_blue +steel_blue_3 +cornflower_blue +chartreuse_3 +dark_sea_green_4 +cadet_blue +cadet_blue +sky_blue_3 +steel_blue_1 +chartreuse_3 +pale_green_3 +sea_green_3 +aquamarine_3 +medium_turquoise +steel_blue_1 +chartreuse_2 +sea_green_2 +sea_green_1 +sea_green_1 +aquamarine_1 +dark_slate_gray_2 +dark_red +deep_pink_4 +dark_magenta +dark_magenta +dark_violet +purple +orange_4 +light_pink_4 +plum_4 +medium_purple_3 +medium_purple_3 +slate_blue_1 +yellow_4 +wheat_4 +grey_53 +light_slate_grey +medium_purple +light_slate_blue +yellow_4 +dark_olive_green_3 +dark_sea_green +light_sky_blue_3 +light_sky_blue_3 +sky_blue_2 +chartreuse_2 +dark_olive_green_3 +pale_green_3 +dark_sea_green_3 +dark_slate_gray_3 +sky_blue_1 +chartreuse_1 +light_green +light_green +pale_green_1 +aquamarine_1 +dark_slate_gray_1 +red_3 +deep_pink_4 +medium_violet_red +magenta_3 +dark_violet +purple +dark_orange_3 +indian_red +hot_pink_3 +medium_orchid_3 +medium_orchid +medium_purple_2 +dark_goldenrod +light_salmon_3 +rosy_brown +grey_63 +medium_purple_2 +medium_purple_1 +gold_3 +dark_khaki +navajo_white_3 +grey_69 +light_steel_blue_3 +light_steel_blue +yellow_3 +dark_olive_green_3 +dark_sea_green_3 +dark_sea_green_2 +light_cyan_3 +light_sky_blue_1 +green_yellow +dark_olive_green_2 +pale_green_1 +dark_sea_green_2 +dark_sea_green_1 +pale_turquoise_1 +red_3 +deep_pink_3 +deep_pink_3 +magenta_3 +magenta_3 +magenta_2 +dark_orange_3 +indian_red +hot_pink_3 +hot_pink_2 +orchid +medium_orchid_1 +orange_3 +light_salmon_3 +light_pink_3 +pink_3 +plum_3 +violet +gold_3 +light_goldenrod_3 +tan +misty_rose_3 +thistle_3 +plum_2 +yellow_3 +khaki_3 +light_goldenrod_2 +light_yellow_3 +grey_84 +light_steel_blue_1 +yellow_2 +dark_olive_green_1 +dark_olive_green_1 +dark_sea_green_1 +honeydew_2 +light_cyan_1 +red_1 +deep_pink_2 +deep_pink_1 +deep_pink_1 +magenta_2 +magenta_1 +orange_red_1 +indian_red_1 +indian_red_1 +hot_pink +hot_pink +medium_orchid_1 +dark_orange +salmon_1 +light_coral +pale_violet_red_1 +orchid_2 +orchid_1 +orange_1 +sandy_brown +light_salmon_1 +light_pink_1 +pink_1 +plum_1 +gold_1 +light_goldenrod_2 +light_goldenrod_2 +navajo_white_1 +misty_rose_1 +thistle_1 +yellow_1 +light_goldenrod_1 +khaki_1 +wheat_1 +cornsilk_1 +grey_10_0 +grey_3 +grey_7 +grey_11 +grey_15 +grey_19 +grey_23 +grey_27 +grey_30 +grey_35 +grey_39 +grey_42 +grey_46 +grey_50 +grey_54 +grey_58 +grey_62 +grey_66 +grey_70 +grey_74 +grey_78 +grey_82 +grey_85 +grey_89 +grey_93'''.split() + +_greys = (3.4, 7.4, 11, 15, 19, 23, 26.7, 30.49, 34.6, 38.6, 42.4, 46.4, 50, 54, 58, 62, 66, 69.8, 73.8, 77.7, 81.6, 85.3, 89.3, 93) + +_grey_html = ['#' + format(int(x/100*16*16),'02x')*3 for x in _greys] + +_normals = [int(x,16) for x in '0 5f 87 af d7 ff'.split()] +_normal_html = ['#' + format(_normals[n//36],'02x') + format(_normals[n//6%6],'02x') + format(_normals[n%6],'02x') for n in range(16-16,232-16)] -color_names = [n.split(',')[0] for n in _named_colors.split()] -color_html = [n.split(',')[1] for n in _named_colors.split()] +_base_pattern = [(n//4,n//2%2,n%2) for n in range(8)] +_base_html = (['#{2:02x}{1:02x}{0:02x}'.format(x[0]*192,x[1]*192,x[2]*192) for x in _base_pattern] + + ['#808080'] + + ['#{2:02x}{1:02x}{0:02x}'.format(x[0]*255,x[1]*255,x[2]*255) for x in _base_pattern][1:]) +color_html = _base_html + _normal_html + _grey_html color_codes_simple = list(range(8)) + list(range(60,68)) """Simple colors, remember that reset is #9, second half is non as common.""" From c5aa031266f0e29a7460c1c71f4390652da18dfe Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 21 Jul 2015 11:06:07 -0500 Subject: [PATCH 49/80] Fixed division issue on Python 2 --- plumbum/color/names.py | 2 ++ tests/test_style.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index bcf2bd4d6..0be13186a 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -6,6 +6,8 @@ rgb values with ``r=int(html[n][1:3],16)``, etc. ''' +from __future__ import division, print_function + color_names = '''\ black red diff --git a/tests/test_style.py b/tests/test_style.py index 6b4e9a89a..a66795193 100644 --- a/tests/test_style.py +++ b/tests/test_style.py @@ -29,7 +29,7 @@ class TestColorLoad(unittest.TestCase): def test_rgb(self): blue = Color(0,0,255) # Red, Green, Blue self.assertEqual(blue.rgb, (0,0,255)) - + def test_simple_name(self): green = Color.from_simple('green') self.assertEqual(green.number, 2) From 629178df4343392a51b53714b2f0c7a5fc77028a Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 21 Jul 2015 12:06:44 -0500 Subject: [PATCH 50/80] Better docs, improvement for [] access --- README.rst | 6 ++-- docs/_cheatsheet.rst | 6 ++-- docs/color.rst | 63 ++++++++++++++++++++++++++------------ plumbum/color/factories.py | 5 ++- tests/test_color.py | 5 +++ 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/README.rst b/README.rst index ce90459b0..c4fc3829f 100644 --- a/README.rst +++ b/README.rst @@ -135,9 +135,9 @@ Sample output:: with COLOR.RED: print("This library provides safe, flexible color access.") print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 16 colors or", '256 named colors,' << COLOR['Orchid'] + COLOR.UNDERLINE, - "or full hex colors" << COLOR("#129240"), 'can be used.') - print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") + print("The simple 16 colors or", '256 named colors,' << COLOR.ORCHID + COLOR.UNDERLINE, + "or full hex colors" << COLOR["#129240"], 'can be used.') + print("Unsafe " + COLOR.BG.DARK_KHAKI + "color access" - COLOR.BG + " is available too.") diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index e44ca964f..d1e361f1c 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -112,9 +112,9 @@ Sample output:: with COLOR.RED: print("This library provides safe, flexible color access.") print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 16 colors or", '256 named colors,' << COLOR['Orchid'] + COLOR.UNDERLINE, - "or full hex colors" << COLOR("#129240"), 'can be used.') - print("Unsafe " + COLOR.BG['DarkKhaki'] + "color access" - COLOR.BG + " is available too.") + print("The simple 16 colors or", '256 named colors,' << COLOR.ORCHID + COLOR.UNDERLINE, + "or full hex colors" << COLOR["#129240"], 'can be used.') + print("Unsafe " + COLOR.BG.DARK_KHAKI + "color access" - COLOR.BG + " is available too.") Sample output: diff --git a/docs/color.rst b/docs/color.rst index e834bdf70..da38b682d 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -20,34 +20,58 @@ API for creating other color schemes for other systems using escapes. system can display color. You can force the use of color globally by setting ``COLOR.use_color=True``. -The Color Factory -================= +Generating colors +================ Styles are accessed through the ``COLOR`` object, which is an instance of a StyleFactory. -The ``COLOR`` object has the following properties: + +Style Factory +^^^^^^^^^^^^^ + +The ``COLOR`` object has the following available objects: ``FG`` and ``BG`` The foreground and background colors, reset to default with ``COLOR.FG.RESET`` - or ``~COLOR.FG`` and likewise for ``BG``. (Named foreground colors are available - directly as well). The first 16 primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, - ``BLUE``, ``MAGENTA``, ``CYAN``, etc, as well as ``RESET``, are available. - You can also access colors numerically with ``COLOR.FG(n)`` or ``COLOR.FG(n)`` - with the extended 256 color codes. These - are ``ColorFactory`` instances. + or ``~COLOR.FG`` and likewise for ``BG``. These are ``ColorFactory`` instances. ``BOLD``, ``DIM``, ``UNDERLINE``, ``ITALICS``, ``REVERSE``, ``STRIKEOUT``, and ``HIDDEN`` - All the `ANSI` modifiers are available, as well as their negations, such as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. + All the `ANSI` modifiers are available, as well as their negations, such + as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. (These are generated automatically + based on the Style attached to the factory.) ``RESET`` The global reset will restore all properties at once. ``DO_NOTHING`` Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. -The ``COLOR`` object can be used in a with statement, which resets the color on leaving -the statement body. (The ``FG`` and ``BG`` also can be put in with statements, and they -will restore the foreground and background color, respectively). Although it does support -some of the same things as a Style, its primary purpose is to generate Styles. +The ``COLOR`` object can be used in a with statement, which resets all styles on leaving +the statement body. Although factories do support +some of the same methods as a Style, their primary purpose is to generate Styles. The COLOR object has a +``use_color`` property that can be set to force the use of color. A ``stdout`` property is provided +to make changing the output of color statement easier. A ``COLOR.from_ansi(code)`` method allows +you to create a Style from any ansi sequence, even complex or combined ones. + +Color Factories +^^^^^^^^^^^^^^^ + +The ``COLOR.FG`` and ``COLOR.BG`` are ``ColorFactory``'s. In fact, the COLOR object itself acts exactly +like the ``COLOR.FG`` object, with the exception of the properties listed above. + +Named foreground colors are available +directly as methods. The first 16 primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, +``BLUE``, ``MAGENTA``, ``CYAN``, etc, as well as ``RESET``, are available. All 256 color +names are available, but do not populate factory directly, so that auto-completion +gives reasonable results. You can also access colors using strings and do ``COLOR[string]``. +Capitalization, underscores, and spaces (for strings) will be ignored. + +You can also access colors numerically with ``COLOR(n)`` or ``COLOR[n]`` +with the extended 256 color codes. The former will default to simple versions of +colors for the first 16 values. The later notation can also be used to slice. +Full hex codes can be used, too. If no match is found, +these will be the true 24 bit color value. + +The ``FG`` and ``BG`` also can be put in with statements, and they +will restore the foreground and background color only, respectively. -If you call ``COLOR.from_ansi(seq)``, you can manually pass in any `ANSI` escape sequence, -even complex or combined ones. ``COLOR.rgb(r,g,b)`` will create a color from an +``COLOR.rgb(r,g,b)`` will create a color from an input red, green, and blue values (integers from 0-255). ``COLOR.hex(code)`` will allow you to input an html style hex sequence. These work on ``FG`` and ``BG`` too. The ``repr`` of styles is smart and will show you the closest color to the one you selected if you didn't exactly @@ -123,7 +147,7 @@ These produce strings that can be further manipulated or printed. free to use this method. Finally, you can also print a color to stdout directly using ``color("string")`` or -``color.print("string")``. Since the first can be an unsafe operation if you forget an arguement, +``color.print("string")``. Since the first can be an unsafe operation if you forget an argument, you may prefer the latter. This has the same syntax as the Python 3 print function. In Python 2, if you do not have ``from __future__ import print_function`` enabled, ``color.print_("string")`` is provided as @@ -165,9 +189,10 @@ While this library supports full 24 bit colors through escape sequences, the library has special support for the "full" 256 colorset through numbers, names or HEX html codes. Even if you use 24 bit color, the closest name is displayed in the ``repr``. You can access the colors as -as ``COLOR.FG(12)``, ``COLOR.FG('Light_Blue')``, ``COLOR.FG('LightBlue')``, or ``COLOR.FG('#0000FF')``. +as ``COLOR.FG.Light_Blue``, ``COLOR.FG.LIGHTBLUE``, ``COLOR.FG[12]``, ``COLOR.FG('Light_Blue')``, +``COLOR.FG('LightBlue')``, or ``COLOR.FG('#0000FF')``. You can also iterate or slice the ``COLOR``, ``COLOR.FG``, or ``COLOR.BG`` objects. Slicing even -intelegently downgrades to the simple version of the codes if it is within the first 16 elements. +intelligently downgrades to the simple version of the codes if it is within the first 16 elements. The supported colors are: .. raw:: html diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 2c833ed4a..5bc1f597d 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -62,7 +62,10 @@ def __getitem__(self, val): else: return [self.full(v) for v in range(start, stop, stride)] except AttributeError: - return self.full(val) + try: + return self.full(val) + except ColorNotFound: + return self.hex(val) def __call__(self, val): """Shortcut to provide way to access colors.""" diff --git a/tests/test_color.py b/tests/test_color.py index fb88fb5c9..d1c934480 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -19,6 +19,11 @@ def testColorSlice(self): self.assertEqual(len(vals),10) self.assertEqual(vals[1], COLOR.full(41)) + def testLoadNumericalColor(self): + self.assertEqual(COLOR.full(2), COLOR[2]) + self.assertEqual(COLOR.simple(2), COLOR(2)) + self.assertEqual(COLOR(54), COLOR[54]) + def testColorStrings(self): self.assertEqual('\033[0m', COLOR.RESET) self.assertEqual('\033[1m', COLOR.BOLD) From 150dfe887e957d27fe4b6b74ecd1a70fe6fba1e5 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 21 Jul 2015 12:06:59 -0500 Subject: [PATCH 51/80] Made all color names unique. --- plumbum/color/names.py | 108 ++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 0be13186a..3cba18336 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -29,19 +29,19 @@ navy_blue dark_blue blue_3 -blue_3 +blue_3a blue_1 dark_green deep_sky_blue_4 -deep_sky_blue_4 -deep_sky_blue_4 +deep_sky_blue_4a +deep_sky_blue_4b dodger_blue_3 dodger_blue_2 green_4 spring_green_4 turquoise_4 deep_sky_blue_3 -deep_sky_blue_3 +deep_sky_blue_3a dodger_blue_1 green_3 spring_green_3 @@ -49,14 +49,14 @@ light_sea_green deep_sky_blue_2 deep_sky_blue_1 -green_3 -spring_green_3 +green_3a +spring_green_3a spring_green_2 cyan_3 dark_turquoise turquoise_2 green_1 -spring_green_2 +spring_green_2a spring_green_1 medium_spring_green cyan_2 @@ -64,14 +64,14 @@ dark_red deep_pink_4 purple_4 -purple_4 +purple_4a purple_3 blue_violet orange_4 grey_37 medium_purple_4 slate_blue_3 -slate_blue_3 +slate_blue_3a royal_blue_1 chartreuse_4 dark_sea_green_4 @@ -80,34 +80,34 @@ steel_blue_3 cornflower_blue chartreuse_3 -dark_sea_green_4 -cadet_blue +dark_sea_green_4a cadet_blue +cadet_blue_a sky_blue_3 steel_blue_1 -chartreuse_3 +chartreuse_3a pale_green_3 sea_green_3 aquamarine_3 medium_turquoise -steel_blue_1 -chartreuse_2 +steel_blue_1a +chartreuse_2a sea_green_2 sea_green_1 -sea_green_1 +sea_green_1a aquamarine_1 dark_slate_gray_2 -dark_red -deep_pink_4 -dark_magenta +dark_red_a +deep_pink_4a dark_magenta +dark_magenta_a dark_violet purple -orange_4 +orange_4a light_pink_4 plum_4 medium_purple_3 -medium_purple_3 +medium_purple_3a slate_blue_1 yellow_4 wheat_4 @@ -115,30 +115,30 @@ light_slate_grey medium_purple light_slate_blue -yellow_4 +yellow_4_a dark_olive_green_3 dark_sea_green light_sky_blue_3 -light_sky_blue_3 +light_sky_blue_3a sky_blue_2 chartreuse_2 -dark_olive_green_3 -pale_green_3 +dark_olive_green_3a +pale_green_3a dark_sea_green_3 dark_slate_gray_3 sky_blue_1 chartreuse_1 -light_green -light_green +light_green_a +light_green_b pale_green_1 -aquamarine_1 +aquamarine_1a dark_slate_gray_1 red_3 -deep_pink_4 +deep_pink_4b medium_violet_red magenta_3 -dark_violet -purple +dark_violet_a +purple_a dark_orange_3 indian_red hot_pink_3 @@ -149,7 +149,7 @@ light_salmon_3 rosy_brown grey_63 -medium_purple_2 +medium_purple_2a medium_purple_1 gold_3 dark_khaki @@ -158,42 +158,42 @@ light_steel_blue_3 light_steel_blue yellow_3 -dark_olive_green_3 -dark_sea_green_3 +dark_olive_green_3b +dark_sea_green_3a dark_sea_green_2 light_cyan_3 light_sky_blue_1 green_yellow dark_olive_green_2 -pale_green_1 -dark_sea_green_2 +pale_green_1a +dark_sea_green_2a dark_sea_green_1 pale_turquoise_1 -red_3 +red_3a deep_pink_3 -deep_pink_3 -magenta_3 -magenta_3 +deep_pink_3a +magenta_3a +magenta_3b magenta_2 -dark_orange_3 -indian_red -hot_pink_3 +dark_orange_3a +indian_red_a +hot_pink_3a hot_pink_2 orchid medium_orchid_1 orange_3 -light_salmon_3 +light_salmon_3a light_pink_3 pink_3 plum_3 violet -gold_3 +gold_3a light_goldenrod_3 tan misty_rose_3 thistle_3 plum_2 -yellow_3 +yellow_3a khaki_3 light_goldenrod_2 light_yellow_3 @@ -201,22 +201,22 @@ light_steel_blue_1 yellow_2 dark_olive_green_1 -dark_olive_green_1 -dark_sea_green_1 +dark_olive_green_1a +dark_sea_green_1a honeydew_2 light_cyan_1 red_1 deep_pink_2 deep_pink_1 -deep_pink_1 -magenta_2 +deep_pink_1a +magenta_2a magenta_1 orange_red_1 indian_red_1 -indian_red_1 -hot_pink +indian_red_1a hot_pink -medium_orchid_1 +hot_pink_a +medium_orchid_1a dark_orange salmon_1 light_coral @@ -230,8 +230,8 @@ pink_1 plum_1 gold_1 -light_goldenrod_2 -light_goldenrod_2 +light_goldenrod_2a +light_goldenrod_2b navajo_white_1 misty_rose_1 thistle_1 From 0bf4793ab805ec3f668eef32d16a505c53fc1ace Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Tue, 21 Jul 2015 17:03:08 -0500 Subject: [PATCH 52/80] Moving tests to better names, prepping for some mild speedups for lookup. --- tests/test_color.py | 238 +++++++++++---------------------------- tests/test_colornames.py | 11 ++ tests/test_factories.py | 179 +++++++++++++++++++++++++++++ tests/test_style.py | 75 ------------ 4 files changed, 257 insertions(+), 246 deletions(-) create mode 100644 tests/test_colornames.py create mode 100644 tests/test_factories.py delete mode 100644 tests/test_style.py diff --git a/tests/test_color.py b/tests/test_color.py index d1c934480..a66795193 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,179 +1,75 @@ -#!/usr/bin/env python from __future__ import with_statement, print_function import unittest -from plumbum import COLOR -from plumbum.color.styles import ANSIStyle as Style, ColorNotFound -from plumbum.color import HTMLCOLOR -import sys +from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound +from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color + + +class TestNearestColor(unittest.TestCase): + def test_exact(self): + self.assertEqual(find_nearest_color(0,0,0),0) + for n,color in enumerate(color_html): + # Ignoring duplicates + if n not in (16, 21, 46, 51, 196, 201, 226, 231, 244): + rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) + self.assertEqual(find_nearest_color(*rgb),n) + + def test_nearby(self): + self.assertEqual(find_nearest_color(1,2,2),0) + self.assertEqual(find_nearest_color(7,7,9),232) + + def test_simplecolor(self): + self.assertEqual(find_nearest_simple_color(1,2,4), 0) + self.assertEqual(find_nearest_simple_color(0,255,0), 2) + self.assertEqual(find_nearest_simple_color(100,100,0), 3) + self.assertEqual(find_nearest_simple_color(140,140,140), 7) + + +class TestColorLoad(unittest.TestCase): + + def test_rgb(self): + blue = Color(0,0,255) # Red, Green, Blue + self.assertEqual(blue.rgb, (0,0,255)) + + def test_simple_name(self): + green = Color.from_simple('green') + self.assertEqual(green.number, 2) + + def test_different_names(self): + self.assertEqual(Color('Dark Blue'), + Color('Dark_Blue')) + self.assertEqual(Color('Dark_blue'), + Color('Dark_Blue')) + self.assertEqual(Color('DARKBLUE'), + Color('Dark_Blue')) + self.assertEqual(Color('DarkBlue'), + Color('Dark_Blue')) + self.assertEqual(Color('Dark Green'), + Color('Dark_Green')) + + def test_loading_methods(self): + self.assertEqual(Color("Yellow"), + Color.from_full("Yellow")) + self.assertNotEqual(Color.from_full("yellow"), + Color.from_simple("yellow")) + class TestANSIColor(unittest.TestCase): + def setUp(self): + ANSIStyle.use_color = True + + def test_ansi(self): + self.assertEqual(str(ANSIStyle(fgcolor=Color('reset'))), '\033[39m') + self.assertEqual(str(ANSIStyle(fgcolor=Color('green'))), '\033[38;5;2m') + self.assertEqual(str(ANSIStyle(fgcolor=Color.from_simple('red'))), '\033[31m') + +class TestStyle(unittest.TestCase): def setUp(self): - COLOR.use_color = True - - def testColorSlice(self): - vals = COLOR[:8] - self.assertEqual(len(vals),8) - self.assertEqual(vals[1], COLOR.RED) - vals = COLOR[40:50] - self.assertEqual(len(vals),10) - self.assertEqual(vals[1], COLOR.full(41)) - - def testLoadNumericalColor(self): - self.assertEqual(COLOR.full(2), COLOR[2]) - self.assertEqual(COLOR.simple(2), COLOR(2)) - self.assertEqual(COLOR(54), COLOR[54]) - - def testColorStrings(self): - self.assertEqual('\033[0m', COLOR.RESET) - self.assertEqual('\033[1m', COLOR.BOLD) - self.assertEqual('\033[39m', COLOR.FG.RESET) - - def testNegateIsReset(self): - self.assertEqual(COLOR.RESET, -COLOR) - self.assertEqual(COLOR.FG.RESET, -COLOR.FG) - self.assertEqual(COLOR.BG.RESET, -COLOR.BG) - - def testShifts(self): - self.assertEqual("This" << COLOR.RED, "This" >> COLOR.RED) - self.assertEqual("This" << COLOR.RED, "This" << COLOR.RED) - if sys.version_info >= (2, 7): - self.assertEqual("This" << COLOR.RED, "This" * COLOR.RED) - self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") - self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") - self.assertEqual("This" << COLOR.RED, COLOR.RED * "This") - self.assertEqual(COLOR.RED.wrap("This"), "This" << COLOR.RED) - - def testLoadColorByName(self): - self.assertEqual(COLOR['LightBlue'], COLOR.FG['LightBlue']) - self.assertEqual(COLOR.BG['light_green'], COLOR.BG['LightGreen']) - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR.hex('#00afff')) - - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) - self.assertEqual(COLOR.DeepSkyBlue1, COLOR[39]) - self.assertEqual(COLOR.deepskyblue1, COLOR[39]) - self.assertEqual(COLOR.Deep_Sky_Blue1, COLOR[39]) - - self.assertRaises(AttributeError, lambda: COLOR.Notacoloratall) - - - - - def testMultiColor(self): - sumcolor = COLOR.BOLD + COLOR.BLUE - self.assertEqual(COLOR.BOLD.RESET + COLOR.FG.RESET, -sumcolor) - - def testSums(self): - # Sums should not be communitave, last one is used - self.assertEqual(COLOR.RED, COLOR.BLUE + COLOR.RED) - self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) - - def testFromAnsi(self): - for color in COLOR[1:7]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG[1:7]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR[:16]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG[:16]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.ITALICS): - self.assertEqual(color, COLOR.from_ansi(str(color))) - - color = COLOR.BOLD + COLOR.FG.GREEN + COLOR.BG.BLUE + COLOR.UNDERLINE - self.assertEqual(color, COLOR.from_ansi(str(color))) - color = COLOR.RESET - self.assertEqual(color, COLOR.from_ansi(str(color))) - - def testWrappedColor(self): - string = 'This is a string' - wrapped = '\033[31mThis is a string\033[39m' - self.assertEqual(COLOR.RED.wrap(string), wrapped) - self.assertEqual(string << COLOR.RED, wrapped) - self.assertEqual(COLOR.RED*string, wrapped) - self.assertEqual(COLOR.RED[string], wrapped) - - newcolor = COLOR.BLUE + COLOR.UNDERLINE - self.assertEqual(newcolor[string], string << newcolor) - self.assertEqual(newcolor.wrap(string), string << COLOR.BLUE + COLOR.UNDERLINE) - - def testUndoColor(self): - self.assertEqual('\033[39m', -COLOR.FG) - self.assertEqual('\033[39m', ~COLOR.FG) - self.assertEqual('\033[39m', ''-COLOR.FG) - self.assertEqual('\033[49m', -COLOR.BG) - self.assertEqual('\033[49m', ''-COLOR.BG) - self.assertEqual('\033[21m', -COLOR.BOLD) - self.assertEqual('\033[22m', -COLOR.DIM) - for i in range(7): - self.assertEqual('\033[39m', -COLOR(i)) - self.assertEqual('\033[49m', -COLOR.BG(i)) - self.assertEqual('\033[39m', -COLOR.FG(i)) - self.assertEqual('\033[49m', -COLOR.BG(i)) - for i in range(256): - self.assertEqual('\033[39m', -COLOR.FG[i]) - self.assertEqual('\033[49m', -COLOR.BG[i]) - self.assertEqual('\033[0m', -COLOR.RESET) - self.assertEqual(COLOR.DO_NOTHING, -COLOR.DO_NOTHING) - - self.assertEqual(COLOR.BOLD.RESET, -COLOR.BOLD) - - def testLackOfColor(self): - Style.use_color = False - self.assertEqual('', COLOR.FG.RED) - self.assertEqual('', -COLOR.FG) - self.assertEqual('', COLOR.FG['LightBlue']) - - def testFromHex(self): - self.assertRaises(ColorNotFound, lambda: COLOR.hex('asdf')) - self.assertRaises(ColorNotFound, lambda: COLOR.hex('#1234Z2')) - self.assertRaises(ColorNotFound, lambda: COLOR.hex(12)) - - def testDirectCall(self): - COLOR.BLUE() - - if not hasattr(sys.stdout, "getvalue"): - self.fail("Need to run in buffered mode!") - - output = sys.stdout.getvalue().strip() - self.assertEquals(output,str(COLOR.BLUE)) - - def testDirectCallArgs(self): - COLOR.BLUE("This is") - - if not hasattr(sys.stdout, "getvalue"): - self.fail("Need to run in buffered mode!") - - output = sys.stdout.getvalue().strip() - self.assertEquals(output,str("This is" << COLOR.BLUE)) - - def testPrint(self): - COLOR.YELLOW.print('This is printed to stdout') - - if not hasattr(sys.stdout, "getvalue"): - self.fail("Need to run in buffered mode!") - - output = sys.stdout.getvalue().strip() - self.assertEquals(output,str(COLOR.YELLOW.wrap('This is printed to stdout'))) - - -class TestHTMLColor(unittest.TestCase): - def test_html(self): - red_tagged = 'This is tagged' - self.assertEqual(HTMLCOLOR.RED["This is tagged"], red_tagged) - self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) - self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) - - twin_tagged = 'This is tagged' - self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) - self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) - self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) - self.assertEqual(HTMLCOLOR.RED << "This should be wrapped", "This should be wrapped" << HTMLCOLOR.RED) + ANSIStyle.use_color = True + + def test_InvalidAttributes(self): + pass + if __name__ == '__main__': - unittest.main(buffer=True) + unittest.main() diff --git a/tests/test_colornames.py b/tests/test_colornames.py new file mode 100644 index 000000000..b986327dc --- /dev/null +++ b/tests/test_colornames.py @@ -0,0 +1,11 @@ +from __future__ import with_statement, print_function +import unittest +from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color, find_nearest_colorblock + +class TestColorConvert(unittest.TestCase) + + def test_colorblock(self): + + red = (255, 0, 0) + midway = None + nearby = None diff --git a/tests/test_factories.py b/tests/test_factories.py new file mode 100644 index 000000000..d1c934480 --- /dev/null +++ b/tests/test_factories.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python +from __future__ import with_statement, print_function +import unittest +from plumbum import COLOR +from plumbum.color.styles import ANSIStyle as Style, ColorNotFound +from plumbum.color import HTMLCOLOR +import sys + +class TestANSIColor(unittest.TestCase): + + def setUp(self): + COLOR.use_color = True + + def testColorSlice(self): + vals = COLOR[:8] + self.assertEqual(len(vals),8) + self.assertEqual(vals[1], COLOR.RED) + vals = COLOR[40:50] + self.assertEqual(len(vals),10) + self.assertEqual(vals[1], COLOR.full(41)) + + def testLoadNumericalColor(self): + self.assertEqual(COLOR.full(2), COLOR[2]) + self.assertEqual(COLOR.simple(2), COLOR(2)) + self.assertEqual(COLOR(54), COLOR[54]) + + def testColorStrings(self): + self.assertEqual('\033[0m', COLOR.RESET) + self.assertEqual('\033[1m', COLOR.BOLD) + self.assertEqual('\033[39m', COLOR.FG.RESET) + + def testNegateIsReset(self): + self.assertEqual(COLOR.RESET, -COLOR) + self.assertEqual(COLOR.FG.RESET, -COLOR.FG) + self.assertEqual(COLOR.BG.RESET, -COLOR.BG) + + def testShifts(self): + self.assertEqual("This" << COLOR.RED, "This" >> COLOR.RED) + self.assertEqual("This" << COLOR.RED, "This" << COLOR.RED) + if sys.version_info >= (2, 7): + self.assertEqual("This" << COLOR.RED, "This" * COLOR.RED) + self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") + self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") + self.assertEqual("This" << COLOR.RED, COLOR.RED * "This") + self.assertEqual(COLOR.RED.wrap("This"), "This" << COLOR.RED) + + def testLoadColorByName(self): + self.assertEqual(COLOR['LightBlue'], COLOR.FG['LightBlue']) + self.assertEqual(COLOR.BG['light_green'], COLOR.BG['LightGreen']) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR.hex('#00afff')) + + self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) + self.assertEqual(COLOR.DeepSkyBlue1, COLOR[39]) + self.assertEqual(COLOR.deepskyblue1, COLOR[39]) + self.assertEqual(COLOR.Deep_Sky_Blue1, COLOR[39]) + + self.assertRaises(AttributeError, lambda: COLOR.Notacoloratall) + + + + + def testMultiColor(self): + sumcolor = COLOR.BOLD + COLOR.BLUE + self.assertEqual(COLOR.BOLD.RESET + COLOR.FG.RESET, -sumcolor) + + def testSums(self): + # Sums should not be communitave, last one is used + self.assertEqual(COLOR.RED, COLOR.BLUE + COLOR.RED) + self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) + + def testFromAnsi(self): + for color in COLOR[1:7]: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG[1:7]: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR[:16]: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in COLOR.BG[:16]: + self.assertEqual(color, COLOR.from_ansi(str(color))) + for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.ITALICS): + self.assertEqual(color, COLOR.from_ansi(str(color))) + + color = COLOR.BOLD + COLOR.FG.GREEN + COLOR.BG.BLUE + COLOR.UNDERLINE + self.assertEqual(color, COLOR.from_ansi(str(color))) + color = COLOR.RESET + self.assertEqual(color, COLOR.from_ansi(str(color))) + + def testWrappedColor(self): + string = 'This is a string' + wrapped = '\033[31mThis is a string\033[39m' + self.assertEqual(COLOR.RED.wrap(string), wrapped) + self.assertEqual(string << COLOR.RED, wrapped) + self.assertEqual(COLOR.RED*string, wrapped) + self.assertEqual(COLOR.RED[string], wrapped) + + newcolor = COLOR.BLUE + COLOR.UNDERLINE + self.assertEqual(newcolor[string], string << newcolor) + self.assertEqual(newcolor.wrap(string), string << COLOR.BLUE + COLOR.UNDERLINE) + + def testUndoColor(self): + self.assertEqual('\033[39m', -COLOR.FG) + self.assertEqual('\033[39m', ~COLOR.FG) + self.assertEqual('\033[39m', ''-COLOR.FG) + self.assertEqual('\033[49m', -COLOR.BG) + self.assertEqual('\033[49m', ''-COLOR.BG) + self.assertEqual('\033[21m', -COLOR.BOLD) + self.assertEqual('\033[22m', -COLOR.DIM) + for i in range(7): + self.assertEqual('\033[39m', -COLOR(i)) + self.assertEqual('\033[49m', -COLOR.BG(i)) + self.assertEqual('\033[39m', -COLOR.FG(i)) + self.assertEqual('\033[49m', -COLOR.BG(i)) + for i in range(256): + self.assertEqual('\033[39m', -COLOR.FG[i]) + self.assertEqual('\033[49m', -COLOR.BG[i]) + self.assertEqual('\033[0m', -COLOR.RESET) + self.assertEqual(COLOR.DO_NOTHING, -COLOR.DO_NOTHING) + + self.assertEqual(COLOR.BOLD.RESET, -COLOR.BOLD) + + def testLackOfColor(self): + Style.use_color = False + self.assertEqual('', COLOR.FG.RED) + self.assertEqual('', -COLOR.FG) + self.assertEqual('', COLOR.FG['LightBlue']) + + def testFromHex(self): + self.assertRaises(ColorNotFound, lambda: COLOR.hex('asdf')) + self.assertRaises(ColorNotFound, lambda: COLOR.hex('#1234Z2')) + self.assertRaises(ColorNotFound, lambda: COLOR.hex(12)) + + def testDirectCall(self): + COLOR.BLUE() + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str(COLOR.BLUE)) + + def testDirectCallArgs(self): + COLOR.BLUE("This is") + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str("This is" << COLOR.BLUE)) + + def testPrint(self): + COLOR.YELLOW.print('This is printed to stdout') + + if not hasattr(sys.stdout, "getvalue"): + self.fail("Need to run in buffered mode!") + + output = sys.stdout.getvalue().strip() + self.assertEquals(output,str(COLOR.YELLOW.wrap('This is printed to stdout'))) + + +class TestHTMLColor(unittest.TestCase): + def test_html(self): + red_tagged = 'This is tagged' + self.assertEqual(HTMLCOLOR.RED["This is tagged"], red_tagged) + self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) + self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) + + twin_tagged = 'This is tagged' + self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) + self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) + self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) + self.assertEqual(HTMLCOLOR.RED << "This should be wrapped", "This should be wrapped" << HTMLCOLOR.RED) + +if __name__ == '__main__': + unittest.main(buffer=True) diff --git a/tests/test_style.py b/tests/test_style.py deleted file mode 100644 index a66795193..000000000 --- a/tests/test_style.py +++ /dev/null @@ -1,75 +0,0 @@ -from __future__ import with_statement, print_function -import unittest -from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound -from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color - - -class TestNearestColor(unittest.TestCase): - def test_exact(self): - self.assertEqual(find_nearest_color(0,0,0),0) - for n,color in enumerate(color_html): - # Ignoring duplicates - if n not in (16, 21, 46, 51, 196, 201, 226, 231, 244): - rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) - self.assertEqual(find_nearest_color(*rgb),n) - - def test_nearby(self): - self.assertEqual(find_nearest_color(1,2,2),0) - self.assertEqual(find_nearest_color(7,7,9),232) - - def test_simplecolor(self): - self.assertEqual(find_nearest_simple_color(1,2,4), 0) - self.assertEqual(find_nearest_simple_color(0,255,0), 2) - self.assertEqual(find_nearest_simple_color(100,100,0), 3) - self.assertEqual(find_nearest_simple_color(140,140,140), 7) - - -class TestColorLoad(unittest.TestCase): - - def test_rgb(self): - blue = Color(0,0,255) # Red, Green, Blue - self.assertEqual(blue.rgb, (0,0,255)) - - def test_simple_name(self): - green = Color.from_simple('green') - self.assertEqual(green.number, 2) - - def test_different_names(self): - self.assertEqual(Color('Dark Blue'), - Color('Dark_Blue')) - self.assertEqual(Color('Dark_blue'), - Color('Dark_Blue')) - self.assertEqual(Color('DARKBLUE'), - Color('Dark_Blue')) - self.assertEqual(Color('DarkBlue'), - Color('Dark_Blue')) - self.assertEqual(Color('Dark Green'), - Color('Dark_Green')) - - def test_loading_methods(self): - self.assertEqual(Color("Yellow"), - Color.from_full("Yellow")) - self.assertNotEqual(Color.from_full("yellow"), - Color.from_simple("yellow")) - - -class TestANSIColor(unittest.TestCase): - def setUp(self): - ANSIStyle.use_color = True - - def test_ansi(self): - self.assertEqual(str(ANSIStyle(fgcolor=Color('reset'))), '\033[39m') - self.assertEqual(str(ANSIStyle(fgcolor=Color('green'))), '\033[38;5;2m') - self.assertEqual(str(ANSIStyle(fgcolor=Color.from_simple('red'))), '\033[31m') - - -class TestStyle(unittest.TestCase): - def setUp(self): - ANSIStyle.use_color = True - - def test_InvalidAttributes(self): - pass - - -if __name__ == '__main__': - unittest.main() From 8fb57ac6b42ed499b22303d68664bef0199201de Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Tue, 21 Jul 2015 19:13:01 -0500 Subject: [PATCH 53/80] Removed stub file for test, won't be needed. --- tests/test_colornames.py | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 tests/test_colornames.py diff --git a/tests/test_colornames.py b/tests/test_colornames.py deleted file mode 100644 index b986327dc..000000000 --- a/tests/test_colornames.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import with_statement, print_function -import unittest -from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color, find_nearest_colorblock - -class TestColorConvert(unittest.TestCase) - - def test_colorblock(self): - - red = (255, 0, 0) - midway = None - nearby = None From 49b18c887e4fe9166bc9a4117ba07ea1ac95838b Mon Sep 17 00:00:00 2001 From: Henry MuonOne Date: Wed, 22 Jul 2015 11:28:36 -0500 Subject: [PATCH 54/80] Move to new color find system, ~8x faster --- plumbum/color/names.py | 86 +++++++++++++++++++++++++++++------------ plumbum/color/styles.py | 6 +-- tests/test_color.py | 28 +++++++++----- 3 files changed, 84 insertions(+), 36 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 3cba18336..94f50c866 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -267,8 +267,9 @@ grey_93'''.split() _greys = (3.4, 7.4, 11, 15, 19, 23, 26.7, 30.49, 34.6, 38.6, 42.4, 46.4, 50, 54, 58, 62, 66, 69.8, 73.8, 77.7, 81.6, 85.3, 89.3, 93) +_grey_vals = [int(x/100.0*16*16) for x in _greys] -_grey_html = ['#' + format(int(x/100*16*16),'02x')*3 for x in _greys] +_grey_html = ['#' + format(x,'02x')*3 for x in _grey_vals] _normals = [int(x,16) for x in '0 5f 87 af d7 ff'.split()] _normal_html = ['#' + format(_normals[n//36],'02x') + format(_normals[n//6%6],'02x') + format(_normals[n%6],'02x') for n in range(16-16,232-16)] @@ -295,34 +296,68 @@ ) #Functions to be used for color name operations -def _distance_to_color(r, g, b, color): - """This computes the distance to a color, should be minimized""" - rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) - return (r-rgb[0])**2 + (g-rgb[1])**2 + (b-rgb[2])**2 +class FindNearest(object): + def __init__(self, r, g, b): + self.r = r + self.b = b + self.g = g -def find_nearest_color(r, g, b, color_slice=slice(None, None, None)): - """This is a slow way to find the nearest color.""" - distances = [_distance_to_color(r, g, b, color) for color in color_html[color_slice]] - return min(range(len(distances)), key=distances.__getitem__) + def very_simple(self): + """This will only return simple colors! + Breaks the colorspace into cubes, returns color""" + midlevel = 0x40 # Since bright is not included -def find_nearest_simple_color(r, g, b): - """This will only return simple colors! - Breaks the colorspace into cubes, returns color""" - midlevel = 0x40 # Since bright is not included + # The colors are originised so that it is a + # 3D cube, black at 0,0,0, white at 1,1,1 + # Compressed to linear_integers r,g,b + # [[[0,1],[2,3]],[[4,5],[6,7]]] + # r*1 + g*2 + b*4 + return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 + + def all_slow(self, color_slice=slice(None, None, None)): + """This is a slow way to find the nearest color.""" + distances = [self._distance_to_color(color) for color in color_html[color_slice]] + return min(range(len(distances)), key=distances.__getitem__) + + def _distance_to_color(self, color): + """This computes the distance to a color, should be minimized.""" + rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) + return (self.r-rgb[0])**2 + (self.g-rgb[1])**2 + (self.b-rgb[2])**2 + + def _distance_to_color_number(self, n): + color = color_html[n] + return self._distance_to_color(color) + + def only_colorblock(self): + """This finds the nearest color based on block system, only works + for 17-232 color values.""" + rint = min(range(len(_normals)), key=[abs(x-self.r) for x in _normals].__getitem__) + bint = min(range(len(_normals)), key=[abs(x-self.b) for x in _normals].__getitem__) + gint = min(range(len(_normals)), key=[abs(x-self.g) for x in _normals].__getitem__) + return (16 + 36 * rint + 6 * gint + bint) + + def only_simple(self): + """Finds the simple color-block color.""" + return self.all_slow(slice(0,16,None)) - # The colors are originised so that it is a - # 3D cube, black at 0,0,0, white at 1,1,1 - # Compressed to linear_integers r,g,b - # [[[0,1],[2,3]],[[4,5],[6,7]]] - # r*1 + g*2 + b*4 - return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 + def only_grey(self): + """Finds the greyscale color.""" + rawval = (self.r + self.b + self.g) / 3 + n = min(range(len(_grey_vals)), key=[abs(x-rawval) for x in _grey_vals].__getitem__) + return n+232 -def find_nearest_colorblock(*rgb): - """This finds the nearest color based on block system, only works - for 17-232 color values.""" - r, g, b = (round(v / 256. * 5) for v in rgb) - return (16 + 36 * r + 6 * g + b) + def all_fast(self): + """Runs roughly 8 times faster than the slow version.""" + colors = [self.only_simple(), self.only_colorblock(), self.only_grey()] + distances = [self._distance_to_color_number(n) for n in colors] + return colors[min(range(len(distances)), key=distances.__getitem__)] + +def find_nearest_color(r, g, b, color_slice=slice(None,None,None)): + return FindNearest(r, g, b).all_slow(color_slice) + +def find_nearest_simple_color(r, g, b): + return FindNearest(r, g, b).very_simple() def from_html(color): """Convert html hex code to rgb""" @@ -330,6 +365,9 @@ def from_html(color): raise ValueError("Invalid length of html code") return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) +def to_html(r, g, b): + return "#{0:02x}{1:02x}{2:02x}".format(r, g, b) + def print_html_table(): """Prints html names for documentation""" diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index d9d9f4cf8..aad6962b6 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -14,7 +14,7 @@ from copy import copy from plumbum.color.names import color_names, color_html from plumbum.color.names import color_codes_simple, from_html -from plumbum.color.names import find_nearest_color, find_nearest_simple_color, attributes_ansi +from plumbum.color.names import FindNearest, attributes_ansi __all__ = ['Color', 'Style', 'ANSIStyle', 'ColorNotFound', 'AttributeNotFound'] @@ -110,10 +110,10 @@ def _init_number(self): self.exact = True else: if self.simple: - self.number = find_nearest_color(self.rgb[0], self.rgb[1], self.rgb[2], slice(0,16)) + self.number = FindNearest(*self.rgb).only_simple() self.exact = self.rgb == from_html(color_html[self.number]) else: - self.number = find_nearest_color(*self.rgb) + self.number = FindNearest(*self.rgb).all_fast() self.exact = self.rgb == from_html(color_html[self.number]) self.reset = False diff --git a/tests/test_color.py b/tests/test_color.py index a66795193..58ea26748 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,27 +1,27 @@ from __future__ import with_statement, print_function import unittest from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound -from plumbum.color.names import find_nearest_color, color_html, find_nearest_simple_color +from plumbum.color.names import color_html, FindNearest class TestNearestColor(unittest.TestCase): def test_exact(self): - self.assertEqual(find_nearest_color(0,0,0),0) + self.assertEqual(FindNearest(0,0,0).all_fast(),0) for n,color in enumerate(color_html): # Ignoring duplicates if n not in (16, 21, 46, 51, 196, 201, 226, 231, 244): rgb = (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) - self.assertEqual(find_nearest_color(*rgb),n) + self.assertEqual(FindNearest(*rgb).all_fast(),n) def test_nearby(self): - self.assertEqual(find_nearest_color(1,2,2),0) - self.assertEqual(find_nearest_color(7,7,9),232) + self.assertEqual(FindNearest(1,2,2).all_fast(),0) + self.assertEqual(FindNearest(7,7,9).all_fast(),232) def test_simplecolor(self): - self.assertEqual(find_nearest_simple_color(1,2,4), 0) - self.assertEqual(find_nearest_simple_color(0,255,0), 2) - self.assertEqual(find_nearest_simple_color(100,100,0), 3) - self.assertEqual(find_nearest_simple_color(140,140,140), 7) + self.assertEqual(FindNearest(1,2,4).very_simple(), 0) + self.assertEqual(FindNearest(0,255,0).very_simple(), 2) + self.assertEqual(FindNearest(100,100,0).very_simple(), 3) + self.assertEqual(FindNearest(140,140,140).very_simple(), 7) class TestColorLoad(unittest.TestCase): @@ -70,6 +70,16 @@ def setUp(self): def test_InvalidAttributes(self): pass +class TestNearestColor(unittest.TestCase): + def test_allcolors(self): + myrange = (0,1,2,5,17,39,48,73,82,140,193,210,240,244,250,254,255) + for r in myrange: + for g in myrange: + for b in myrange: + near = FindNearest(r,g,b) + self.assertEqual(near.all_slow(),near.all_fast(), 'Tested: {0}, {1}, {2}'.format(r,g,b)) + + if __name__ == '__main__': unittest.main() From 45b6a2a46907772e462954ce7bb67287a3f67aa9 Mon Sep 17 00:00:00 2001 From: Henry MuonOne Date: Wed, 22 Jul 2015 11:39:15 -0500 Subject: [PATCH 55/80] Removing a few redundant functions. --- plumbum/color/names.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index 94f50c866..e584cfb97 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -298,6 +298,8 @@ #Functions to be used for color name operations class FindNearest(object): + """This is a class for finding the nearest color given rgb values. + Different find methods are available.""" def __init__(self, r, g, b): self.r = r self.b = b @@ -353,30 +355,13 @@ def all_fast(self): distances = [self._distance_to_color_number(n) for n in colors] return colors[min(range(len(distances)), key=distances.__getitem__)] -def find_nearest_color(r, g, b, color_slice=slice(None,None,None)): - return FindNearest(r, g, b).all_slow(color_slice) - -def find_nearest_simple_color(r, g, b): - return FindNearest(r, g, b).very_simple() - def from_html(color): - """Convert html hex code to rgb""" + """Convert html hex code to rgb.""" if len(color) != 7 or color[0] != '#': raise ValueError("Invalid length of html code") return (int(color[1:3],16), int(color[3:5],16), int(color[5:7],16)) def to_html(r, g, b): + """Convert rgb to html hex code.""" return "#{0:02x}{1:02x}{2:02x}".format(r, g, b) - -def print_html_table(): - """Prints html names for documentation""" - print(r'
          ') - for i in range(256): - name = color_names[i] - val = color_html[i] - print(r'
        1. ' + val - + r' ' + name - + r'
        2. ') - print(r'
        ') From 8aa3e4016120185d4a1be068d9db80c22bd96826 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 11:53:40 -0500 Subject: [PATCH 56/80] Small bugfix --- plumbum/color/names.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plumbum/color/names.py b/plumbum/color/names.py index e584cfb97..cec2084f7 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -315,8 +315,8 @@ def very_simple(self): # Compressed to linear_integers r,g,b # [[[0,1],[2,3]],[[4,5],[6,7]]] # r*1 + g*2 + b*4 - return (r>=midlevel)*1 + (g>=midlevel)*2 + (b>=midlevel)*4 - + return (self.r>=midlevel)*1 + (self.g>=midlevel)*2 + (self.b>=midlevel)*4 + def all_slow(self, color_slice=slice(None, None, None)): """This is a slow way to find the nearest color.""" distances = [self._distance_to_color(color) for color in color_html[color_slice]] From e4ee148f0aa1625e6e1006df1b583ba41f70a17c Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 12:23:13 -0500 Subject: [PATCH 57/80] Changed "simple" to "representation", preparing to add conversion feature. --- plumbum/color/styles.py | 44 +++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index aad6962b6..2736dd1e2 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -89,9 +89,15 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): self.fg = fg self.reset = True # Starts as reset color self.rgb = (0,0,0) - self.simple = False - self.exact = True # Set to False if interpolation done + self.number = None + 'Number of the original color, or closest color' + + self.representation = None + '0 for 8 colors, 1 for 16 colors, 2 for 256 colors, 3 for true color' + + self.exact = True + 'This is false if the named color does not match the real color' if r_or_color is not None and None in (g,b): try: @@ -105,17 +111,17 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): self._init_number() def _init_number(self): - """Should always be called after filling in r, g, b. Color will not be a reset color anymore.""" - if self.number is not None: - self.exact = True + """Should always be called after filling in r, g, b, and representation. + Color will not be a reset color anymore.""" + + if self.representation == 0: + self.number = FindNearest(*self.rgb).very_simple() + elif self.representation == 1: + self.number = FindNearest(*self.rgb).only_simple() else: - if self.simple: - self.number = FindNearest(*self.rgb).only_simple() - self.exact = self.rgb == from_html(color_html[self.number]) - else: - self.number = FindNearest(*self.rgb).all_fast() - self.exact = self.rgb == from_html(color_html[self.number]) + self.number = FindNearest(*self.rgb).all_fast() + self.exact = self.rgb == from_html(color_html[self.number]) self.reset = False @classmethod @@ -137,7 +143,6 @@ def _from_simple(self, color): elif color in color_names[:16]: self.number = color_names.index(color) self.rgb = from_html(color_html[self.number]) - self.simple = True elif isinstance(color, int) and 0 <= color < 16: self.number = color @@ -147,6 +152,7 @@ def _from_simple(self, color): else: raise ColorNotFound("Did not find color: " + repr(color)) + self.representation = 1 self._init_number() @classmethod @@ -178,6 +184,7 @@ def _from_full(self, color): else: raise ColorNotFound("Did not find color: " + repr(color)) + self.representation = 2 self._init_number() @classmethod @@ -194,6 +201,7 @@ def _from_hex(self, color): except (TypeError, ValueError): raise ColorNotFound("Did not find htmlcode: " + repr(color)) + self.representation = 3 self._init_number() @property @@ -211,20 +219,18 @@ def name_camelcase(self): def __repr__(self): """This class has a smart representation that shows name and color (if not unique).""" - name = ' Simple' if self.simple else '' + name = [' Basic: ', '', ' Full: ', ' True: '][self.representation] name += '' if self.fg else ' Background' name += ' ' + self.name_camelcase name += '' if self.exact else ' ' + self.html_hex_code return name[1:] def __eq__(self, other): - """Reset colors are equal, otherwise number, rgb, and simple status have to match.""" + """Reset colors are equal, otherwise rgb have to match.""" if self.reset: return other.reset else: - return (self.number == other.number - and self.rgb == other.rgb - and self.simple == other.simple) + return self.rgb == other.rgb @property def ansi_sequence(self): @@ -238,9 +244,9 @@ def ansi_codes(self): if self.reset: return (ansi_addition+9,) - elif self.simple: + elif self.representation < 2: return (color_codes_simple[self.number]+ansi_addition,) - elif self.exact: + elif self.representation == 2: return (ansi_addition+8, 5, self.number) else: return (ansi_addition+8, 2, self.rgb[0], self.rgb[1], self.rgb[2]) From cf68efc1a77ca12a28d965deee17af1f7aec6250 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 13:20:07 -0500 Subject: [PATCH 58/80] Added representations, standardized call -> simple will be tried first. --- docs/color.rst | 2 ++ plumbum/color/factories.py | 23 ++++++--------- plumbum/color/names.py | 4 +-- plumbum/color/styles.py | 59 +++++++++++++++++++++++++++++++++----- tests/test_color.py | 10 +++---- tests/test_factories.py | 12 ++++++-- 6 files changed, 79 insertions(+), 31 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index da38b682d..37267ce77 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -198,6 +198,8 @@ The supported colors are: .. raw:: html :file: _color_list.html +If you want to enforce a specific represenation, you can use ``.basic`` (8 color), ``.simple`` (16 color), ``.full`` (256 color), or ``.true`` (24 bit color) on a Style, and the colors in that Style will conform to the output representation and name of the best match color. The internal RGB colors +are remembered, so this is a non-destructive operation. The Classes =========== diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index 5bc1f597d..b004403bc 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -34,7 +34,7 @@ def full(self, name): def __getattr__(self, item): """Full color names work, but do not populate __dir__.""" try: - return self._style.from_color(self._style.color_class.from_full(item, fg=self._fg)) + return self._style.from_color(self._style.color_class(item, fg=self._fg)) except ColorNotFound: raise AttributeError(item) @@ -50,32 +50,25 @@ def hex(self, hexcode): """Return the extended color scheme color for a value.""" return self._style.from_color(self._style.color_class.from_hex(hexcode, fg=self._fg)) - def __getitem__(self, val): """\ Shortcut to provide way to access colors numerically or by slice. If end <= 16, will stay to simple ANSI version.""" - try: + if isinstance(val, slice): (start, stop, stride) = val.indices(256) if stop <= 16: return [self.simple(v) for v in range(start, stop, stride)] else: return [self.full(v) for v in range(start, stop, stride)] - except AttributeError: - try: - return self.full(val) - except ColorNotFound: - return self.hex(val) - def __call__(self, val): - """Shortcut to provide way to access colors.""" try: - return self.simple(val) + return self.full(val) except ColorNotFound: - try: - return self.full(val) - except ColorNotFound: - return self.hex(val) + return self.hex(val) + + def __call__(self, val_or_r, g = None, b = None): + """Shortcut to provide way to access colors.""" + return self._style.from_color(self._style.color_class(val_or_r, g, b, fg=self._fg)) def __iter__(self): """Iterates through all colors in extended colorset.""" diff --git a/plumbum/color/names.py b/plumbum/color/names.py index cec2084f7..c9ae30d32 100644 --- a/plumbum/color/names.py +++ b/plumbum/color/names.py @@ -305,8 +305,8 @@ def __init__(self, r, g, b): self.b = b self.g = g - def very_simple(self): - """This will only return simple colors! + def only_basic(self): + """This will only return the first 8 colors! Breaks the colorspace into cubes, returns color""" midlevel = 0x40 # Since bright is not included diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 2736dd1e2..9dac69c30 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -93,7 +93,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): self.number = None 'Number of the original color, or closest color' - self.representation = None + self.representation = 3 '0 for 8 colors, 1 for 16 colors, 2 for 256 colors, 3 for true color' self.exact = True @@ -101,21 +101,28 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): if r_or_color is not None and None in (g,b): try: - self._from_full(r_or_color) + self._from_simple(r_or_color) except ColorNotFound: - self._from_hex(r_or_color) + try: + self._from_full(r_or_color) + except ColorNotFound: + self._from_hex(r_or_color) elif None not in (r_or_color, g, b): self.rgb = (r_or_color,g,b) self._init_number() + elif r_or_color is None and g is None and b is None: + return + else: + raise ColorNotFound("Invalid parameters for a color!") def _init_number(self): """Should always be called after filling in r, g, b, and representation. Color will not be a reset color anymore.""" if self.representation == 0: - self.number = FindNearest(*self.rgb).very_simple() + self.number = FindNearest(*self.rgb).only_basic() elif self.representation == 1: self.number = FindNearest(*self.rgb).only_simple() else: @@ -134,14 +141,16 @@ def from_simple(cls, color, fg=True): def _from_simple(self, color): try: color = color.lower() + color = color.replace(' ','') + color = color.replace('_','') except AttributeError: pass if color == 'reset': return - elif color in color_names[:16]: - self.number = color_names.index(color) + elif color in _lower_camel_names[:16]: + self.number = _lower_camel_names.index(color) self.rgb = from_html(color_html[self.number]) elif isinstance(color, int) and 0 <= color < 16: @@ -219,7 +228,7 @@ def name_camelcase(self): def __repr__(self): """This class has a smart representation that shows name and color (if not unique).""" - name = [' Basic: ', '', ' Full: ', ' True: '][self.representation] + name = [' Basic:', '', ' Full:', ' True:'][self.representation] name += '' if self.fg else ' Background' name += ' ' + self.name_camelcase name += '' if self.exact else ' ' + self.html_hex_code @@ -263,6 +272,14 @@ def __str__(self): """This just prints it's simple name""" return self.name + def to_representation(self, val): + """Converts a color to any represntation""" + other = copy(self) + other.representation = val + other._init_number() + return other + + class Style(object): @@ -583,6 +600,34 @@ def add_ansi(self, sequence): except StopIteration: return + def _to_representation(self, rep): + """This converts both colors to a specific representation""" + other = copy(self) + if other.fg: + other.fg = other.fg.to_representation(rep) + if other.bg: + other.bg = other.bg.to_representation(rep) + return other + + @property + def basic(self): + """The color in the 8 color representation.""" + return self._to_representation(0) + + @property + def simple(self): + """The color in the 16 color representation.""" + return self._to_representation(1) + + @property + def full(self): + """The color in the 256 color representation.""" + return self._to_representation(2) + + @property + def true(self): + """The color in the true color representation.""" + return self._to_representation(3) class ANSIStyle(Style): """This is a subclass for ANSI styles. Use it to get diff --git a/tests/test_color.py b/tests/test_color.py index 58ea26748..06fa7e853 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -18,10 +18,10 @@ def test_nearby(self): self.assertEqual(FindNearest(7,7,9).all_fast(),232) def test_simplecolor(self): - self.assertEqual(FindNearest(1,2,4).very_simple(), 0) - self.assertEqual(FindNearest(0,255,0).very_simple(), 2) - self.assertEqual(FindNearest(100,100,0).very_simple(), 3) - self.assertEqual(FindNearest(140,140,140).very_simple(), 7) + self.assertEqual(FindNearest(1,2,4).only_basic(), 0) + self.assertEqual(FindNearest(0,255,0).only_basic(), 2) + self.assertEqual(FindNearest(100,100,0).only_basic(), 3) + self.assertEqual(FindNearest(140,140,140).only_basic(), 7) class TestColorLoad(unittest.TestCase): @@ -78,7 +78,7 @@ def test_allcolors(self): for b in myrange: near = FindNearest(r,g,b) self.assertEqual(near.all_slow(),near.all_fast(), 'Tested: {0}, {1}, {2}'.format(r,g,b)) - + if __name__ == '__main__': diff --git a/tests/test_factories.py b/tests/test_factories.py index d1c934480..a7cba2b08 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -58,8 +58,6 @@ def testLoadColorByName(self): self.assertRaises(AttributeError, lambda: COLOR.Notacoloratall) - - def testMultiColor(self): sumcolor = COLOR.BOLD + COLOR.BLUE self.assertEqual(COLOR.BOLD.RESET + COLOR.FG.RESET, -sumcolor) @@ -69,6 +67,16 @@ def testSums(self): self.assertEqual(COLOR.RED, COLOR.BLUE + COLOR.RED) self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) + def testRepresentations(self): + color1 = COLOR.full(87) + self.assertEqual(color1, COLOR.DarkSlateGray2) + self.assertEqual(color1.basic, COLOR.DarkSlateGray2) + self.assertEqual(str(color1.basic), str(COLOR.LightGray)) + + color2 = COLOR.rgb(1,45,214) + self.assertEqual(str(color2.full), str(COLOR.Blue3A)) + + def testFromAnsi(self): for color in COLOR[1:7]: self.assertEqual(color, COLOR.from_ansi(str(color))) From dece5a2cc017e0cf48329d55354c6f190371e670 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 14:29:01 -0500 Subject: [PATCH 59/80] Adding a few html styles for blogpost. --- plumbum/color/styles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 9dac69c30..3e6bba647 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -650,7 +650,7 @@ class HTMLStyle(Style): """This was meant to be a demo of subclassing Style, but actually can be a handy way to quicky color html text.""" - attribute_names = dict(bold='b', em='em', li='li', underline='span style="text-decoration: underline;"', code='code', ol='ol start=0') + attribute_names = dict(bold='b', em='em', italics='i', li='li', underline='span style="text-decoration: underline;"', code='code', ol='ol start=0', strikeout='s') end = '
        \n' def __str__(self): From 0ddde06b2183fec54fd19d6f26d9563aa39441d3 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 14:55:07 -0500 Subject: [PATCH 60/80] Updated colortable, fix for loading by number being simplified to a different number. --- docs/_color_list.html | 512 ++++++++++++++++++------------------- docs/color.rst | 18 +- plumbum/color/factories.py | 10 +- plumbum/color/styles.py | 22 +- 4 files changed, 283 insertions(+), 279 deletions(-) diff --git a/docs/_color_list.html b/docs/_color_list.html index 504ee37aa..89cbd0ada 100644 --- a/docs/_color_list.html +++ b/docs/_color_list.html @@ -40,262 +40,262 @@
          -
        1. #000000 Black
        2. -
        3. #800000 Red
        4. -
        5. #008000 Green
        6. -
        7. #808000 Yellow
        8. -
        9. #000080 Blue
        10. -
        11. #800080 Magenta
        12. -
        13. #008080 Cyan
        14. -
        15. #c0c0c0 LightGray
        16. -
        17. #808080 DarkGray
        18. -
        19. #ff0000 LightRed
        20. -
        21. #00ff00 LightGreen
        22. -
        23. #ffff00 LightYellow
        24. -
        25. #0000ff LightBlue
        26. -
        27. #ff00ff LightMagenta
        28. -
        29. #00ffff LightCyan
        30. -
        31. #ffffff White
        32. -
        33. #000000 Grey0
        34. -
        35. #00005f NavyBlue
        36. -
        37. #000087 DarkBlue
        38. -
        39. #0000af Blue3
        40. -
        41. #0000d7 Blue3
        42. -
        43. #0000ff Blue1
        44. -
        45. #005f00 DarkGreen
        46. -
        47. #005f5f DeepSkyBlue4
        48. -
        49. #005f87 DeepSkyBlue4
        50. -
        51. #005faf DeepSkyBlue4
        52. -
        53. #005fd7 DodgerBlue3
        54. -
        55. #005fff DodgerBlue2
        56. -
        57. #008700 Green4
        58. -
        59. #00875f SpringGreen4
        60. -
        61. #008787 Turquoise4
        62. -
        63. #0087af DeepSkyBlue3
        64. -
        65. #0087d7 DeepSkyBlue3
        66. -
        67. #0087ff DodgerBlue1
        68. -
        69. #00af00 Green3
        70. -
        71. #00af5f SpringGreen3
        72. -
        73. #00af87 DarkCyan
        74. -
        75. #00afaf LightSeaGreen
        76. -
        77. #00afd7 DeepSkyBlue2
        78. -
        79. #00afff DeepSkyBlue1
        80. -
        81. #00d700 Green3
        82. -
        83. #00d75f SpringGreen3
        84. -
        85. #00d787 SpringGreen2
        86. -
        87. #00d7af Cyan3
        88. -
        89. #00d7d7 DarkTurquoise
        90. -
        91. #00d7ff Turquoise2
        92. -
        93. #00ff00 Green1
        94. -
        95. #00ff5f SpringGreen2
        96. -
        97. #00ff87 SpringGreen1
        98. -
        99. #00ffaf MediumSpringGreen
        100. -
        101. #00ffd7 Cyan2
        102. -
        103. #00ffff Cyan1
        104. -
        105. #5f0000 DarkRed
        106. -
        107. #5f005f DeepPink4
        108. -
        109. #5f0087 Purple4
        110. -
        111. #5f00af Purple4
        112. -
        113. #5f00d7 Purple3
        114. -
        115. #5f00ff BlueViolet
        116. -
        117. #5f5f00 Orange4
        118. -
        119. #5f5f5f Grey37
        120. -
        121. #5f5f87 MediumPurple4
        122. -
        123. #5f5faf SlateBlue3
        124. -
        125. #5f5fd7 SlateBlue3
        126. -
        127. #5f5fff RoyalBlue1
        128. -
        129. #5f8700 Chartreuse4
        130. -
        131. #5f875f DarkSeaGreen4
        132. -
        133. #5f8787 PaleTurquoise4
        134. -
        135. #5f87af SteelBlue
        136. -
        137. #5f87d7 SteelBlue3
        138. -
        139. #5f87ff CornflowerBlue
        140. -
        141. #5faf00 Chartreuse3
        142. -
        143. #5faf5f DarkSeaGreen4
        144. -
        145. #5faf87 CadetBlue
        146. -
        147. #5fafaf CadetBlue
        148. -
        149. #5fafd7 SkyBlue3
        150. -
        151. #5fafff SteelBlue1
        152. -
        153. #5fd700 Chartreuse3
        154. -
        155. #5fd75f PaleGreen3
        156. -
        157. #5fd787 SeaGreen3
        158. -
        159. #5fd7af Aquamarine3
        160. -
        161. #5fd7d7 MediumTurquoise
        162. -
        163. #5fd7ff SteelBlue1
        164. -
        165. #5fff00 Chartreuse2
        166. -
        167. #5fff5f SeaGreen2
        168. -
        169. #5fff87 SeaGreen1
        170. -
        171. #5fffaf SeaGreen1
        172. -
        173. #5fffd7 Aquamarine1
        174. -
        175. #5fffff DarkSlateGray2
        176. -
        177. #870000 DarkRed
        178. -
        179. #87005f DeepPink4
        180. -
        181. #870087 DarkMagenta
        182. -
        183. #8700af DarkMagenta
        184. -
        185. #8700d7 DarkViolet
        186. -
        187. #8700ff Purple
        188. -
        189. #875f00 Orange4
        190. -
        191. #875f5f LightPink4
        192. -
        193. #875f87 Plum4
        194. -
        195. #875faf MediumPurple3
        196. -
        197. #875fd7 MediumPurple3
        198. -
        199. #875fff SlateBlue1
        200. -
        201. #878700 Yellow4
        202. -
        203. #87875f Wheat4
        204. -
        205. #878787 Grey53
        206. -
        207. #8787af LightSlateGrey
        208. -
        209. #8787d7 MediumPurple
        210. -
        211. #8787ff LightSlateBlue
        212. -
        213. #87af00 Yellow4
        214. -
        215. #87af5f DarkOliveGreen3
        216. -
        217. #87af87 DarkSeaGreen
        218. -
        219. #87afaf LightSkyBlue3
        220. -
        221. #87afd7 LightSkyBlue3
        222. -
        223. #87afff SkyBlue2
        224. -
        225. #87d700 Chartreuse2
        226. -
        227. #87d75f DarkOliveGreen3
        228. -
        229. #87d787 PaleGreen3
        230. -
        231. #87d7af DarkSeaGreen3
        232. -
        233. #87d7d7 DarkSlateGray3
        234. -
        235. #87d7ff SkyBlue1
        236. -
        237. #87ff00 Chartreuse1
        238. -
        239. #87ff5f LightGreen
        240. -
        241. #87ff87 LightGreen
        242. -
        243. #87ffaf PaleGreen1
        244. -
        245. #87ffd7 Aquamarine1
        246. -
        247. #87ffff DarkSlateGray1
        248. -
        249. #af0000 Red3
        250. -
        251. #af005f DeepPink4
        252. -
        253. #af0087 MediumVioletRed
        254. -
        255. #af00af Magenta3
        256. -
        257. #af00d7 DarkViolet
        258. -
        259. #af00ff Purple
        260. -
        261. #af5f00 DarkOrange3
        262. -
        263. #af5f5f IndianRed
        264. -
        265. #af5f87 HotPink3
        266. -
        267. #af5faf MediumOrchid3
        268. -
        269. #af5fd7 MediumOrchid
        270. -
        271. #af5fff MediumPurple2
        272. -
        273. #af8700 DarkGoldenrod
        274. -
        275. #af875f LightSalmon3
        276. -
        277. #af8787 RosyBrown
        278. -
        279. #af87af Grey63
        280. -
        281. #af87d7 MediumPurple2
        282. -
        283. #af87ff MediumPurple1
        284. -
        285. #afaf00 Gold3
        286. -
        287. #afaf5f DarkKhaki
        288. -
        289. #afaf87 NavajoWhite3
        290. -
        291. #afafaf Grey69
        292. -
        293. #afafd7 LightSteelBlue3
        294. -
        295. #afafff LightSteelBlue
        296. -
        297. #afd700 Yellow3
        298. -
        299. #afd75f DarkOliveGreen3
        300. -
        301. #afd787 DarkSeaGreen3
        302. -
        303. #afd7af DarkSeaGreen2
        304. -
        305. #afd7d7 LightCyan3
        306. -
        307. #afd7ff LightSkyBlue1
        308. -
        309. #afff00 GreenYellow
        310. -
        311. #afff5f DarkOliveGreen2
        312. -
        313. #afff87 PaleGreen1
        314. -
        315. #afffaf DarkSeaGreen2
        316. -
        317. #afffd7 DarkSeaGreen1
        318. -
        319. #afffff PaleTurquoise1
        320. -
        321. #d70000 Red3
        322. -
        323. #d7005f DeepPink3
        324. -
        325. #d70087 DeepPink3
        326. -
        327. #d700af Magenta3
        328. -
        329. #d700d7 Magenta3
        330. -
        331. #d700ff Magenta2
        332. -
        333. #d75f00 DarkOrange3
        334. -
        335. #d75f5f IndianRed
        336. -
        337. #d75f87 HotPink3
        338. -
        339. #d75faf HotPink2
        340. -
        341. #d75fd7 Orchid
        342. -
        343. #d75fff MediumOrchid1
        344. -
        345. #d78700 Orange3
        346. -
        347. #d7875f LightSalmon3
        348. -
        349. #d78787 LightPink3
        350. -
        351. #d787af Pink3
        352. -
        353. #d787d7 Plum3
        354. -
        355. #d787ff Violet
        356. -
        357. #d7af00 Gold3
        358. -
        359. #d7af5f LightGoldenrod3
        360. -
        361. #d7af87 Tan
        362. -
        363. #d7afaf MistyRose3
        364. -
        365. #d7afd7 Thistle3
        366. -
        367. #d7afff Plum2
        368. -
        369. #d7d700 Yellow3
        370. -
        371. #d7d75f Khaki3
        372. -
        373. #d7d787 LightGoldenrod2
        374. -
        375. #d7d7af LightYellow3
        376. -
        377. #d7d7d7 Grey84
        378. -
        379. #d7d7ff LightSteelBlue1
        380. -
        381. #d7ff00 Yellow2
        382. -
        383. #d7ff5f DarkOliveGreen1
        384. -
        385. #d7ff87 DarkOliveGreen1
        386. -
        387. #d7ffaf DarkSeaGreen1
        388. -
        389. #d7ffd7 Honeydew2
        390. -
        391. #d7ffff LightCyan1
        392. -
        393. #ff0000 Red1
        394. -
        395. #ff005f DeepPink2
        396. -
        397. #ff0087 DeepPink1
        398. -
        399. #ff00af DeepPink1
        400. -
        401. #ff00d7 Magenta2
        402. -
        403. #ff00ff Magenta1
        404. -
        405. #ff5f00 OrangeRed1
        406. -
        407. #ff5f5f IndianRed1
        408. -
        409. #ff5f87 IndianRed1
        410. -
        411. #ff5faf HotPink
        412. -
        413. #ff5fd7 HotPink
        414. -
        415. #ff5fff MediumOrchid1
        416. -
        417. #ff8700 DarkOrange
        418. -
        419. #ff875f Salmon1
        420. -
        421. #ff8787 LightCoral
        422. -
        423. #ff87af PaleVioletRed1
        424. -
        425. #ff87d7 Orchid2
        426. -
        427. #ff87ff Orchid1
        428. -
        429. #ffaf00 Orange1
        430. -
        431. #ffaf5f SandyBrown
        432. -
        433. #ffaf87 LightSalmon1
        434. -
        435. #ffafaf LightPink1
        436. -
        437. #ffafd7 Pink1
        438. -
        439. #ffafff Plum1
        440. -
        441. #ffd700 Gold1
        442. -
        443. #ffd75f LightGoldenrod2
        444. -
        445. #ffd787 LightGoldenrod2
        446. -
        447. #ffd7af NavajoWhite1
        448. -
        449. #ffd7d7 MistyRose1
        450. -
        451. #ffd7ff Thistle1
        452. -
        453. #ffff00 Yellow1
        454. -
        455. #ffff5f LightGoldenrod1
        456. -
        457. #ffff87 Khaki1
        458. -
        459. #ffffaf Wheat1
        460. -
        461. #ffffd7 Cornsilk1
        462. -
        463. #ffffff Grey100
        464. -
        465. #080808 Grey3
        466. -
        467. #121212 Grey7
        468. -
        469. #1c1c1c Grey11
        470. -
        471. #262626 Grey15
        472. -
        473. #303030 Grey19
        474. -
        475. #3a3a3a Grey23
        476. -
        477. #444444 Grey27
        478. -
        479. #4e4e4e Grey30
        480. -
        481. #585858 Grey35
        482. -
        483. #626262 Grey39
        484. -
        485. #6c6c6c Grey42
        486. -
        487. #767676 Grey46
        488. -
        489. #808080 Grey50
        490. -
        491. #8a8a8a Grey54
        492. -
        493. #949494 Grey58
        494. -
        495. #9e9e9e Grey62
        496. -
        497. #a8a8a8 Grey66
        498. -
        499. #b2b2b2 Grey70
        500. -
        501. #bcbcbc Grey74
        502. -
        503. #c6c6c6 Grey78
        504. -
        505. #d0d0d0 Grey82
        506. -
        507. #dadada Grey85
        508. -
        509. #e4e4e4 Grey89
        510. -
        511. #eeeeee Grey93
        512. +
        513. #000000 Black

        514. +
        515. #C00000 Red

        516. +
        517. #00C000 Green

        518. +
        519. #C0C000 Yellow

        520. +
        521. #0000C0 Blue

        522. +
        523. #C000C0 Magenta

        524. +
        525. #00C0C0 Cyan

        526. +
        527. #C0C0C0 LightGray

        528. +
        529. #808080 DarkGray

        530. +
        531. #FF0000 LightRed

        532. +
        533. #00FF00 LightGreen

        534. +
        535. #FFFF00 LightYellow

        536. +
        537. #0000FF LightBlue

        538. +
        539. #FF00FF LightMagenta

        540. +
        541. #00FFFF LightCyan

        542. +
        543. #FFFFFF White

        544. +
        545. #000000 Grey0

        546. +
        547. #00005F NavyBlue

        548. +
        549. #000087 DarkBlue

        550. +
        551. #0000AF Blue3

        552. +
        553. #0000D7 Blue3A

        554. +
        555. #0000FF Blue1

        556. +
        557. #005F00 DarkGreen

        558. +
        559. #005F5F DeepSkyBlue4

        560. +
        561. #005F87 DeepSkyBlue4A

        562. +
        563. #005FAF DeepSkyBlue4B

        564. +
        565. #005FD7 DodgerBlue3

        566. +
        567. #005FFF DodgerBlue2

        568. +
        569. #008700 Green4

        570. +
        571. #00875F SpringGreen4

        572. +
        573. #008787 Turquoise4

        574. +
        575. #0087AF DeepSkyBlue3

        576. +
        577. #0087D7 DeepSkyBlue3A

        578. +
        579. #0087FF DodgerBlue1

        580. +
        581. #00AF00 Green3

        582. +
        583. #00AF5F SpringGreen3

        584. +
        585. #00AF87 DarkCyan

        586. +
        587. #00AFAF LightSeaGreen

        588. +
        589. #00AFD7 DeepSkyBlue2

        590. +
        591. #00AFFF DeepSkyBlue1

        592. +
        593. #00D700 Green3A

        594. +
        595. #00D75F SpringGreen3A

        596. +
        597. #00D787 SpringGreen2

        598. +
        599. #00D7AF Cyan3

        600. +
        601. #00D7D7 DarkTurquoise

        602. +
        603. #00D7FF Turquoise2

        604. +
        605. #00FF00 Green1

        606. +
        607. #00FF5F SpringGreen2A

        608. +
        609. #00FF87 SpringGreen1

        610. +
        611. #00FFAF MediumSpringGreen

        612. +
        613. #00FFD7 Cyan2

        614. +
        615. #00FFFF Cyan1

        616. +
        617. #5F0000 DarkRed

        618. +
        619. #5F005F DeepPink4

        620. +
        621. #5F0087 Purple4

        622. +
        623. #5F00AF Purple4A

        624. +
        625. #5F00D7 Purple3

        626. +
        627. #5F00FF BlueViolet

        628. +
        629. #5F5F00 Orange4

        630. +
        631. #5F5F5F Grey37

        632. +
        633. #5F5F87 MediumPurple4

        634. +
        635. #5F5FAF SlateBlue3

        636. +
        637. #5F5FD7 SlateBlue3A

        638. +
        639. #5F5FFF RoyalBlue1

        640. +
        641. #5F8700 Chartreuse4

        642. +
        643. #5F875F DarkSeaGreen4

        644. +
        645. #5F8787 PaleTurquoise4

        646. +
        647. #5F87AF SteelBlue

        648. +
        649. #5F87D7 SteelBlue3

        650. +
        651. #5F87FF CornflowerBlue

        652. +
        653. #5FAF00 Chartreuse3

        654. +
        655. #5FAF5F DarkSeaGreen4A

        656. +
        657. #5FAF87 CadetBlue

        658. +
        659. #5FAFAF CadetBlueA

        660. +
        661. #5FAFD7 SkyBlue3

        662. +
        663. #5FAFFF SteelBlue1

        664. +
        665. #5FD700 Chartreuse3A

        666. +
        667. #5FD75F PaleGreen3

        668. +
        669. #5FD787 SeaGreen3

        670. +
        671. #5FD7AF Aquamarine3

        672. +
        673. #5FD7D7 MediumTurquoise

        674. +
        675. #5FD7FF SteelBlue1A

        676. +
        677. #5FFF00 Chartreuse2A

        678. +
        679. #5FFF5F SeaGreen2

        680. +
        681. #5FFF87 SeaGreen1

        682. +
        683. #5FFFAF SeaGreen1A

        684. +
        685. #5FFFD7 Aquamarine1

        686. +
        687. #5FFFFF DarkSlateGray2

        688. +
        689. #870000 DarkRedA

        690. +
        691. #87005F DeepPink4A

        692. +
        693. #870087 DarkMagenta

        694. +
        695. #8700AF DarkMagentaA

        696. +
        697. #8700D7 DarkViolet

        698. +
        699. #8700FF Purple

        700. +
        701. #875F00 Orange4A

        702. +
        703. #875F5F LightPink4

        704. +
        705. #875F87 Plum4

        706. +
        707. #875FAF MediumPurple3

        708. +
        709. #875FD7 MediumPurple3A

        710. +
        711. #875FFF SlateBlue1

        712. +
        713. #878700 Yellow4

        714. +
        715. #87875F Wheat4

        716. +
        717. #878787 Grey53

        718. +
        719. #8787AF LightSlateGrey

        720. +
        721. #8787D7 MediumPurple

        722. +
        723. #8787FF LightSlateBlue

        724. +
        725. #87AF00 Yellow4A

        726. +
        727. #87AF5F DarkOliveGreen3

        728. +
        729. #87AF87 DarkSeaGreen

        730. +
        731. #87AFAF LightSkyBlue3

        732. +
        733. #87AFD7 LightSkyBlue3A

        734. +
        735. #87AFFF SkyBlue2

        736. +
        737. #87D700 Chartreuse2

        738. +
        739. #87D75F DarkOliveGreen3A

        740. +
        741. #87D787 PaleGreen3A

        742. +
        743. #87D7AF DarkSeaGreen3

        744. +
        745. #87D7D7 DarkSlateGray3

        746. +
        747. #87D7FF SkyBlue1

        748. +
        749. #87FF00 Chartreuse1

        750. +
        751. #87FF5F LightGreenA

        752. +
        753. #87FF87 LightGreenB

        754. +
        755. #87FFAF PaleGreen1

        756. +
        757. #87FFD7 Aquamarine1A

        758. +
        759. #87FFFF DarkSlateGray1

        760. +
        761. #AF0000 Red3

        762. +
        763. #AF005F DeepPink4B

        764. +
        765. #AF0087 MediumVioletRed

        766. +
        767. #AF00AF Magenta3

        768. +
        769. #AF00D7 DarkVioletA

        770. +
        771. #AF00FF PurpleA

        772. +
        773. #AF5F00 DarkOrange3

        774. +
        775. #AF5F5F IndianRed

        776. +
        777. #AF5F87 HotPink3

        778. +
        779. #AF5FAF MediumOrchid3

        780. +
        781. #AF5FD7 MediumOrchid

        782. +
        783. #AF5FFF MediumPurple2

        784. +
        785. #AF8700 DarkGoldenrod

        786. +
        787. #AF875F LightSalmon3

        788. +
        789. #AF8787 RosyBrown

        790. +
        791. #AF87AF Grey63

        792. +
        793. #AF87D7 MediumPurple2A

        794. +
        795. #AF87FF MediumPurple1

        796. +
        797. #AFAF00 Gold3

        798. +
        799. #AFAF5F DarkKhaki

        800. +
        801. #AFAF87 NavajoWhite3

        802. +
        803. #AFAFAF Grey69

        804. +
        805. #AFAFD7 LightSteelBlue3

        806. +
        807. #AFAFFF LightSteelBlue

        808. +
        809. #AFD700 Yellow3

        810. +
        811. #AFD75F DarkOliveGreen3B

        812. +
        813. #AFD787 DarkSeaGreen3A

        814. +
        815. #AFD7AF DarkSeaGreen2

        816. +
        817. #AFD7D7 LightCyan3

        818. +
        819. #AFD7FF LightSkyBlue1

        820. +
        821. #AFFF00 GreenYellow

        822. +
        823. #AFFF5F DarkOliveGreen2

        824. +
        825. #AFFF87 PaleGreen1A

        826. +
        827. #AFFFAF DarkSeaGreen2A

        828. +
        829. #AFFFD7 DarkSeaGreen1

        830. +
        831. #AFFFFF PaleTurquoise1

        832. +
        833. #D70000 Red3A

        834. +
        835. #D7005F DeepPink3

        836. +
        837. #D70087 DeepPink3A

        838. +
        839. #D700AF Magenta3A

        840. +
        841. #D700D7 Magenta3B

        842. +
        843. #D700FF Magenta2

        844. +
        845. #D75F00 DarkOrange3A

        846. +
        847. #D75F5F IndianRedA

        848. +
        849. #D75F87 HotPink3A

        850. +
        851. #D75FAF HotPink2

        852. +
        853. #D75FD7 Orchid

        854. +
        855. #D75FFF MediumOrchid1

        856. +
        857. #D78700 Orange3

        858. +
        859. #D7875F LightSalmon3A

        860. +
        861. #D78787 LightPink3

        862. +
        863. #D787AF Pink3

        864. +
        865. #D787D7 Plum3

        866. +
        867. #D787FF Violet

        868. +
        869. #D7AF00 Gold3A

        870. +
        871. #D7AF5F LightGoldenrod3

        872. +
        873. #D7AF87 Tan

        874. +
        875. #D7AFAF MistyRose3

        876. +
        877. #D7AFD7 Thistle3

        878. +
        879. #D7AFFF Plum2

        880. +
        881. #D7D700 Yellow3A

        882. +
        883. #D7D75F Khaki3

        884. +
        885. #D7D787 LightGoldenrod2

        886. +
        887. #D7D7AF LightYellow3

        888. +
        889. #D7D7D7 Grey84

        890. +
        891. #D7D7FF LightSteelBlue1

        892. +
        893. #D7FF00 Yellow2

        894. +
        895. #D7FF5F DarkOliveGreen1

        896. +
        897. #D7FF87 DarkOliveGreen1A

        898. +
        899. #D7FFAF DarkSeaGreen1A

        900. +
        901. #D7FFD7 Honeydew2

        902. +
        903. #D7FFFF LightCyan1

        904. +
        905. #FF0000 Red1

        906. +
        907. #FF005F DeepPink2

        908. +
        909. #FF0087 DeepPink1

        910. +
        911. #FF00AF DeepPink1A

        912. +
        913. #FF00D7 Magenta2A

        914. +
        915. #FF00FF Magenta1

        916. +
        917. #FF5F00 OrangeRed1

        918. +
        919. #FF5F5F IndianRed1

        920. +
        921. #FF5F87 IndianRed1A

        922. +
        923. #FF5FAF HotPink

        924. +
        925. #FF5FD7 HotPinkA

        926. +
        927. #FF5FFF MediumOrchid1A

        928. +
        929. #FF8700 DarkOrange

        930. +
        931. #FF875F Salmon1

        932. +
        933. #FF8787 LightCoral

        934. +
        935. #FF87AF PaleVioletRed1

        936. +
        937. #FF87D7 Orchid2

        938. +
        939. #FF87FF Orchid1

        940. +
        941. #FFAF00 Orange1

        942. +
        943. #FFAF5F SandyBrown

        944. +
        945. #FFAF87 LightSalmon1

        946. +
        947. #FFAFAF LightPink1

        948. +
        949. #FFAFD7 Pink1

        950. +
        951. #FFAFFF Plum1

        952. +
        953. #FFD700 Gold1

        954. +
        955. #FFD75F LightGoldenrod2A

        956. +
        957. #FFD787 LightGoldenrod2B

        958. +
        959. #FFD7AF NavajoWhite1

        960. +
        961. #FFD7D7 MistyRose1

        962. +
        963. #FFD7FF Thistle1

        964. +
        965. #FFFF00 Yellow1

        966. +
        967. #FFFF5F LightGoldenrod1

        968. +
        969. #FFFF87 Khaki1

        970. +
        971. #FFFFAF Wheat1

        972. +
        973. #FFFFD7 Cornsilk1

        974. +
        975. #FFFFFF Grey100

        976. +
        977. #080808 Grey3

        978. +
        979. #121212 Grey7

        980. +
        981. #1C1C1C Grey11

        982. +
        983. #262626 Grey15

        984. +
        985. #303030 Grey19

        986. +
        987. #3A3A3A Grey23

        988. +
        989. #444444 Grey27

        990. +
        991. #4E4E4E Grey30

        992. +
        993. #585858 Grey35

        994. +
        995. #626262 Grey39

        996. +
        997. #6C6C6C Grey42

        998. +
        999. #767676 Grey46

        1000. +
        1001. #808080 Grey50

        1002. +
        1003. #8A8A8A Grey54

        1004. +
        1005. #949494 Grey58

        1006. +
        1007. #9E9E9E Grey62

        1008. +
        1009. #A8A8A8 Grey66

        1010. +
        1011. #B2B2B2 Grey70

        1012. +
        1013. #BCBCBC Grey74

        1014. +
        1015. #C6C6C6 Grey78

        1016. +
        1017. #D0D0D0 Grey82

        1018. +
        1019. #DADADA Grey85

        1020. +
        1021. #E4E4E4 Grey89

        1022. +
        1023. #EEEEEE Grey93

        diff --git a/docs/color.rst b/docs/color.rst index 37267ce77..337556790 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -229,16 +229,16 @@ Subclassing Style For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: class HTMLStyle(Style): - attribute_names = dict(bold='b', em='em', li='li', code='code') + attribute_names = dict(bold='b', li='li', code='code') end = '
        \n' def __str__(self): result = '' if self.bg and not self.bg.reset: - result += ''.format(self.bg.html_hex_code) + result += ''.format(self.bg.hex_code) if self.fg and not self.fg.reset: - result += ''.format(self.fg.html_hex_code) + result += ''.format(self.fg.hex_code) for attr in sorted(self.attributes): if self.attributes[attr]: result += '<' + self.attribute_names[attr] + '>' @@ -265,13 +265,11 @@ An example of usage:: The above color table can be generated with:: - with open('_color_list.html', 'wt') as f: - with HTMLCOLOR.OL: - for color in HTMLCOLOR: - HTMLCOLOR.LI( - "■" << color, - color.fg.html_hex_code << HTMLCOLOR.CODE, - color.fg.name_camelcase) + for color in HTMLCOLOR: + HTMLCOLOR.LI( + "■" << color, + color.fg.hex_code << HTMLCOLOR.CODE, + color.fg.name_camelcase) .. note:: diff --git a/plumbum/color/factories.py b/plumbum/color/factories.py index b004403bc..8396122cd 100644 --- a/plumbum/color/factories.py +++ b/plumbum/color/factories.py @@ -26,11 +26,6 @@ def __init__(self, fg, style): setattr(self, item.upper(), style.from_color(style.color_class.from_simple(item, fg=fg))) - def full(self, name): - """Gets the style for a color, using standard name procedure: either full - color name, html code, or number.""" - return self._style.from_color(self._style.color_class(name, fg=self._fg)) - def __getattr__(self, item): """Full color names work, but do not populate __dir__.""" try: @@ -38,6 +33,11 @@ def __getattr__(self, item): except ColorNotFound: raise AttributeError(item) + def full(self, name): + """Gets the style for a color, using standard name procedure: either full + color name, html code, or number.""" + return self._style.from_color(self._style.color_class.from_full(name, fg=self._fg)) + def simple(self, name): """Return the extended color scheme color for a value or name.""" return self._style.from_color(self._style.color_class.from_simple(name, fg=self._fg)) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 3e6bba647..77ba756e5 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -122,14 +122,20 @@ def _init_number(self): Color will not be a reset color anymore.""" if self.representation == 0: - self.number = FindNearest(*self.rgb).only_basic() + number = FindNearest(*self.rgb).only_basic() elif self.representation == 1: - self.number = FindNearest(*self.rgb).only_simple() + number = FindNearest(*self.rgb).only_simple() else: - self.number = FindNearest(*self.rgb).all_fast() + number = FindNearest(*self.rgb).all_fast() + + if self.number is None: + self.number = number - self.exact = self.rgb == from_html(color_html[self.number]) self.reset = False + self.exact = self.rgb == from_html(color_html[self.number]) + if not self.exact: + self.number = number + @classmethod def from_simple(cls, color, fg=True): @@ -231,7 +237,7 @@ def __repr__(self): name = [' Basic:', '', ' Full:', ' True:'][self.representation] name += '' if self.fg else ' Background' name += ' ' + self.name_camelcase - name += '' if self.exact else ' ' + self.html_hex_code + name += '' if self.exact else ' ' + self.hex_code return name[1:] def __eq__(self, other): @@ -261,7 +267,7 @@ def ansi_codes(self): return (ansi_addition+8, 2, self.rgb[0], self.rgb[1], self.rgb[2]) @property - def html_hex_code(self): + def hex_code(self): """This is the hex code of the current color, html style notation.""" if self.reset: return '#000000' @@ -661,9 +667,9 @@ def __str__(self): result = '' if self.bg and not self.bg.reset: - result += ''.format(self.bg.html_hex_code) + result += ''.format(self.bg.hex_code) if self.fg and not self.fg.reset: - result += ''.format(self.fg.html_hex_code) + result += ''.format(self.fg.hex_code) for attr in sorted(self.attributes): if self.attributes[attr]: result += '<' + self.attribute_names[attr] + '>' From c5240a0d807e3cad5374f9cdee2a7fa91083764e Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 14:57:15 -0500 Subject: [PATCH 61/80] Fix test for changed behavior from name loading. --- tests/test_color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_color.py b/tests/test_color.py index 06fa7e853..73051c8f3 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -59,7 +59,7 @@ def setUp(self): def test_ansi(self): self.assertEqual(str(ANSIStyle(fgcolor=Color('reset'))), '\033[39m') - self.assertEqual(str(ANSIStyle(fgcolor=Color('green'))), '\033[38;5;2m') + self.assertEqual(str(ANSIStyle(fgcolor=Color.from_full('green'))), '\033[38;5;2m') self.assertEqual(str(ANSIStyle(fgcolor=Color.from_simple('red'))), '\033[31m') From ce0d033f695d955f00ae507f06da080cde99ccc1 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 15:08:43 -0500 Subject: [PATCH 62/80] Converting color needs to have color number reset. --- plumbum/color/styles.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 77ba756e5..7ad9c242a 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -282,6 +282,7 @@ def to_representation(self, val): """Converts a color to any represntation""" other = copy(self) other.representation = val + other.number = None other._init_number() return other From 6698dc7d0013addaf7a33b3a601f06b2618de379 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 22 Jul 2015 15:15:09 -0500 Subject: [PATCH 63/80] Correction to not-equal test. --- tests/test_color.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_color.py b/tests/test_color.py index 73051c8f3..76d153566 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -49,8 +49,8 @@ def test_different_names(self): def test_loading_methods(self): self.assertEqual(Color("Yellow"), Color.from_full("Yellow")) - self.assertNotEqual(Color.from_full("yellow"), - Color.from_simple("yellow")) + self.assertNotEqual(Color.from_full("yellow").representation, + Color.from_simple("yellow").representation) class TestANSIColor(unittest.TestCase): From 2be9eb771d0781395a339eb68c32a80fd7352bcd Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Thu, 23 Jul 2015 07:40:04 -0500 Subject: [PATCH 64/80] Adding & support --- plumbum/color/styles.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plumbum/color/styles.py b/plumbum/color/styles.py index 7ad9c242a..772fb1de3 100644 --- a/plumbum/color/styles.py +++ b/plumbum/color/styles.py @@ -432,6 +432,13 @@ def __mul__(self, other): __rlshift__ = wrap """This class supports ``"String:" << color`` syntax""" + __rand__ = wrap + """Support for "String" & color syntax""" + + __and__ = __mul__ + """This class supports ``color & color2`` syntax. It also supports + ``"color & "String"`` syntax too. """ + __lshift__ = __mul__ """This class supports ``color << color2`` syntax. It also supports ``"color << "String"`` syntax too. """ From 0dca53b2b86ae3bea7f52cfa342682d9290d834d Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Thu, 23 Jul 2015 09:20:37 -0500 Subject: [PATCH 65/80] Added default colors to plumbum.color. --- plumbum/color/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py index 201fb8d20..48c396656 100644 --- a/plumbum/color/__init__.py +++ b/plumbum/color/__init__.py @@ -10,6 +10,17 @@ COLOR = StyleFactory(ANSIStyle) HTMLCOLOR = StyleFactory(HTMLStyle) + +#=================================================================================================== +# Module hack: ``from plumbum.color import red`` +#=================================================================================================== + +for _color in COLOR[:16]: + globals()[_color.fg.name] = _color +for _style in COLOR._style.attribute_names: + globals()[_style] = getattr(COLOR, _style.upper()) +globals()['reset'] = COLOR.RESET + def load_ipython_extension(ipython): try: from plumbum.color._ipython_ext import OutputMagics From 858371c3c846d79f7bec7c680ef01a2544893284 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 09:12:07 -0500 Subject: [PATCH 66/80] Change COLOR -> colors, now an imitation module, all lowercase names --- CHANGELOG.rst | 2 +- README.rst | 12 +- docs/_cheatsheet.rst | 12 +- docs/cli.rst | 2 +- docs/color.rst | 117 +++++------ examples/color.py | 26 +-- examples/fullcolor.py | 6 +- examples/geet.py | 8 +- plumbum/__init__.py | 1 - plumbum/cli/application.py | 37 ++-- plumbum/color/__init__.py | 33 ---- plumbum/color/__main__.py | 12 -- plumbum/colorlib/__init__.py | 22 +++ plumbum/colorlib/__main__.py | 15 ++ plumbum/{color => colorlib}/_ipython_ext.py | 0 plumbum/{color => colorlib}/factories.py | 22 +-- plumbum/{color => colorlib}/names.py | 0 plumbum/{color => colorlib}/styles.py | 54 ++--- plumbum/colors.py | 26 +++ tests/test_color.py | 4 +- tests/test_factories.py | 209 ++++++++++---------- tests/test_visual_color.py | 24 +-- 22 files changed, 333 insertions(+), 311 deletions(-) delete mode 100644 plumbum/color/__init__.py delete mode 100644 plumbum/color/__main__.py create mode 100644 plumbum/colorlib/__init__.py create mode 100644 plumbum/colorlib/__main__.py rename plumbum/{color => colorlib}/_ipython_ext.py (100%) rename plumbum/{color => colorlib}/factories.py (88%) rename plumbum/{color => colorlib}/names.py (100%) rename plumbum/{color => colorlib}/styles.py (95%) create mode 100644 plumbum/colors.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5a2565544..807d6dba8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,6 @@ In development -------------- -* Color: Added color module, support for color added to cli +* Color: Added colors module, support for colors added to cli * Machines: Added ``.get()`` method for checking several commands. 1.5.0 diff --git a/README.rst b/README.rst index c4fc3829f..0b7d6f7aa 100644 --- a/README.rst +++ b/README.rst @@ -131,13 +131,13 @@ Sample output:: **Color controls** :: - from plumbum import COLOR - with COLOR.RED: + from plumbum import colors + with colors.red: print("This library provides safe, flexible color access.") - print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 16 colors or", '256 named colors,' << COLOR.ORCHID + COLOR.UNDERLINE, - "or full hex colors" << COLOR["#129240"], 'can be used.') - print("Unsafe " + COLOR.BG.DARK_KHAKI + "color access" - COLOR.BG + " is available too.") + print("Color", "(and styles in general)" << colors.bold, "are easy!") + print("The simple 16 colors or", '256 named colors,' << colors.orchid + colors.underline, + "or full hex colors" << colors["#129240"], 'can be used.') + print("Unsafe " + colors.bg.dark_khaki + "color access" - colors.bg + " is available too.") diff --git a/docs/_cheatsheet.rst b/docs/_cheatsheet.rst index d1e361f1c..c4f1f5091 100644 --- a/docs/_cheatsheet.rst +++ b/docs/_cheatsheet.rst @@ -108,13 +108,13 @@ Sample output:: **Color controls** :: - from plumbum import COLOR - with COLOR.RED: + from plumbum import colors + with colors.red: print("This library provides safe, flexible color access.") - print("Color", "(and styles in general)" << COLOR.BOLD, "are easy!") - print("The simple 16 colors or", '256 named colors,' << COLOR.ORCHID + COLOR.UNDERLINE, - "or full hex colors" << COLOR["#129240"], 'can be used.') - print("Unsafe " + COLOR.BG.DARK_KHAKI + "color access" - COLOR.BG + " is available too.") + print("Color", "(and styles in general)" << colors.bold, "are easy!") + print("The simple 16 colors or", '256 named colors,' << colors.orchid + color.underline, + "or full hex colors" << colors["#129240"], 'can be used.') + print("Unsafe " + colors.bg.dark_khaki + "color access" - colors.bg + " is available too.") Sample output: diff --git a/docs/cli.rst b/docs/cli.rst index 745735ec8..3dd13bfe1 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -84,7 +84,7 @@ Colors are supported through the class level attributes ``COLOR_GROUPS[]``, and ``COLOR_GROUPS_BODY[]``, which should contain Style objects. The dictionaries support custom colors -for named groups. The default is COLOR.DO_NOTHING, but if you just want more +for named groups. The default is ``colors.do_nothing``, but if you just want more colorful defaults, subclass ``cli.ColorfulApplication``. .. versionadded:: 1.5 diff --git a/docs/color.rst b/docs/color.rst index 337556790..5ac1ff937 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -6,7 +6,7 @@ Color tools .. versionadded:: 1.6.0 -The purpose of the `plumbum.color` library is to make adding +The purpose of the `plumbum.colors` library is to make adding text styles (such as color) to Python easy and safe. Color is often a great addition to shell scripts, but not a necessity, and implementing it properly is tricky. It is easy to end up with an unreadable color stuck on your terminal or @@ -18,62 +18,65 @@ API for creating other color schemes for other systems using escapes. ``ANSIStyle`` assumes that only a terminal on a posix-identity system can display color. You can force the use of color globally by setting - ``COLOR.use_color=True``. + ``colors.use_color=True``. Generating colors ================ -Styles are accessed through the ``COLOR`` object, which is an instance of a StyleFactory. +Styles are accessed through the ``colors`` object, which is an instance of a StyleFactory. The ``colors`` +object is actually an imitation module that wraps ``plumbum.colorlib.ansicolors`` with module-like access. +Thus, things like from ``plumbum.colors.bg import red`` work also. The library actually lives in ``plubmumb.colorlib``. + Style Factory ^^^^^^^^^^^^^ -The ``COLOR`` object has the following available objects: +The ``colors`` object has the following available objects: - ``FG`` and ``BG`` - The foreground and background colors, reset to default with ``COLOR.FG.RESET`` - or ``~COLOR.FG`` and likewise for ``BG``. These are ``ColorFactory`` instances. - ``BOLD``, ``DIM``, ``UNDERLINE``, ``ITALICS``, ``REVERSE``, ``STRIKEOUT``, and ``HIDDEN`` + ``fg`` and ``bg`` + The foreground and background colors, reset to default with ``colors.fg.reset`` + or ``~colors.fg`` and likewise for ``bg``. These are ``ColorFactory`` instances. + ``bold``, ``dim``, ``underline``, ``italics``, ``reverse``, ``strikeout``, and ``hidden`` All the `ANSI` modifiers are available, as well as their negations, such - as ``~COLOR.BOLD`` or ``COLOR.BOLD.RESET``, etc. (These are generated automatically + as ``~colors.bold`` or ``colors.bold.reset``, etc. (These are generated automatically based on the Style attached to the factory.) - ``RESET`` + ``reset`` The global reset will restore all properties at once. - ``DO_NOTHING`` + ``do_nothing`` Does nothing at all, but otherwise acts like any ``Style`` object. It is its own inverse. Useful for ``cli`` properties. -The ``COLOR`` object can be used in a with statement, which resets all styles on leaving +The ``colors`` object can be used in a with statement, which resets all styles on leaving the statement body. Although factories do support -some of the same methods as a Style, their primary purpose is to generate Styles. The COLOR object has a +some of the same methods as a Style, their primary purpose is to generate Styles. The colors object has a ``use_color`` property that can be set to force the use of color. A ``stdout`` property is provided -to make changing the output of color statement easier. A ``COLOR.from_ansi(code)`` method allows +to make changing the output of color statement easier. A ``colors.from_ansi(code)`` method allows you to create a Style from any ansi sequence, even complex or combined ones. Color Factories ^^^^^^^^^^^^^^^ -The ``COLOR.FG`` and ``COLOR.BG`` are ``ColorFactory``'s. In fact, the COLOR object itself acts exactly -like the ``COLOR.FG`` object, with the exception of the properties listed above. +The ``colors.fg`` and ``colors.bg`` are ``ColorFactory``'s. In fact, the colors object itself acts exactly +like the ``colors.fg`` object, with the exception of the properties listed above. Named foreground colors are available -directly as methods. The first 16 primary colors, ``BLACK``, ``RED``, ``GREEN``, ``YELLOW``, -``BLUE``, ``MAGENTA``, ``CYAN``, etc, as well as ``RESET``, are available. All 256 color +directly as methods. The first 16 primary colors, ``black``, ``red``, ``green``, ``yellow``, +``blue``, ``magenta``, ``cyan``, etc, as well as ``reset``, are available. All 256 color names are available, but do not populate factory directly, so that auto-completion -gives reasonable results. You can also access colors using strings and do ``COLOR[string]``. +gives reasonable results. You can also access colors using strings and do ``colors[string]``. Capitalization, underscores, and spaces (for strings) will be ignored. -You can also access colors numerically with ``COLOR(n)`` or ``COLOR[n]`` +You can also access colors numerically with ``colors(n)`` or ``colors[n]`` with the extended 256 color codes. The former will default to simple versions of colors for the first 16 values. The later notation can also be used to slice. Full hex codes can be used, too. If no match is found, these will be the true 24 bit color value. -The ``FG`` and ``BG`` also can be put in with statements, and they +The ``fg`` and ``bg`` also can be put in with statements, and they will restore the foreground and background color only, respectively. -``COLOR.rgb(r,g,b)`` will create a color from an -input red, green, and blue values (integers from 0-255). ``COLOR.hex(code)`` will allow -you to input an html style hex sequence. These work on ``FG`` and ``BG`` too. The ``repr`` of +``colors.rgb(r,g,b)`` will create a color from an +input red, green, and blue values (integers from 0-255). ``colors.hex(code)`` will allow +you to input an html style hex sequence. These work on ``fg`` and ``bg`` too. The ``repr`` of styles is smart and will show you the closest color to the one you selected if you didn't exactly select a color through RGB. @@ -97,16 +100,16 @@ arguments (directly calling a style without arguments is also a shortcut for ``. operations do not restore normal color to the terminal by themselves. To protect their use, you should always use a context manager around any unsafe operation. -An example of the usage of unsafe ``COLOR`` manipulations inside a context manager:: +An example of the usage of unsafe ``colors`` manipulations inside a context manager:: - from plumbum import COLOR + from plumbum import colors - with COLOR: - COLOR.FG.RED() + with colors: + colors.fg.red() print('This is in red') - COLOR.GREEN() - print('This is green ' + COLOR.UNDERLINE + 'and now also underlined!') - print('Underlined' - COLOR.UNDERLINE + ' and not underlined but still red') + colors.green() + print('This is green ' + colors.underline + 'and now also underlined!') + print('Underlined' - colors.underline + ' and not underlined but still red') print('This is completly restored, even if an exception is thrown!') Output: @@ -118,18 +121,18 @@ Output: Underlined and not underlined but still green.
        This is completly restored, even if an exception is thrown!

        -We can use ``COLOR`` instead of ``COLOR.FG`` for foreground colors. If we had used ``COLOR.FG`` -as the context manager, then non-foreground properties, such as ``COLOR.UNDERLINE`` or -``COLOR.BG.YELLOW``, would not have reset those properties. Each attribute, -as well as ``FG``, ``BG``, and ``COLOR`` all have inverses in the ANSI standard. They are -accessed with ``~``, ``-``, or ``.RESET``, and can be used to manually make these operations +We can use ``colors`` instead of ``colors.fg`` for foreground colors. If we had used ``colors.fg`` +as the context manager, then non-foreground properties, such as ``colors.underline`` or +``colors.bg.YELLOW``, would not have reset those properties. Each attribute, +as well as ``fg``, ``bg``, and ``colors`` all have inverses in the ANSI standard. They are +accessed with ``~``, ``-``, or ``.reset``, and can be used to manually make these operations safer, but there is a better way. Safe Manipulation ^^^^^^^^^^^^^^^^^ All other operations are safe; they restore the color automatically. The first, and hopefully -already obvious one, is using a Style rather than a ``COLOR`` or ``COLOR.FG`` object in a ``with`` statement. +already obvious one, is using a Style rather than a ``colors`` or ``colors.fg`` object in a ``with`` statement. This will set the color (using sys.stdout by default) to that color, and restore color on leaving. The second method is to manually wrap a string. This can be done with ``color.wrap("string")``, @@ -155,15 +158,15 @@ an alternative, following the PyQT convention for method names that match reserv An example of safe manipulations:: - COLOR.FG.YELLOW('This is yellow', end='') + colors.fg.yellow('This is yellow', end='') print(' And this is normal again.') - with COLOR.RED: + with colors.red: print('Red color!') - with COLOR.BOLD: + with colors.bold: print("This is red and bold.") print("Not bold, but still red.") print("Not red color or bold.") - print("This is bold and colorful!" << (COLOR.MAGENTA + COLOR.BOLD), "And this is not.") + print("This is bold and colorful!" << (colors.magenta + colors.bold), "And this is not.") Output: @@ -180,7 +183,7 @@ Style Combinations ^^^^^^^^^^^^^^^^^^ You can combine styles with ``+``, ``*``, ``<<``, or ``>>``, and they will create a new combined Style object. Colors will not be "summed" or otherwise combined; the rightmost color will be used (this matches the expected effect of -applying the Styles individually to the strings). However, combined Styles are intelligent and know how to reset just the properties that they contain. As you have seen in the example above, the combined style ``(COLOR.MAGENTA + COLOR.BOLD)`` can be used in any way a normal Style can. +applying the Styles individually to the strings). However, combined Styles are intelligent and know how to reset just the properties that they contain. As you have seen in the example above, the combined style ``(colors.magenta + colors.bold)`` can be used in any way a normal Style can. 256 Color Support ================= @@ -189,9 +192,9 @@ While this library supports full 24 bit colors through escape sequences, the library has special support for the "full" 256 colorset through numbers, names or HEX html codes. Even if you use 24 bit color, the closest name is displayed in the ``repr``. You can access the colors as -as ``COLOR.FG.Light_Blue``, ``COLOR.FG.LIGHTBLUE``, ``COLOR.FG[12]``, ``COLOR.FG('Light_Blue')``, -``COLOR.FG('LightBlue')``, or ``COLOR.FG('#0000FF')``. -You can also iterate or slice the ``COLOR``, ``COLOR.FG``, or ``COLOR.BG`` objects. Slicing even +as ``colors.fg.Light_Blue``, ``colors.fg.lightblue``, ``colors.fg[12]``, ``colors.fg('Light_Blue')``, +``colors.fg('LightBlue')``, or ``colors.fg('#0000FF')``. +You can also iterate or slice the ``colors``, ``colors.fg``, or ``colors.bg`` objects. Slicing even intelligently downgrades to the simple version of the codes if it is within the first 16 elements. The supported colors are: @@ -214,19 +217,19 @@ not be interacting with the Color class directly, and you probably will not need representations it can produce are welcome. The ``Style`` class hold two colors and a dictionary of attributes. It is the workhorse of the system and is what is produced -by the ``COLOR`` factory. It holds ``Color`` as ``.color_class``, which can be overridden by subclasses (again, this usually is not needed). +by the ``colors`` factory. It holds ``Color`` as ``.color_class``, which can be overridden by subclasses (again, this usually is not needed). To create a color representation, you need to subclass ``Style`` and give it a working ``__str__`` definition. ``ANSIStyle`` is derived from ``Style`` in this way. The factories, ``ColorFactory`` and ``StyleFactory``, are factory classes that are meant to provide simple access to 1 style Style classes. To use, -you need to initialize an object of ``StyleFactory`` with your intended Style. For example, ``COLOR`` is created by:: +you need to initialize an object of ``StyleFactory`` with your intended Style. For example, ``colors`` is created by:: - COLOR = StyleFactory(ANSIStyle) + colors = StyleFactory(ANSIStyle) Subclassing Style ^^^^^^^^^^^^^^^^^ -For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: +For example, if you wanted to create an HTMLStyle and HTMLcolors, you could do:: class HTMLStyle(Style): attribute_names = dict(bold='b', li='li', code='code') @@ -253,31 +256,31 @@ For example, if you wanted to create an HTMLStyle and HTMLCOLOR, you could do:: return result - HTMLCOLOR = StyleFactory(HTMLStyle) + htmlcolors = StyleFactory(HTMLStyle) -This doesn't support global RESETs, since that's not how HTML works, but otherwise is a working implementation. This is an example of how easy it is to add support for other output formats. +This doesn't support global resets, since that's not how HTML works, but otherwise is a working implementation. This is an example of how easy it is to add support for other output formats. An example of usage:: - >>> "This is colored text" << HTMLCOLOR.BOLD + HTMLCOLOR.RED + >>> "This is colored text" << htmlcolors.bold + htmlcolors.red 'This is colored text' The above color table can be generated with:: - for color in HTMLCOLOR: - HTMLCOLOR.LI( + for color in htmlcolors: + htmlcolors.li( "■" << color, - color.fg.hex_code << HTMLCOLOR.CODE, + color.fg.hex_code << htmlcolors.code, color.fg.name_camelcase) .. note:: ``HTMLStyle`` is implemented in the library, as well, with the - ``HTMLCOLOR`` object available in ``plumbum.color``. It was used + ``htmlcolors`` object available in ``plumbum.colorlib``. It was used to create the colored output in this document, with small changes - because ``COLOR.RESET`` cannot be supported with HTML. + because ``colors.reset`` cannot be supported with HTML. See Also ======== diff --git a/examples/color.py b/examples/color.py index a8fe3d080..43fe4af73 100755 --- a/examples/color.py +++ b/examples/color.py @@ -1,25 +1,25 @@ #!/usr/bin/env python from __future__ import with_statement, print_function -from plumbum import COLOR +from plumbum import colors -with COLOR.FG.RED: +with colors.fg.red: print('This is in red') print('This is completly restored, even if an exception is thrown!') -with COLOR: +with colors: print('It is always a good idea to be in a context manager, to avoid being', - 'left with a colored terminal if there is an exception!') - print(COLOR.BOLD + "This is bold and exciting!" - COLOR.BOLD) - print(COLOR.BG.CYAN + "This is on a cyan background." + COLOR.RESET) - print(COLOR.FG[42] + "If your terminal supports 256 colors, this is colorful!" + COLOR.RESET) + '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 color in COLOR: - print(color + u'\u2588', end='') - COLOR.RESET() + for c in colors: + print(c + u'\u2588', end='') + colors.reset() print() - print('Colors can be reset ' + COLOR.UNDERLINE['Too!']) - for color in COLOR[:16]: - print(color["This is in color!"]) + print('Colors can be reset ' + colors.underline['Too!']) + for c in colors[:16]: + print(c["This is in colors!"]) diff --git a/examples/fullcolor.py b/examples/fullcolor.py index e3ac9b0ad..3d85fc761 100755 --- a/examples/fullcolor.py +++ b/examples/fullcolor.py @@ -1,10 +1,10 @@ #!/usr/bin/env python from __future__ import with_statement, print_function -from plumbum import COLOR +from plumbum import colors -with COLOR: +with colors: print("Do you believe in color, punk? DO YOU?") for i in range(0,255,10): for j in range(0,255,10): - print(u''.join(COLOR.rgb(i,j,k)[u'\u2588'] for k in range(0,255,10))) + print(u''.join(colors.rgb(i,j,k)[u'\u2588'] for k in range(0,255,10))) diff --git a/examples/geet.py b/examples/geet.py index 7213817c1..abcb85687 100755 --- a/examples/geet.py +++ b/examples/geet.py @@ -42,14 +42,14 @@ from plumbum import cli # To force no color support: -# from plumbum.color import Style -# Style.use_color = False +# from plumbum import colors +# colors.use_color = False try: import colorama colorama.init() - from plumbum import COLOR - COLOR.use_color = True + from plumbum import colors + colors.use_color = True except ImportError: pass diff --git a/plumbum/__init__.py b/plumbum/__init__.py index 5f55b158b..129b39d96 100644 --- a/plumbum/__init__.py +++ b/plumbum/__init__.py @@ -39,7 +39,6 @@ from plumbum.path import Path, LocalPath, RemotePath from plumbum.machines import local, BaseRemoteMachine, SshMachine, PuttyMachine from plumbum.version import version -from plumbum.color import COLOR __author__ = "Tomer Filiba (tomerfiliba@gmail.com)" __version__ = version diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index bb07d081c..25037c846 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -8,7 +8,8 @@ from plumbum.cli.switches import (SwitchError, UnknownSwitch, MissingArgument, WrongArgumentType, MissingMandatorySwitch, SwitchCombinationError, PositionalArgumentsError, switch, SubcommandError, Flag, CountOf) -from plumbum.color import COLOR +from plumbum.colors import do_nothing +from plumbum import colors class ShowHelp(SwitchError): @@ -121,12 +122,12 @@ def main(self, src, dst): DESCRIPTION = None VERSION = None USAGE = None - COLOR_PROGNAME = COLOR.DO_NOTHING - COLOR_DISCRIPTION = COLOR.DO_NOTHING - COLOR_VERSION = COLOR.DO_NOTHING - COLOR_HEADING = COLOR.DO_NOTHING - COLOR_USAGE = COLOR.DO_NOTHING - COLOR_SUBCOMMANDS = COLOR.DO_NOTHING + COLOR_PROGNAME = do_nothing + COLOR_DISCRIPTION = do_nothing + COLOR_VERSION = do_nothing + COLOR_HEADING = do_nothing + COLOR_USAGE = do_nothing + COLOR_SUBCOMMANDS = do_nothing COLOR_GROUPS = dict() COLOR_GROUPS_BODY = COLOR_GROUPS CALL_MAIN_IF_NESTED_COMMAND = True @@ -531,11 +532,11 @@ def help(self): # @ReservedAssignment def switchs(by_groups, show_groups): for grp, swinfos in sorted(by_groups.items(), key = lambda item: item[0]): if show_groups: - with (self.COLOR_HEADING + self.COLOR_GROUPS.get(grp, COLOR.DO_NOTHING)): + with (self.COLOR_HEADING + self.COLOR_GROUPS.get(grp, do_nothing)): print("%s:" % grp) # Print in body color unless empty, otherwise group color, otherwise nothing - with self.COLOR_GROUPS_BODY.get(grp, self.COLOR_GROUPS.get(grp, COLOR.DO_NOTHING)): + with self.COLOR_GROUPS_BODY.get(grp, self.COLOR_GROUPS.get(grp, do_nothing)): for si in sorted(swinfos, key = lambda si: si.names): swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names if n in self._switches_by_name and self._switches_by_name[n] == si) @@ -618,14 +619,14 @@ def version(self): class ColorfulApplication(Application): """Application with more colorful defaults for easy color output.""" - COLOR_PROGNAME = COLOR.CYAN + COLOR.BOLD - COLOR_VERSION = COLOR.CYAN - COLOR_DISCRIPTION = COLOR.GREEN - COLOR_HEADING = COLOR.BOLD - COLOR_USAGE = COLOR.RED - COLOR_SUBCOMMANDS = COLOR.YELLOW - COLOR_GROUPS = {'Switches':COLOR.BLUE, - 'Meta-switches':COLOR.MAGENTA, - 'Hidden-switches':COLOR.CYAN} + COLOR_PROGNAME = colors.cyan + colors.bold + COLOR_VERSION = colors.cyan + COLOR_DISCRIPTION = colors.green + COLOR_HEADING = colors.bold + COLOR_USAGE = colors.red + COLOR_SUBCOMMANDS = colors.yellow + COLOR_GROUPS = {'Switches':colors.blue, + 'Meta-switches':colors.magenta, + 'Hidden-switches':colors.cyan} COLOR_GROUPS_BODY = COLOR_GROUPS diff --git a/plumbum/color/__init__.py b/plumbum/color/__init__.py deleted file mode 100644 index 48c396656..000000000 --- a/plumbum/color/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -"""\ -The ``COLOR`` object provides ``BG`` and ``FG`` to access colors, -and attributes like bold and -underlined text. It also provides ``RESET`` to recover the normal font. -""" - -from plumbum.color.factories import StyleFactory -from plumbum.color.styles import Style, ANSIStyle, HTMLStyle - -COLOR = StyleFactory(ANSIStyle) -HTMLCOLOR = StyleFactory(HTMLStyle) - - -#=================================================================================================== -# Module hack: ``from plumbum.color import red`` -#=================================================================================================== - -for _color in COLOR[:16]: - globals()[_color.fg.name] = _color -for _style in COLOR._style.attribute_names: - globals()[_style] = getattr(COLOR, _style.upper()) -globals()['reset'] = COLOR.RESET - -def load_ipython_extension(ipython): - try: - from plumbum.color._ipython_ext import OutputMagics - except ImportError: - print("IPython required for the IPython extension to be loaded.") - raise - - ipython.push({"COLOR":HTMLCOLOR}) - ipython.register_magics(OutputMagics) - diff --git a/plumbum/color/__main__.py b/plumbum/color/__main__.py deleted file mode 100644 index bc9be7940..000000000 --- a/plumbum/color/__main__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -This is provided as a quick way to recover your terminal. Simply run -``python -m plumbum.color`` -to recover terminal color. -""" - - -from plumbum.color import COLOR - -if __name__ == '__main__': - COLOR.use_color=True - COLOR.RESET() diff --git a/plumbum/colorlib/__init__.py b/plumbum/colorlib/__init__.py new file mode 100644 index 000000000..8b85c1108 --- /dev/null +++ b/plumbum/colorlib/__init__.py @@ -0,0 +1,22 @@ +"""\ +The ``ansicolor`` object provides ``bg`` and ``fg`` to access colors, +and attributes like bold and +underlined text. It also provides ``reset`` to recover the normal font. +""" + +from plumbum.colorlib.factories import StyleFactory +from plumbum.colorlib.styles import Style, ANSIStyle, HTMLStyle + +ansicolors = StyleFactory(ANSIStyle) +htmlcolors = StyleFactory(HTMLStyle) + +def load_ipython_extension(ipython): + try: + from plumbum.color._ipython_ext import OutputMagics + except ImportError: + print("IPython required for the IPython extension to be loaded.") + raise + + ipython.push({"colors":htmlcolors}) + ipython.register_magics(OutputMagics) + diff --git a/plumbum/colorlib/__main__.py b/plumbum/colorlib/__main__.py new file mode 100644 index 000000000..bbae13323 --- /dev/null +++ b/plumbum/colorlib/__main__.py @@ -0,0 +1,15 @@ +""" +This is provided as a quick way to recover your terminal. Simply run +``python -m plumbum.colorlib`` +to recover terminal color. +""" + + +from plumbum.colorlib import ansicolor + +def main(): + ansicolor.use_color=True + ansicolor.reset() + +if __name__ == '__main__': + main() diff --git a/plumbum/color/_ipython_ext.py b/plumbum/colorlib/_ipython_ext.py similarity index 100% rename from plumbum/color/_ipython_ext.py rename to plumbum/colorlib/_ipython_ext.py diff --git a/plumbum/color/factories.py b/plumbum/colorlib/factories.py similarity index 88% rename from plumbum/color/factories.py rename to plumbum/colorlib/factories.py index 8396122cd..1082f13e6 100644 --- a/plumbum/color/factories.py +++ b/plumbum/colorlib/factories.py @@ -5,8 +5,8 @@ from __future__ import print_function import sys -from plumbum.color.names import color_names -from plumbum.color.styles import ColorNotFound +from plumbum.colorlib.names import color_names +from plumbum.colorlib.styles import ColorNotFound __all__ = ['ColorFactory', 'StyleFactory'] @@ -19,11 +19,11 @@ class ColorFactory(object): def __init__(self, fg, style): self._fg = fg self._style = style - self.RESET = style.from_color(style.color_class(fg=fg)) + self.reset = style.from_color(style.color_class(fg=fg)) # Adding the color name shortcuts for foreground colors for item in color_names[:16]: - setattr(self, item.upper(), style.from_color(style.color_class.from_simple(item, fg=fg))) + setattr(self, item, style.from_color(style.color_class.from_simple(item, fg=fg))) def __getattr__(self, item): @@ -76,7 +76,7 @@ def __iter__(self): def __neg__(self): """Allows clearing a color""" - return self.RESET + return self.reset __invert__ = __neg__ def __rsub__(self, other): @@ -92,7 +92,7 @@ def __exit__(self, type, value, traceback): due to different definition of RESET for the factories.""" - self.RESET.now() + self.reset.now() return False def __repr__(self): @@ -107,14 +107,14 @@ class StyleFactory(ColorFactory): def __init__(self, style): super(StyleFactory,self).__init__(True, style) - self.FG = ColorFactory(True, style) - self.BG = ColorFactory(False, style) + self.fg = ColorFactory(True, style) + self.bg = ColorFactory(False, style) - self.DO_NOTHING = style() - self.RESET = style(reset=True) + self.do_nothing = style() + self.reset = style(reset=True) for item in style.attribute_names: - setattr(self, item.upper(), style(attributes={item:True})) + setattr(self, item, style(attributes={item:True})) @property def use_color(self): diff --git a/plumbum/color/names.py b/plumbum/colorlib/names.py similarity index 100% rename from plumbum/color/names.py rename to plumbum/colorlib/names.py diff --git a/plumbum/color/styles.py b/plumbum/colorlib/styles.py similarity index 95% rename from plumbum/color/styles.py rename to plumbum/colorlib/styles.py index 772fb1de3..2c1a5ff35 100644 --- a/plumbum/color/styles.py +++ b/plumbum/colorlib/styles.py @@ -12,11 +12,11 @@ import os import re from copy import copy -from plumbum.color.names import color_names, color_html -from plumbum.color.names import color_codes_simple, from_html -from plumbum.color.names import FindNearest, attributes_ansi +from plumbum.colorlib.names import color_names, color_html +from plumbum.colorlib.names import color_codes_simple, from_html +from plumbum.colorlib.names import FindNearest, attributes_ansi -__all__ = ['Color', 'Style', 'ANSIStyle', 'ColorNotFound', 'AttributeNotFound'] +__all__ = ['Color', 'Style', 'ANSIStyle', 'HTMLStyle', 'ColorNotFound', 'AttributeNotFound'] _lower_camel_names = [n.replace('_', '') for n in color_names] @@ -87,7 +87,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): """This works from color values, or tries to load non-simple ones.""" self.fg = fg - self.reset = True # Starts as reset color + self.isreset = True # Starts as reset color self.rgb = (0,0,0) self.number = None @@ -131,7 +131,7 @@ def _init_number(self): if self.number is None: self.number = number - self.reset = False + self.isreset = False self.exact = self.rgb == from_html(color_html[self.number]) if not self.exact: self.number = number @@ -222,7 +222,7 @@ def _from_hex(self, color): @property def name(self): """The (closest) name of the current color""" - if self.reset: + if self.isreset: return 'reset' else: return color_names[self.number] @@ -242,8 +242,8 @@ def __repr__(self): def __eq__(self, other): """Reset colors are equal, otherwise rgb have to match.""" - if self.reset: - return other.reset + if self.isreset: + return other.isreset else: return self.rgb == other.rgb @@ -257,7 +257,7 @@ def ansi_codes(self): """This is the full ANSI code, can be reset, simple, 256, or full color.""" ansi_addition = 30 if self.fg else 40 - if self.reset: + if self.isreset: return (ansi_addition+9,) elif self.representation < 2: return (color_codes_simple[self.number]+ansi_addition,) @@ -269,7 +269,7 @@ def ansi_codes(self): @property def hex_code(self): """This is the hex code of the current color, html style notation.""" - if self.reset: + if self.isreset: return '#000000' else: return '#' + '{0[0]:02X}{0[1]:02X}{0[2]:02X}'.format(self.rgb) @@ -321,7 +321,7 @@ def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): self.attributes = attributes if attributes is not None else dict() self.fg = fgcolor self.bg = bgcolor - self.reset = reset + self.isreset = reset invalid_attributes = set(self.attributes) - set(self.attribute_names) if len(invalid_attributes) > 0: raise AttributeNotFound("Attribute(s) not valid: " + ", ".join(invalid_attributes)) @@ -342,8 +342,8 @@ def invert(self): other = self.__class__() # Opposite of reset is reset - if self.reset: - other.reset = True + if self.isreset: + other.isreset = True return other # Flip all attributes @@ -360,14 +360,14 @@ def invert(self): return other @property - def RESET(self): + def reset(self): """Shortcut to access reset as a property.""" return self.invert() def __copy__(self): """Copy is supported, will make dictionary and colors unique.""" result = self.__class__() - result.reset = self.reset + result.isreset = self.isreset result.fg = copy(self.fg) result.bg = copy(self.bg) result.attributes = copy(self.attributes) @@ -398,7 +398,7 @@ def __add__(self, other): if type(self) == type(other): result = copy(other) - result.reset = self.reset or other.reset + result.isreset = self.isreset or other.isreset for attribute in self.attributes: if attribute not in result.attributes: result.attributes[attribute] = self.attributes[attribute] @@ -498,7 +498,7 @@ def __exit__(self, type, value, traceback): def ansi_codes(self): """Generates the full ANSI code sequence for a Style""" - if self.reset: + if self.isreset: return [0] codes = [] @@ -528,15 +528,15 @@ def __repr__(self): neg_attributes = ', '.join('-'+a for a in self.attributes if not self.attributes[a]) colors = ', '.join(repr(c) for c in [self.fg, self.bg] if c) string = '; '.join(s for s in [attributes, neg_attributes, colors] if s) - if self.reset: + if self.isreset: string = 'reset' return "<{0}: {1}>".format(name, string if string else 'empty') def __eq__(self, other): """Equality is true only if reset, or if attributes, fg, and bg match.""" if type(self) == type(other): - if self.reset: - return other.reset + if self.isreset: + return other.isreset else: return (self.attributes == other.attributes and self.fg == other.fg @@ -588,7 +588,7 @@ def add_ansi(self, sequence): else: raise ColorNotFound("the value 5 or 2 should follow a 38 or 48") elif value==0: - self.reset = True + self.isreset = True elif value in attributes_ansi.values(): for name in attributes_ansi: if value == attributes_ansi[name]: @@ -669,14 +669,14 @@ class HTMLStyle(Style): def __str__(self): - if self.reset: + if self.isreset: raise ResetNotSupported("HTML does not support global resets!") result = '' - if self.bg and not self.bg.reset: + if self.bg and not self.bg.isreset: result += ''.format(self.bg.hex_code) - if self.fg and not self.fg.reset: + if self.fg and not self.fg.isreset: result += ''.format(self.fg.hex_code) for attr in sorted(self.attributes): if self.attributes[attr]: @@ -685,9 +685,9 @@ def __str__(self): for attr in reversed(sorted(self.attributes)): if not self.attributes[attr]: result += '' - if self.fg and self.fg.reset: + if self.fg and self.fg.isreset: result += '' - if self.bg and self.bg.reset: + if self.bg and self.bg.isreset: result += '' return result diff --git a/plumbum/colors.py b/plumbum/colors.py new file mode 100644 index 000000000..2a33287f3 --- /dev/null +++ b/plumbum/colors.py @@ -0,0 +1,26 @@ +""" +This module imitates a real module, providing standard syntax +like from `plumbum.colors` and from `plumbum.colors.bg` to work alongside +all the standard syntax for colors. +""" + +import sys +import os +from types import ModuleType + +from plumbum.colorlib import ANSIStyle, StyleFactory + +class ColorModuleType(ModuleType, StyleFactory): + + def __init__(self, name, style): + ModuleType.__init__(self, name) + StyleFactory.__init__(self, style) + self.__path__ = os.path.abspath(os.path.dirname(__file__)) + +mymodule = ColorModuleType(__name__, ANSIStyle) + +# Oddly, the order here matters for Python2, but not Python3 +sys.modules[__name__ + '.fg'] = mymodule.fg +sys.modules[__name__ + '.bg'] = mymodule.bg +sys.modules[__name__] = mymodule + diff --git a/tests/test_color.py b/tests/test_color.py index 76d153566..d7d9340de 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,7 +1,7 @@ from __future__ import with_statement, print_function import unittest -from plumbum.color.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound -from plumbum.color.names import color_html, FindNearest +from plumbum.colorlib.styles import ANSIStyle, Color, AttributeNotFound, ColorNotFound +from plumbum.colorlib.names import color_html, FindNearest class TestNearestColor(unittest.TestCase): diff --git a/tests/test_factories.py b/tests/test_factories.py index a7cba2b08..1936a8c21 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -1,187 +1,188 @@ #!/usr/bin/env python from __future__ import with_statement, print_function import unittest -from plumbum import COLOR -from plumbum.color.styles import ANSIStyle as Style, ColorNotFound -from plumbum.color import HTMLCOLOR +from plumbum import colors +from plumbum.colorlib.styles import ANSIStyle as Style, ColorNotFound +from plumbum.colorlib import htmlcolors import sys class TestANSIColor(unittest.TestCase): def setUp(self): - COLOR.use_color = True + colors.use_color = True def testColorSlice(self): - vals = COLOR[:8] + vals = colors[:8] self.assertEqual(len(vals),8) - self.assertEqual(vals[1], COLOR.RED) - vals = COLOR[40:50] + self.assertEqual(vals[1], colors.red) + vals = colors[40:50] self.assertEqual(len(vals),10) - self.assertEqual(vals[1], COLOR.full(41)) + self.assertEqual(vals[1], colors.full(41)) def testLoadNumericalColor(self): - self.assertEqual(COLOR.full(2), COLOR[2]) - self.assertEqual(COLOR.simple(2), COLOR(2)) - self.assertEqual(COLOR(54), COLOR[54]) + self.assertEqual(colors.full(2), colors[2]) + self.assertEqual(colors.simple(2), colors(2)) + self.assertEqual(colors(54), colors[54]) def testColorStrings(self): - self.assertEqual('\033[0m', COLOR.RESET) - self.assertEqual('\033[1m', COLOR.BOLD) - self.assertEqual('\033[39m', COLOR.FG.RESET) + self.assertEqual('\033[0m', colors.reset) + self.assertEqual('\033[1m', colors.bold) + self.assertEqual('\033[39m', colors.fg.reset) def testNegateIsReset(self): - self.assertEqual(COLOR.RESET, -COLOR) - self.assertEqual(COLOR.FG.RESET, -COLOR.FG) - self.assertEqual(COLOR.BG.RESET, -COLOR.BG) + self.assertEqual(colors.reset, -colors) + self.assertEqual(colors.fg.reset, -colors.fg) + self.assertEqual(colors.bg.reset, -colors.bg) def testShifts(self): - self.assertEqual("This" << COLOR.RED, "This" >> COLOR.RED) - self.assertEqual("This" << COLOR.RED, "This" << COLOR.RED) + self.assertEqual("This" << colors.red, "This" >> colors.red) + self.assertEqual("This" << colors.red, "This" << colors.red) if sys.version_info >= (2, 7): - self.assertEqual("This" << COLOR.RED, "This" * COLOR.RED) - self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") - self.assertEqual("This" << COLOR.RED, COLOR.RED << "This") - self.assertEqual("This" << COLOR.RED, COLOR.RED * "This") - self.assertEqual(COLOR.RED.wrap("This"), "This" << COLOR.RED) + self.assertEqual("This" << colors.red, "This" * colors.red) + self.assertEqual("This" << colors.red, colors.red << "This") + self.assertEqual("This" << colors.red, colors.red << "This") + self.assertEqual("This" << colors.red, colors.red * "This") + self.assertEqual(colors.red.wrap("This"), "This" << colors.red) def testLoadColorByName(self): - self.assertEqual(COLOR['LightBlue'], COLOR.FG['LightBlue']) - self.assertEqual(COLOR.BG['light_green'], COLOR.BG['LightGreen']) - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR['#00afff']) - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR.hex('#00afff')) + self.assertEqual(colors['LightBlue'], colors.fg['LightBlue']) + self.assertEqual(colors.bg['light_green'], colors.bg['LightGreen']) + self.assertEqual(colors['DeepSkyBlue1'], colors['#00afff']) + self.assertEqual(colors['DeepSkyBlue1'], colors.hex('#00afff')) - self.assertEqual(COLOR['DeepSkyBlue1'], COLOR[39]) - self.assertEqual(COLOR.DeepSkyBlue1, COLOR[39]) - self.assertEqual(COLOR.deepskyblue1, COLOR[39]) - self.assertEqual(COLOR.Deep_Sky_Blue1, COLOR[39]) + self.assertEqual(colors['DeepSkyBlue1'], colors[39]) + self.assertEqual(colors.DeepSkyBlue1, colors[39]) + self.assertEqual(colors.deepskyblue1, colors[39]) + self.assertEqual(colors.Deep_Sky_Blue1, colors[39]) + self.assertEqual(colors.RED, colors.red) - self.assertRaises(AttributeError, lambda: COLOR.Notacoloratall) + self.assertRaises(AttributeError, lambda: colors.Notacolorsatall) def testMultiColor(self): - sumcolor = COLOR.BOLD + COLOR.BLUE - self.assertEqual(COLOR.BOLD.RESET + COLOR.FG.RESET, -sumcolor) + sumcolors = colors.bold + colors.blue + self.assertEqual(colors.bold.reset + colors.fg.reset, -sumcolors) def testSums(self): # Sums should not be communitave, last one is used - self.assertEqual(COLOR.RED, COLOR.BLUE + COLOR.RED) - self.assertEqual(COLOR.BG.GREEN, COLOR.BG.RED + COLOR.BG.GREEN) + self.assertEqual(colors.red, colors.blue + colors.red) + self.assertEqual(colors.bg.green, colors.bg.red + colors.bg.green) def testRepresentations(self): - color1 = COLOR.full(87) - self.assertEqual(color1, COLOR.DarkSlateGray2) - self.assertEqual(color1.basic, COLOR.DarkSlateGray2) - self.assertEqual(str(color1.basic), str(COLOR.LightGray)) + colors1 = colors.full(87) + self.assertEqual(colors1, colors.DarkSlateGray2) + self.assertEqual(colors1.basic, colors.DarkSlateGray2) + self.assertEqual(str(colors1.basic), str(colors.LightGray)) - color2 = COLOR.rgb(1,45,214) - self.assertEqual(str(color2.full), str(COLOR.Blue3A)) + colors2 = colors.rgb(1,45,214) + self.assertEqual(str(colors2.full), str(colors.Blue3A)) def testFromAnsi(self): - for color in COLOR[1:7]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG[1:7]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR[:16]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in COLOR.BG[:16]: - self.assertEqual(color, COLOR.from_ansi(str(color))) - for color in (COLOR.BOLD, COLOR.UNDERLINE, COLOR.ITALICS): - self.assertEqual(color, COLOR.from_ansi(str(color))) - - color = COLOR.BOLD + COLOR.FG.GREEN + COLOR.BG.BLUE + COLOR.UNDERLINE - self.assertEqual(color, COLOR.from_ansi(str(color))) - color = COLOR.RESET - self.assertEqual(color, COLOR.from_ansi(str(color))) + for c in colors[1:7]: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in colors.bg[1:7]: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in colors: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in colors.bg: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in colors[:16]: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in colors.bg[:16]: + self.assertEqual(c, colors.from_ansi(str(c))) + for c in (colors.bold, colors.underline, colors.italics): + self.assertEqual(c, colors.from_ansi(str(c))) + + col = colors.bold + colors.fg.green + colors.bg.blue + colors.underline + self.assertEqual(col, colors.from_ansi(str(col))) + col = colors.reset + self.assertEqual(col, colors.from_ansi(str(col))) def testWrappedColor(self): string = 'This is a string' wrapped = '\033[31mThis is a string\033[39m' - self.assertEqual(COLOR.RED.wrap(string), wrapped) - self.assertEqual(string << COLOR.RED, wrapped) - self.assertEqual(COLOR.RED*string, wrapped) - self.assertEqual(COLOR.RED[string], wrapped) + self.assertEqual(colors.red.wrap(string), wrapped) + self.assertEqual(string << colors.red, wrapped) + self.assertEqual(colors.red*string, wrapped) + self.assertEqual(colors.red[string], wrapped) - newcolor = COLOR.BLUE + COLOR.UNDERLINE - self.assertEqual(newcolor[string], string << newcolor) - self.assertEqual(newcolor.wrap(string), string << COLOR.BLUE + COLOR.UNDERLINE) + newcolors = colors.blue + colors.underline + self.assertEqual(newcolors[string], string << newcolors) + self.assertEqual(newcolors.wrap(string), string << colors.blue + colors.underline) def testUndoColor(self): - self.assertEqual('\033[39m', -COLOR.FG) - self.assertEqual('\033[39m', ~COLOR.FG) - self.assertEqual('\033[39m', ''-COLOR.FG) - self.assertEqual('\033[49m', -COLOR.BG) - self.assertEqual('\033[49m', ''-COLOR.BG) - self.assertEqual('\033[21m', -COLOR.BOLD) - self.assertEqual('\033[22m', -COLOR.DIM) + self.assertEqual('\033[39m', -colors.fg) + self.assertEqual('\033[39m', ~colors.fg) + self.assertEqual('\033[39m', ''-colors.fg) + self.assertEqual('\033[49m', -colors.bg) + self.assertEqual('\033[49m', ''-colors.bg) + self.assertEqual('\033[21m', -colors.bold) + self.assertEqual('\033[22m', -colors.dim) for i in range(7): - self.assertEqual('\033[39m', -COLOR(i)) - self.assertEqual('\033[49m', -COLOR.BG(i)) - self.assertEqual('\033[39m', -COLOR.FG(i)) - self.assertEqual('\033[49m', -COLOR.BG(i)) + self.assertEqual('\033[39m', -colors(i)) + self.assertEqual('\033[49m', -colors.bg(i)) + self.assertEqual('\033[39m', -colors.fg(i)) + self.assertEqual('\033[49m', -colors.bg(i)) for i in range(256): - self.assertEqual('\033[39m', -COLOR.FG[i]) - self.assertEqual('\033[49m', -COLOR.BG[i]) - self.assertEqual('\033[0m', -COLOR.RESET) - self.assertEqual(COLOR.DO_NOTHING, -COLOR.DO_NOTHING) + self.assertEqual('\033[39m', -colors.fg[i]) + self.assertEqual('\033[49m', -colors.bg[i]) + self.assertEqual('\033[0m', -colors.reset) + self.assertEqual(colors.do_nothing, -colors.do_nothing) - self.assertEqual(COLOR.BOLD.RESET, -COLOR.BOLD) + self.assertEqual(colors.bold.reset, -colors.bold) def testLackOfColor(self): Style.use_color = False - self.assertEqual('', COLOR.FG.RED) - self.assertEqual('', -COLOR.FG) - self.assertEqual('', COLOR.FG['LightBlue']) + self.assertEqual('', colors.fg.red) + self.assertEqual('', -colors.fg) + self.assertEqual('', colors.fg['LightBlue']) def testFromHex(self): - self.assertRaises(ColorNotFound, lambda: COLOR.hex('asdf')) - self.assertRaises(ColorNotFound, lambda: COLOR.hex('#1234Z2')) - self.assertRaises(ColorNotFound, lambda: COLOR.hex(12)) + self.assertRaises(ColorNotFound, lambda: colors.hex('asdf')) + self.assertRaises(ColorNotFound, lambda: colors.hex('#1234Z2')) + self.assertRaises(ColorNotFound, lambda: colors.hex(12)) def testDirectCall(self): - COLOR.BLUE() + colors.blue() if not hasattr(sys.stdout, "getvalue"): self.fail("Need to run in buffered mode!") output = sys.stdout.getvalue().strip() - self.assertEquals(output,str(COLOR.BLUE)) + self.assertEquals(output,str(colors.blue)) def testDirectCallArgs(self): - COLOR.BLUE("This is") + colors.blue("This is") if not hasattr(sys.stdout, "getvalue"): self.fail("Need to run in buffered mode!") output = sys.stdout.getvalue().strip() - self.assertEquals(output,str("This is" << COLOR.BLUE)) + self.assertEquals(output,str("This is" << colors.blue)) def testPrint(self): - COLOR.YELLOW.print('This is printed to stdout') + colors.yellow.print('This is printed to stdout') if not hasattr(sys.stdout, "getvalue"): self.fail("Need to run in buffered mode!") output = sys.stdout.getvalue().strip() - self.assertEquals(output,str(COLOR.YELLOW.wrap('This is printed to stdout'))) + self.assertEquals(output,str(colors.yellow.wrap('This is printed to stdout'))) class TestHTMLColor(unittest.TestCase): def test_html(self): red_tagged = 'This is tagged' - self.assertEqual(HTMLCOLOR.RED["This is tagged"], red_tagged) - self.assertEqual("This is tagged" << HTMLCOLOR.RED, red_tagged) - self.assertEqual("This is tagged" * HTMLCOLOR.RED, red_tagged) + self.assertEqual(htmlcolors.red["This is tagged"], red_tagged) + self.assertEqual("This is tagged" << htmlcolors.red, red_tagged) + self.assertEqual("This is tagged" * htmlcolors.red, red_tagged) twin_tagged = 'This is tagged' - self.assertEqual("This is tagged" << HTMLCOLOR.RED + HTMLCOLOR.EM, twin_tagged) - self.assertEqual("This is tagged" << HTMLCOLOR.EM << HTMLCOLOR.RED, twin_tagged) - self.assertEqual(HTMLCOLOR.EM * HTMLCOLOR.RED * "This is tagged", twin_tagged) - self.assertEqual(HTMLCOLOR.RED << "This should be wrapped", "This should be wrapped" << HTMLCOLOR.RED) + self.assertEqual("This is tagged" << htmlcolors.red + htmlcolors.em, twin_tagged) + self.assertEqual("This is tagged" << htmlcolors.em << htmlcolors.red, twin_tagged) + self.assertEqual(htmlcolors.em * htmlcolors.red * "This is tagged", twin_tagged) + self.assertEqual(htmlcolors.red << "This should be wrapped", "This should be wrapped" << htmlcolors.red) if __name__ == '__main__': unittest.main(buffer=True) diff --git a/tests/test_visual_color.py b/tests/test_visual_color.py index cb9d4f6aa..ca8237b05 100644 --- a/tests/test_visual_color.py +++ b/tests/test_visual_color.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from __future__ import with_statement, print_function import unittest -from plumbum import COLOR +from plumbum import colors class TestVisualColor(unittest.TestCase): @@ -10,7 +10,7 @@ def setUp(self): import colorama colorama.init() self.colorama = colorama - COLOR.use_color = True + colors.use_color = True print() print("Colorama initialized") except ImportError: @@ -22,23 +22,23 @@ def tearDown(self): def testVisualColors(self): print() - for c in COLOR.FG[:16]: + for c in colors.fg[:16]: with c: print('Cycle color test', end=' ') print(' - > back to normal') - with COLOR: - print(COLOR.FG.GREEN + "Green " - + COLOR.BOLD + "Bold " - - COLOR.BOLD + "Normal") + with colors: + print(colors.fg.green + "Green " + + colors.bold + "Bold " + - colors.bold + "Normal") print("Reset all") def testToggleColors(self): print() - print(COLOR.FG.RED("This is in red"), "but this is not") - print(COLOR.FG.GREEN + "Hi, " + COLOR.BG[23] - + "This is on a BG" - COLOR.BG + " and this is not") - COLOR.YELLOW.print("This is printed from color.") - COLOR.RESET() + print(colors.fg.red("This is in red"), "but this is not") + print(colors.fg.green + "Hi, " + colors.bg[23] + + "This is on a BG" - colors.bg + " and this is not") + colors.yellow.print("This is printed from color.") + colors.reset() if __name__ == '__main__': unittest.main() From db6d8792a0d731efeb09d682dd72ab4344d80c87 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 09:19:37 -0500 Subject: [PATCH 67/80] Adding rename to setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ffaa25b37..a43ee551a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ author_email = "tomerfiliba@gmail.com", license = "MIT", url = "http://plumbum.readthedocs.org", - packages = ["plumbum", "plumbum.cli", "plumbum.commands", "plumbum.machines", "plumbum.path", "plumbum.fs", "plumbum.color"], + packages = ["plumbum", "plumbum.cli", "plumbum.commands", "plumbum.machines", "plumbum.path", "plumbum.fs", "plumbum.colorlib"], platforms = ["POSIX", "Windows"], provides = ["plumbum"], keywords = "path, local, remote, ssh, shell, pipe, popen, process, execution", From 70b0dfb073833bc494cfdb4e360a3388ffd9e2c2 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 09:41:11 -0500 Subject: [PATCH 68/80] Some docs updates for the colors renaming --- docs/api/color.rst | 27 --------------------------- docs/api/colors.rst | 34 ++++++++++++++++++++++++++++++++++ docs/color.rst | 2 +- docs/index.rst | 2 +- 4 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 docs/api/color.rst create mode 100644 docs/api/colors.rst diff --git a/docs/api/color.rst b/docs/api/color.rst deleted file mode 100644 index d6262a458..000000000 --- a/docs/api/color.rst +++ /dev/null @@ -1,27 +0,0 @@ -Package plumbum.color -===================== - -.. automodule:: plumbum.color - :members: - :special-members: - -plumbum.color.styles --------------------- - -.. automodule:: plumbum.color.styles - :members: - :special-members: - -plumbum.color.factories ------------------------ - -.. automodule:: plumbum.color.factories - :members: - :special-members: - -plumbum.color.names -------------------- - -.. automodule:: plumbum.color.names - :members: - :special-members: diff --git a/docs/api/colors.rst b/docs/api/colors.rst new file mode 100644 index 000000000..91662f026 --- /dev/null +++ b/docs/api/colors.rst @@ -0,0 +1,34 @@ +Package plumbum.colors +====================== + +.. automodule:: plumbum.colors + :members: + :special-members: + +plumbum.colorlib +---------------- + +.. automodule:: plumbum.colorlib + :members: + :special-members: + +plumbum.colorlib.styles +----------------------- + +.. automodule:: plumbum.colorlib.styles + :members: + :special-members: + +plumbum.colorlib.factories +-------------------------- + +.. automodule:: plumbum.colorlib.factories + :members: + :special-members: + +plumbum.colorlib.names +---------------------- + +.. automodule:: plumbum.colorlib.names + :members: + :special-members: diff --git a/docs/color.rst b/docs/color.rst index 5ac1ff937..0351b02a0 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -90,7 +90,7 @@ following, typed into the command line, will restore it: .. code:: bash - $ python -m plumbum.color + $ python -m plumbum.colorlib Unsafe Manipulation ^^^^^^^^^^^^^^^^^^^ diff --git a/docs/index.rst b/docs/index.rst index 88fe541ac..411b9a0d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -127,7 +127,7 @@ missing from the guide, so you might want to consult with the API reference in t api/machines api/path api/fs - api/color + api/colors About ===== From 9569eb067c62e69ca46428641124a6c59874948f Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 09:52:38 -0500 Subject: [PATCH 69/80] Improvements to the __doc__ strings of repeated functions. --- plumbum/colorlib/factories.py | 9 +++-- plumbum/colorlib/styles.py | 64 ++++++++++++++++++++--------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/plumbum/colorlib/factories.py b/plumbum/colorlib/factories.py index 1082f13e6..fe89bb54b 100644 --- a/plumbum/colorlib/factories.py +++ b/plumbum/colorlib/factories.py @@ -75,12 +75,15 @@ def __iter__(self): return (self.full(i) for i in range(256)) def __neg__(self): - """Allows clearing a color""" + """Allows clearing a color with -""" + return self.reset + + def __invert__(self): + """Allows clearing a color with ~""" return self.reset - __invert__ = __neg__ def __rsub__(self, other): - """Makes a - COLOR.FG easier""" + """Makes ``- COLOR.FG`` easier""" return other + (-self) def __enter__(self): diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 2c1a5ff35..03f57fe55 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -376,8 +376,10 @@ def __copy__(self): def __neg__(self): """This negates the effect of the current color""" return self.invert() - __invert__ = __neg__ - """This allows ~color == -color.""" + + def __invert__(self) + """This allows ~color == -color.""" + return self.invert() def __sub__(self, other): """Implemented to make muliple Style objects work""" @@ -426,29 +428,36 @@ def __mul__(self, other): else: return self.wrap(other) - __rmul__ = wrap - """This class supports ``"String:" * color`` syntax, excpet in Python 2.6 due to bug with that Python.""" - - __rlshift__ = wrap - """This class supports ``"String:" << color`` syntax""" - - __rand__ = wrap - """Support for "String" & color syntax""" - - __and__ = __mul__ - """This class supports ``color & color2`` syntax. It also supports - ``"color & "String"`` syntax too. """ - - __lshift__ = __mul__ - """This class supports ``color << color2`` syntax. It also supports - ``"color << "String"`` syntax too. """ - - __rrshift__ = wrap - """This class supports ``"String:" >> color`` syntax""" - - __rshift__ = __mul__ - """This class supports ``color >> "String"`` syntax. It also supports - ``"color >> color2`` syntax too. """ + def __rmul__(self, other): + """This class supports ``"String:" * color`` syntax, excpet in Python 2.6 due to bug with that Python.""" + return self.wrap(other) + + def __rlshift__(self, other): + """This class supports ``"String:" << color`` syntax""" + return self.wrap(other) + + def __rand__(self, other): + """Support for "String" & color syntax""" + return self.wrap(other) + + def __and__(self, other): + """This class supports ``color & color2`` syntax. It also supports + ``"color & "String"`` syntax too. """ + return self.__mul__(other) + + def __lshift__(self, other): + """This class supports ``color << color2`` syntax. It also supports + ``"color << "String"`` syntax too. """ + return self.__mul__(other) + + def __rrshift__(self, other): + """This class supports ``"String:" >> color`` syntax""" + return self.wrap(other) + + def __rshift__(self, other): + """This class supports ``color >> "String"`` syntax. It also supports + ``"color >> color2`` syntax too. """ + return self.__mul__(other) def __call__(self, *printables, **kargs): @@ -482,8 +491,9 @@ def print(self, *printables, **kargs): print_ = print """Shortcut just in case user not using __future__""" - __getitem__ = wrap - """The [] syntax is supported for wrapping""" + def __getitem__(self, wrapped): + """The [] syntax is supported for wrapping""" + return self.wrap(wrapped) def __enter__(self): """Context manager support""" From d50e978faaac77d111ff33a5a94026c2a6dac9b4 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 10:05:55 -0500 Subject: [PATCH 70/80] Minor fixes, including tests for importing --- plumbum/colorlib/styles.py | 4 ++-- tests/test_factories.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 03f57fe55..c40003199 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -377,8 +377,8 @@ def __neg__(self): """This negates the effect of the current color""" return self.invert() - def __invert__(self) - """This allows ~color == -color.""" + def __invert__(self): + """This allows ~color == -color.""" return self.invert() def __sub__(self, other): diff --git a/tests/test_factories.py b/tests/test_factories.py index 1936a8c21..edb5071c5 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -6,6 +6,15 @@ from plumbum.colorlib import htmlcolors import sys + +class TestImportColors(unittest.TestCase): + def testDifferentImports(self): + import plumbum.colors + from plumbum.colors import bold + from plumbum.colors.fg import red + self.assertEqual(str(red), str(colors.red)) + self.assertEqual(str(bold), str(colors.bold)) + class TestANSIColor(unittest.TestCase): def setUp(self): From 3b76d5be337a20b302ff31727435fc36c7b78d05 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 10:42:12 -0500 Subject: [PATCH 71/80] Fix for IPython color extension --- plumbum/colorlib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plumbum/colorlib/__init__.py b/plumbum/colorlib/__init__.py index 8b85c1108..30e0d9c60 100644 --- a/plumbum/colorlib/__init__.py +++ b/plumbum/colorlib/__init__.py @@ -12,7 +12,7 @@ def load_ipython_extension(ipython): try: - from plumbum.color._ipython_ext import OutputMagics + from plumbum.colorlib._ipython_ext import OutputMagics except ImportError: print("IPython required for the IPython extension to be loaded.") raise From d88bcef2e1a15d2ec8da85e9cb1fc1574c97a1c4 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Wed, 29 Jul 2015 13:39:07 -0500 Subject: [PATCH 72/80] Dropped unneeded complexity for the colors module; it can just be the original factories. --- plumbum/colors.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/plumbum/colors.py b/plumbum/colors.py index 2a33287f3..ab5fe1396 100644 --- a/plumbum/colors.py +++ b/plumbum/colors.py @@ -6,21 +6,12 @@ import sys import os -from types import ModuleType -from plumbum.colorlib import ANSIStyle, StyleFactory +from plumbum.colorlib import ansicolors -class ColorModuleType(ModuleType, StyleFactory): - - def __init__(self, name, style): - ModuleType.__init__(self, name) - StyleFactory.__init__(self, style) - self.__path__ = os.path.abspath(os.path.dirname(__file__)) - -mymodule = ColorModuleType(__name__, ANSIStyle) # Oddly, the order here matters for Python2, but not Python3 -sys.modules[__name__ + '.fg'] = mymodule.fg -sys.modules[__name__ + '.bg'] = mymodule.bg -sys.modules[__name__] = mymodule +sys.modules[__name__ + '.fg'] = ansicolors.fg +sys.modules[__name__ + '.bg'] = ansicolors.bg +sys.modules[__name__] = ansicolors From e3169a86ec339bb5025e6e1eff6a092d50ce84f7 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Mon, 3 Aug 2015 11:51:50 -0500 Subject: [PATCH 73/80] Added __slots__, minor cleanup, factory call from Style or ansi now possible --- plumbum/colorlib/factories.py | 8 ++++++++ plumbum/colorlib/styles.py | 15 ++++++++++++++- tests/test_factories.py | 8 ++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/plumbum/colorlib/factories.py b/plumbum/colorlib/factories.py index fe89bb54b..6f467e27d 100644 --- a/plumbum/colorlib/factories.py +++ b/plumbum/colorlib/factories.py @@ -50,6 +50,10 @@ def hex(self, hexcode): """Return the extended color scheme color for a value.""" return self._style.from_color(self._style.color_class.from_hex(hexcode, fg=self._fg)) + def ansi(self, ansiseq): + """Make a style from an ansi text sequence""" + return self._style.from_ansi(ansiseq) + def __getitem__(self, val): """\ Shortcut to provide way to access colors numerically or by slice. @@ -68,6 +72,10 @@ def __getitem__(self, val): def __call__(self, val_or_r, g = None, b = None): """Shortcut to provide way to access colors.""" + if isinstance(val_or_r, self._style): + return self._style(val_or_r) + if isinstance(val_or_r, str) and '\033' in val_or_r: + return self.ansi(val_or_r) return self._style.from_color(self._style.color_class(val_or_r, g, b, fg=self._fg)) def __iter__(self): diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index c40003199..4d3d23537 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -82,10 +82,16 @@ class Color(object): """ + __slots__ = ('fg', 'isreset', 'rgb', 'number', 'representation', 'exact') def __init__(self, r_or_color=None, g=None, b=None, fg=True): """This works from color values, or tries to load non-simple ones.""" + if isinstance(r_or_color, type(self)): + for item in ('fg', 'isreset', 'rgb', 'number', 'representation', 'exact'): + setattr(self, item, getattr(r_or_color, item)) + return + self.fg = fg self.isreset = True # Starts as reset color self.rgb = (0,0,0) @@ -162,7 +168,6 @@ def _from_simple(self, color): elif isinstance(color, int) and 0 <= color < 16: self.number = color self.rgb = from_html(color_html[color]) - self.simple = True else: raise ColorNotFound("Did not find color: " + repr(color)) @@ -295,6 +300,8 @@ class Style(object): and can be called in a with statement. """ + __slots__ = ('attributes','fg', 'bg', 'isreset') + color_class = Color """The class of color to use. Never hardcode ``Color`` call when writing a Style method.""" @@ -318,6 +325,10 @@ def stdout(self, newout): def __init__(self, attributes=None, fgcolor=None, bgcolor=None, reset=False): """This is usually initialized from a factory.""" + if isinstance(attributes, type(self)): + for item in ('attributes','fg', 'bg', 'isreset'): + setattr(self, item, copy(getattr(attributes, item))) + return self.attributes = attributes if attributes is not None else dict() self.fg = fgcolor self.bg = bgcolor @@ -660,6 +671,7 @@ class ANSIStyle(Style): Set ``use_color = True/False`` if you want to control color for anything using this Style.""" + __slots__ = () use_color = sys.stdout.isatty() and os.name == "posix" attribute_names = attributes_ansi @@ -674,6 +686,7 @@ class HTMLStyle(Style): """This was meant to be a demo of subclassing Style, but actually can be a handy way to quicky color html text.""" + __slots__ = () attribute_names = dict(bold='b', em='em', italics='i', li='li', underline='span style="text-decoration: underline;"', code='code', ol='ol start=0', strikeout='s') end = '
        \n' diff --git a/tests/test_factories.py b/tests/test_factories.py index edb5071c5..2cf27b4c8 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -53,6 +53,14 @@ def testShifts(self): self.assertEqual("This" << colors.red, colors.red * "This") self.assertEqual(colors.red.wrap("This"), "This" << colors.red) + def testFromPreviousColor(self): + self.assertEqual(colors(colors.red), colors.red) + self.assertEqual(colors(colors.bg.red), colors.bg.red) + self.assertEqual(colors(colors.bold), colors.bold) + + def testFromCode(self): + self.assertEqual(colors('\033[31m'),colors.red) + def testLoadColorByName(self): self.assertEqual(colors['LightBlue'], colors.fg['LightBlue']) self.assertEqual(colors.bg['light_green'], colors.bg['LightGreen']) From b603a36ece168183892b49a1d3e00d8b206beaeb Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Mon, 3 Aug 2015 12:08:46 -0500 Subject: [PATCH 74/80] Added ansi support for empty Styles, added empty style generator to factories --- plumbum/colorlib/factories.py | 4 +++- plumbum/colorlib/styles.py | 18 +++++++++++------- tests/test_factories.py | 5 +++++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/plumbum/colorlib/factories.py b/plumbum/colorlib/factories.py index 6f467e27d..79a8e52fd 100644 --- a/plumbum/colorlib/factories.py +++ b/plumbum/colorlib/factories.py @@ -70,8 +70,10 @@ def __getitem__(self, val): except ColorNotFound: return self.hex(val) - def __call__(self, val_or_r, g = None, b = None): + def __call__(self, val_or_r=None, g = None, b = None): """Shortcut to provide way to access colors.""" + if val_or_r is None or val_or_r is '': + return self._style() if isinstance(val_or_r, self._style): return self._style(val_or_r) if isinstance(val_or_r, str) and '\033' in val_or_r: diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 4d3d23537..5212159cc 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -90,7 +90,7 @@ def __init__(self, r_or_color=None, g=None, b=None, fg=True): if isinstance(r_or_color, type(self)): for item in ('fg', 'isreset', 'rgb', 'number', 'representation', 'exact'): setattr(self, item, getattr(r_or_color, item)) - return + return self.fg = fg self.isreset = True # Starts as reset color @@ -446,25 +446,25 @@ def __rmul__(self, other): def __rlshift__(self, other): """This class supports ``"String:" << color`` syntax""" return self.wrap(other) - + def __rand__(self, other): """Support for "String" & color syntax""" return self.wrap(other) - + def __and__(self, other): """This class supports ``color & color2`` syntax. It also supports ``"color & "String"`` syntax too. """ return self.__mul__(other) - + def __lshift__(self, other): """This class supports ``color << color2`` syntax. It also supports ``"color << "String"`` syntax too. """ return self.__mul__(other) - + def __rrshift__(self, other): """This class supports ``"String:" >> color`` syntax""" return self.wrap(other) - + def __rshift__(self, other): """This class supports ``color >> "String"`` syntax. It also supports ``"color >> color2`` syntax too. """ @@ -541,7 +541,11 @@ def ansi_codes(self): @property def ansi_sequence(self): """This is the string ANSI sequence.""" - return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + codes = self.ansi_codes + if codes: + return '\033[' + ';'.join(map(str, self.ansi_codes)) + 'm' + else: + return '' def __repr__(self): name = self.__class__.__name__ diff --git a/tests/test_factories.py b/tests/test_factories.py index 2cf27b4c8..2a62b5e84 100644 --- a/tests/test_factories.py +++ b/tests/test_factories.py @@ -61,6 +61,11 @@ def testFromPreviousColor(self): def testFromCode(self): self.assertEqual(colors('\033[31m'),colors.red) + def testEmptyStyle(self): + self.assertEqual(str(colors()), '') + self.assertEqual(str(colors('')), '') + self.assertEqual(str(colors(None)), '') + def testLoadColorByName(self): self.assertEqual(colors['LightBlue'], colors.fg['LightBlue']) self.assertEqual(colors.bg['light_green'], colors.bg['LightGreen']) From e90c80801298bd015f165a192203fc090c467cc5 Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Mon, 3 Aug 2015 12:37:04 -0500 Subject: [PATCH 75/80] cli.Application has cleaner color integration, works with any color library, None or '' means no color. --- examples/geet.py | 1 + plumbum/cli/application.py | 46 ++++++++++++++++++++++++-------------- plumbum/colorlib/styles.py | 2 +- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/examples/geet.py b/examples/geet.py index abcb85687..eadaba02c 100755 --- a/examples/geet.py +++ b/examples/geet.py @@ -57,6 +57,7 @@ class Geet(cli.ColorfulApplication): """The l33t version control""" PROGNAME = "geet" VERSION = "1.7.2" + COLOR_PROGNAME = None verbosity = cli.SwitchAttr("--verbosity", cli.Set("low", "high", "some-very-long-name", "to-test-wrap-around"), help = "sets the verbosity level of the geet tool. doesn't really do anything except for testing line-wrapping " diff --git a/plumbum/cli/application.py b/plumbum/cli/application.py index 25037c846..6c900a481 100644 --- a/plumbum/cli/application.py +++ b/plumbum/cli/application.py @@ -4,11 +4,11 @@ import functools from plumbum.lib import six from textwrap import TextWrapper +from collections import defaultdict from plumbum.cli.terminal import get_terminal_size from plumbum.cli.switches import (SwitchError, UnknownSwitch, MissingArgument, WrongArgumentType, MissingMandatorySwitch, SwitchCombinationError, PositionalArgumentsError, switch, SubcommandError, Flag, CountOf) -from plumbum.colors import do_nothing from plumbum import colors @@ -91,21 +91,21 @@ def main(self, src, dst): * ``USAGE`` - the usage line (shown in help) - * ``COLOR_PROGNAME`` - the color to print the name in, defaults to ``DO_NOTHING`` + * ``COLOR_PROGNAME`` - the color to print the name in, defaults to None - * ``COLOR_PROGNAME`` - the color to print the discription in, defaults to ``DO_NOTHING`` + * ``COLOR_PROGNAME`` - the color to print the discription in, defaults to None - * ``COLOR_VERSION`` - the color to print the version in, defaults to ``DO_NOTHING`` + * ``COLOR_VERSION`` - the color to print the version in, defaults to None - * ``COLOR_HEADING`` - the color for headings, can be an attribute, defaults to ``DO_NOTHING`` + * ``COLOR_HEADING`` - the color for headings, can be an attribute, defaults to None - * ``COLOR_USAGE`` - the color for usage, defaults to ``DO_NOTHING`` + * ``COLOR_USAGE`` - the color for usage, defaults to None - * ``COLOR_SUBCOMMANDS`` - the color for subcommands, defaults to ``DO_NOTHING`` + * ``COLOR_SUBCOMMANDS`` - the color for subcommands, defaults to None - * ``COLOR_SWITCHES`` - the color for switches, defaults to ``DO_NOTHING`` + * ``COLOR_SWITCHES`` - the color for switches, defaults to None - * ``COLOR_METASWITCHES`` - the color for meta switches, defaults to ``DO_NOTHING`` + * ``COLOR_METASWITCHES`` - the color for meta switches, defaults to None * ``COLOR_GROUPS[]`` - Dictionary for colors for the groups, defaults to empty (no colors) @@ -122,12 +122,12 @@ def main(self, src, dst): DESCRIPTION = None VERSION = None USAGE = None - COLOR_PROGNAME = do_nothing - COLOR_DISCRIPTION = do_nothing - COLOR_VERSION = do_nothing - COLOR_HEADING = do_nothing - COLOR_USAGE = do_nothing - COLOR_SUBCOMMANDS = do_nothing + COLOR_PROGNAME = None + COLOR_DISCRIPTION = None + COLOR_VERSION = None + COLOR_HEADING = None + COLOR_USAGE = None + COLOR_SUBCOMMANDS = None COLOR_GROUPS = dict() COLOR_GROUPS_BODY = COLOR_GROUPS CALL_MAIN_IF_NESTED_COMMAND = True @@ -137,6 +137,18 @@ def main(self, src, dst): _unbound_switches = () def __init__(self, executable): + # Convert the colors to plumbum.colors on the instance (class remains the same) + for item in ('COLOR_PROGNAME', 'COLOR_DISCRIPTION', 'COLOR_VERSION', + 'COLOR_HEADING', 'COLOR_USAGE', 'COLOR_SUBCOMMANDS'): + setattr(self, item, colors(getattr(type(self), item))) + + self.COLOR_GROUPS = defaultdict(lambda: colors()) + self.COLOR_GROUPS_BODY = defaultdict(lambda: colors()) + for item in type(self).COLOR_GROUPS: + self.COLOR_GROUPS[item] = colors(type(self).COLOR_GROUPS[item]) + for item in type(self).COLOR_GROUPS_BODY: + self.COLOR_GROUPS_BODY[item] = colors(type(self).COLOR_GROUPS_BODY[item]) + if self.PROGNAME is None: self.PROGNAME = os.path.basename(executable) if self.DESCRIPTION is None: @@ -532,11 +544,11 @@ def help(self): # @ReservedAssignment def switchs(by_groups, show_groups): for grp, swinfos in sorted(by_groups.items(), key = lambda item: item[0]): if show_groups: - with (self.COLOR_HEADING + self.COLOR_GROUPS.get(grp, do_nothing)): + with (self.COLOR_HEADING + self.COLOR_GROUPS[grp]): print("%s:" % grp) # Print in body color unless empty, otherwise group color, otherwise nothing - with self.COLOR_GROUPS_BODY.get(grp, self.COLOR_GROUPS.get(grp, do_nothing)): + with self.COLOR_GROUPS_BODY.get(grp, self.COLOR_GROUPS[grp]): for si in sorted(swinfos, key = lambda si: si.names): swnames = ", ".join(("-" if len(n) == 1 else "--") + n for n in si.names if n in self._switches_by_name and self._switches_by_name[n] == si) diff --git a/plumbum/colorlib/styles.py b/plumbum/colorlib/styles.py index 5212159cc..b7cd810c8 100644 --- a/plumbum/colorlib/styles.py +++ b/plumbum/colorlib/styles.py @@ -38,7 +38,7 @@ class ResetNotSupported(Exception): class Color(object): """\ - Loaded with ``(r, g, b, fg)`` or ``(color, fg=)``. The second signature is a short cut + Loaded with ``(r, g, b, fg)`` or ``(color, fg=fg)``. The second signature is a short cut and will try full and hex loading. This class stores the idea of a color, rather than a specific implementation. From b4be2a2ce690b15e4a6cf1c094be7a212bdd6417 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 19 Aug 2015 11:14:50 -0500 Subject: [PATCH 76/80] Minor bugfix with emergency reset, and now can be used as python -m plumbum.colors --- plumbum/colorlib/__main__.py | 7 +++---- plumbum/colors.py | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plumbum/colorlib/__main__.py b/plumbum/colorlib/__main__.py index bbae13323..86dd6bffb 100644 --- a/plumbum/colorlib/__main__.py +++ b/plumbum/colorlib/__main__.py @@ -4,12 +4,11 @@ to recover terminal color. """ - -from plumbum.colorlib import ansicolor +from plumbum.colorlib import ansicolors def main(): - ansicolor.use_color=True - ansicolor.reset() + ansicolors.use_color=True + ansicolors.reset() if __name__ == '__main__': main() diff --git a/plumbum/colors.py b/plumbum/colors.py index ab5fe1396..68fdae12e 100644 --- a/plumbum/colors.py +++ b/plumbum/colors.py @@ -8,10 +8,12 @@ import os from plumbum.colorlib import ansicolors +from plumbum.colorlib.__main__ import main # Oddly, the order here matters for Python2, but not Python3 sys.modules[__name__ + '.fg'] = ansicolors.fg sys.modules[__name__ + '.bg'] = ansicolors.bg sys.modules[__name__] = ansicolors +sys.modules[__name__ + '.__main__'] = main From a6a542eb367ae185a1b234a320cfc1b40c747447 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 19 Aug 2015 11:40:09 -0500 Subject: [PATCH 77/80] Dropping unsupported python -m plumbum.colors, this doesn't work. --- plumbum/colors.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plumbum/colors.py b/plumbum/colors.py index 68fdae12e..d34256c88 100644 --- a/plumbum/colors.py +++ b/plumbum/colors.py @@ -8,12 +8,9 @@ import os from plumbum.colorlib import ansicolors -from plumbum.colorlib.__main__ import main - # Oddly, the order here matters for Python2, but not Python3 sys.modules[__name__ + '.fg'] = ansicolors.fg sys.modules[__name__ + '.bg'] = ansicolors.bg sys.modules[__name__] = ansicolors -sys.modules[__name__ + '.__main__'] = main From bfe8e68c02a04d847f4d4cf77769acd362e3e245 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 19 Aug 2015 11:45:47 -0500 Subject: [PATCH 78/80] Adding color changes from the command line, like -m plumbum.colorlib bg.green --- plumbum/colorlib/__init__.py | 2 +- plumbum/colorlib/__main__.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/plumbum/colorlib/__init__.py b/plumbum/colorlib/__init__.py index 30e0d9c60..45e45b30c 100644 --- a/plumbum/colorlib/__init__.py +++ b/plumbum/colorlib/__init__.py @@ -5,7 +5,7 @@ """ from plumbum.colorlib.factories import StyleFactory -from plumbum.colorlib.styles import Style, ANSIStyle, HTMLStyle +from plumbum.colorlib.styles import Style, ANSIStyle, HTMLStyle, ColorNotFound ansicolors = StyleFactory(ANSIStyle) htmlcolors = StyleFactory(HTMLStyle) diff --git a/plumbum/colorlib/__main__.py b/plumbum/colorlib/__main__.py index 86dd6bffb..18e1d1916 100644 --- a/plumbum/colorlib/__main__.py +++ b/plumbum/colorlib/__main__.py @@ -4,11 +4,27 @@ to recover terminal color. """ -from plumbum.colorlib import ansicolors +from plumbum.colorlib import ansicolors, ColorNotFound +import sys def main(): + color = ' '.join(sys.argv[1:]) if len(sys.argv) > 1 else None ansicolors.use_color=True - ansicolors.reset() + if color is None: + ansicolors.reset() + else: + names = color.replace('.', ' ').split() + prev = ansicolors + for name in names: + try: + prev = getattr(prev, name) + except AttributeError: + try: + prev = prev(int(name)) + except (ColorNotFound, ValueError): + prev = prev(name) + prev() + if __name__ == '__main__': main() From 431c4518a7575587ddb5ba316531f15c180ddee5 Mon Sep 17 00:00:00 2001 From: "Henry Schreiner (Muon Linux)" Date: Wed, 19 Aug 2015 12:18:34 -0500 Subject: [PATCH 79/80] Now implemented a working `-m plumbum.colors`! --- docs/color.rst | 14 +++++++++++++- plumbum/colorlib/__init__.py | 26 ++++++++++++++++++++++++++ plumbum/colorlib/__main__.py | 25 ++----------------------- plumbum/colors.py | 4 +++- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/docs/color.rst b/docs/color.rst index 0351b02a0..303a82264 100644 --- a/docs/color.rst +++ b/docs/color.rst @@ -90,7 +90,19 @@ following, typed into the command line, will restore it: .. code:: bash - $ python -m plumbum.colorlib + $ python -m plumbum.colors + +This also supports command line access to unsafe color manipulations, such as + +.. code:: bash + + $ python -m plumbum.colors blue + $ python -m plumbum.colors bg red + $ python -m plumbum.colors fg 123 + $ python -m plumbum.colors bg reset + $ python -m plumbum.colors underline + +You can use any path or number available as a style. Unsafe Manipulation ^^^^^^^^^^^^^^^^^^^ diff --git a/plumbum/colorlib/__init__.py b/plumbum/colorlib/__init__.py index 45e45b30c..9fc95a6d1 100644 --- a/plumbum/colorlib/__init__.py +++ b/plumbum/colorlib/__init__.py @@ -20,3 +20,29 @@ def load_ipython_extension(ipython): ipython.push({"colors":htmlcolors}) ipython.register_magics(OutputMagics) +def main(): + """Color changing script entry. Call using + python -m plumbum.colors, will reset if no arguements given.""" + import sys + color = ' '.join(sys.argv[1:]) if len(sys.argv) > 1 else '' + ansicolors.use_color=True + get_colors_from_string(color)() + +def get_colors_from_string(color=''): + """ + Sets color based on string, use `.` or space for seperator, + and numbers, fg/bg, htmlcodes, etc all accepted (as strings). + """ + + names = color.replace('.', ' ').split() + prev = ansicolors + for name in names: + try: + prev = getattr(prev, name) + except AttributeError: + try: + prev = prev(int(name)) + except (ColorNotFound, ValueError): + prev = prev(name) + return prev if isinstance(prev, Style) else prev.reset + diff --git a/plumbum/colorlib/__main__.py b/plumbum/colorlib/__main__.py index 18e1d1916..d848c59d4 100644 --- a/plumbum/colorlib/__main__.py +++ b/plumbum/colorlib/__main__.py @@ -4,27 +4,6 @@ to recover terminal color. """ -from plumbum.colorlib import ansicolors, ColorNotFound -import sys +from plumbum.colorlib import main -def main(): - color = ' '.join(sys.argv[1:]) if len(sys.argv) > 1 else None - ansicolors.use_color=True - if color is None: - ansicolors.reset() - else: - names = color.replace('.', ' ').split() - prev = ansicolors - for name in names: - try: - prev = getattr(prev, name) - except AttributeError: - try: - prev = prev(int(name)) - except (ColorNotFound, ValueError): - prev = prev(name) - prev() - - -if __name__ == '__main__': - main() +main() diff --git a/plumbum/colors.py b/plumbum/colors.py index d34256c88..e10772231 100644 --- a/plumbum/colors.py +++ b/plumbum/colors.py @@ -7,7 +7,9 @@ import sys import os -from plumbum.colorlib import ansicolors +from plumbum.colorlib import ansicolors, main +if __name__ == '__main__': + main() # Oddly, the order here matters for Python2, but not Python3 sys.modules[__name__ + '.fg'] = ansicolors.fg From 830db9d5949cefff4fd502cd25c8fa3fccf6132b Mon Sep 17 00:00:00 2001 From: Henry's desktop Ubuntu Date: Thu, 20 Aug 2015 21:13:32 -0500 Subject: [PATCH 80/80] Adding highlighting patch to color part of readme --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6fe00ee3f..cd7db2dd5 100644 --- a/README.rst +++ b/README.rst @@ -149,7 +149,9 @@ Sample output: Include dirs: ['foo/bar', 'spam/eggs'] Compiling: ('x.cpp', 'y.cpp', 'z.cpp') -**Color controls** :: +**Color controls** + +.. code-block:: python from plumbum import colors with colors.red: