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
+
+
+ - ■
#000000
Black
+ - ■
#800000
Red
+ - ■
#008000
Green
+ - ■
#808000
Yellow
+ - ■
#000080
Blue
+ - ■
#800080
Magenta
+ - ■
#008080
Cyan
+ - ■
#c0c0c0
LightGray
+ - ■
#808080
DarkGray
+ - ■
#ff0000
LightRed
+ - ■
#00ff00
LightGreen
+ - ■
#ffff00
LightYellow
+ - ■
#0000ff
LightBlue
+ - ■
#ff00ff
LightMagenta
+ - ■
#00ffff
LightCyan
+ - ■
#ffffff
White
+ - ■
#000000
Grey0
+ - ■
#00005f
NavyBlue
+ - ■
#000087
DarkBlue
+ - ■
#0000af
Blue3
+ - ■
#0000d7
Blue3
+ - ■
#0000ff
Blue1
+ - ■
#005f00
DarkGreen
+ - ■
#005f5f
DeepSkyBlue4
+ - ■
#005f87
DeepSkyBlue4
+ - ■
#005faf
DeepSkyBlue4
+ - ■
#005fd7
DodgerBlue3
+ - ■
#005fff
DodgerBlue2
+ - ■
#008700
Green4
+ - ■
#00875f
SpringGreen4
+ - ■
#008787
Turquoise4
+ - ■
#0087af
DeepSkyBlue3
+ - ■
#0087d7
DeepSkyBlue3
+ - ■
#0087ff
DodgerBlue1
+ - ■
#00af00
Green3
+ - ■
#00af5f
SpringGreen3
+ - ■
#00af87
DarkCyan
+ - ■
#00afaf
LightSeaGreen
+ - ■
#00afd7
DeepSkyBlue2
+ - ■
#00afff
DeepSkyBlue1
+ - ■
#00d700
Green3
+ - ■
#00d75f
SpringGreen3
+ - ■
#00d787
SpringGreen2
+ - ■
#00d7af
Cyan3
+ - ■
#00d7d7
DarkTurquoise
+ - ■
#00d7ff
Turquoise2
+ - ■
#00ff00
Green1
+ - ■
#00ff5f
SpringGreen2
+ - ■
#00ff87
SpringGreen1
+ - ■
#00ffaf
MediumSpringGreen
+ - ■
#00ffd7
Cyan2
+ - ■
#00ffff
Cyan1
+ - ■
#5f0000
DarkRed
+ - ■
#5f005f
DeepPink4
+ - ■
#5f0087
Purple4
+ - ■
#5f00af
Purple4
+ - ■
#5f00d7
Purple3
+ - ■
#5f00ff
BlueViolet
+ - ■
#5f5f00
Orange4
+ - ■
#5f5f5f
Grey37
+ - ■
#5f5f87
MediumPurple4
+ - ■
#5f5faf
SlateBlue3
+ - ■
#5f5fd7
SlateBlue3
+ - ■
#5f5fff
RoyalBlue1
+ - ■
#5f8700
Chartreuse4
+ - ■
#5f875f
DarkSeaGreen4
+ - ■
#5f8787
PaleTurquoise4
+ - ■
#5f87af
SteelBlue
+ - ■
#5f87d7
SteelBlue3
+ - ■
#5f87ff
CornflowerBlue
+ - ■
#5faf00
Chartreuse3
+ - ■
#5faf5f
DarkSeaGreen4
+ - ■
#5faf87
CadetBlue
+ - ■
#5fafaf
CadetBlue
+ - ■
#5fafd7
SkyBlue3
+ - ■
#5fafff
SteelBlue1
+ - ■
#5fd700
Chartreuse3
+ - ■
#5fd75f
PaleGreen3
+ - ■
#5fd787
SeaGreen3
+ - ■
#5fd7af
Aquamarine3
+ - ■
#5fd7d7
MediumTurquoise
+ - ■
#5fd7ff
SteelBlue1
+ - ■
#5fff00
Chartreuse2
+ - ■
#5fff5f
SeaGreen2
+ - ■
#5fff87
SeaGreen1
+ - ■
#5fffaf
SeaGreen1
+ - ■
#5fffd7
Aquamarine1
+ - ■
#5fffff
DarkSlateGray2
+ - ■
#870000
DarkRed
+ - ■
#87005f
DeepPink4
+ - ■
#870087
DarkMagenta
+ - ■
#8700af
DarkMagenta
+ - ■
#8700d7
DarkViolet
+ - ■
#8700ff
Purple
+ - ■
#875f00
Orange4
+ - ■
#875f5f
LightPink4
+ - ■
#875f87
Plum4
+ - ■
#875faf
MediumPurple3
+ - ■
#875fd7
MediumPurple3
+ - ■
#875fff
SlateBlue1
+ - ■
#878700
Yellow4
+ - ■
#87875f
Wheat4
+ - ■
#878787
Grey53
+ - ■
#8787af
LightSlateGrey
+ - ■
#8787d7
MediumPurple
+ - ■
#8787ff
LightSlateBlue
+ - ■
#87af00
Yellow4
+ - ■
#87af5f
DarkOliveGreen3
+ - ■
#87af87
DarkSeaGreen
+ - ■
#87afaf
LightSkyBlue3
+ - ■
#87afd7
LightSkyBlue3
+ - ■
#87afff
SkyBlue2
+ - ■
#87d700
Chartreuse2
+ - ■
#87d75f
DarkOliveGreen3
+ - ■
#87d787
PaleGreen3
+ - ■
#87d7af
DarkSeaGreen3
+ - ■
#87d7d7
DarkSlateGray3
+ - ■
#87d7ff
SkyBlue1
+ - ■
#87ff00
Chartreuse1
+ - ■
#87ff5f
LightGreen
+ - ■
#87ff87
LightGreen
+ - ■
#87ffaf
PaleGreen1
+ - ■
#87ffd7
Aquamarine1
+ - ■
#87ffff
DarkSlateGray1
+ - ■
#af0000
Red3
+ - ■
#af005f
DeepPink4
+ - ■
#af0087
MediumVioletRed
+ - ■
#af00af
Magenta3
+ - ■
#af00d7
DarkViolet
+ - ■
#af00ff
Purple
+ - ■
#af5f00
DarkOrange3
+ - ■
#af5f5f
IndianRed
+ - ■
#af5f87
HotPink3
+ - ■
#af5faf
MediumOrchid3
+ - ■
#af5fd7
MediumOrchid
+ - ■
#af5fff
MediumPurple2
+ - ■
#af8700
DarkGoldenrod
+ - ■
#af875f
LightSalmon3
+ - ■
#af8787
RosyBrown
+ - ■
#af87af
Grey63
+ - ■
#af87d7
MediumPurple2
+ - ■
#af87ff
MediumPurple1
+ - ■
#afaf00
Gold3
+ - ■
#afaf5f
DarkKhaki
+ - ■
#afaf87
NavajoWhite3
+ - ■
#afafaf
Grey69
+ - ■
#afafd7
LightSteelBlue3
+ - ■
#afafff
LightSteelBlue
+ - ■
#afd700
Yellow3
+ - ■
#afd75f
DarkOliveGreen3
+ - ■
#afd787
DarkSeaGreen3
+ - ■
#afd7af
DarkSeaGreen2
+ - ■
#afd7d7
LightCyan3
+ - ■
#afd7ff
LightSkyBlue1
+ - ■
#afff00
GreenYellow
+ - ■
#afff5f
DarkOliveGreen2
+ - ■
#afff87
PaleGreen1
+ - ■
#afffaf
DarkSeaGreen2
+ - ■
#afffd7
DarkSeaGreen1
+ - ■
#afffff
PaleTurquoise1
+ - ■
#d70000
Red3
+ - ■
#d7005f
DeepPink3
+ - ■
#d70087
DeepPink3
+ - ■
#d700af
Magenta3
+ - ■
#d700d7
Magenta3
+ - ■
#d700ff
Magenta2
+ - ■
#d75f00
DarkOrange3
+ - ■
#d75f5f
IndianRed
+ - ■
#d75f87
HotPink3
+ - ■
#d75faf
HotPink2
+ - ■
#d75fd7
Orchid
+ - ■
#d75fff
MediumOrchid1
+ - ■
#d78700
Orange3
+ - ■
#d7875f
LightSalmon3
+ - ■
#d78787
LightPink3
+ - ■
#d787af
Pink3
+ - ■
#d787d7
Plum3
+ - ■
#d787ff
Violet
+ - ■
#d7af00
Gold3
+ - ■
#d7af5f
LightGoldenrod3
+ - ■
#d7af87
Tan
+ - ■
#d7afaf
MistyRose3
+ - ■
#d7afd7
Thistle3
+ - ■
#d7afff
Plum2
+ - ■
#d7d700
Yellow3
+ - ■
#d7d75f
Khaki3
+ - ■
#d7d787
LightGoldenrod2
+ - ■
#d7d7af
LightYellow3
+ - ■
#d7d7d7
Grey84
+ - ■
#d7d7ff
LightSteelBlue1
+ - ■
#d7ff00
Yellow2
+ - ■
#d7ff5f
DarkOliveGreen1
+ - ■
#d7ff87
DarkOliveGreen1
+ - ■
#d7ffaf
DarkSeaGreen1
+ - ■
#d7ffd7
Honeydew2
+ - ■
#d7ffff
LightCyan1
+ - ■
#ff0000
Red1
+ - ■
#ff005f
DeepPink2
+ - ■
#ff0087
DeepPink1
+ - ■
#ff00af
DeepPink1
+ - ■
#ff00d7
Magenta2
+ - ■
#ff00ff
Magenta1
+ - ■
#ff5f00
OrangeRed1
+ - ■
#ff5f5f
IndianRed1
+ - ■
#ff5f87
IndianRed1
+ - ■
#ff5faf
HotPink
+ - ■
#ff5fd7
HotPink
+ - ■
#ff5fff
MediumOrchid1
+ - ■
#ff8700
DarkOrange
+ - ■
#ff875f
Salmon1
+ - ■
#ff8787
LightCoral
+ - ■
#ff87af
PaleVioletRed1
+ - ■
#ff87d7
Orchid2
+ - ■
#ff87ff
Orchid1
+ - ■
#ffaf00
Orange1
+ - ■
#ffaf5f
SandyBrown
+ - ■
#ffaf87
LightSalmon1
+ - ■
#ffafaf
LightPink1
+ - ■
#ffafd7
Pink1
+ - ■
#ffafff
Plum1
+ - ■
#ffd700
Gold1
+ - ■
#ffd75f
LightGoldenrod2
+ - ■
#ffd787
LightGoldenrod2
+ - ■
#ffd7af
NavajoWhite1
+ - ■
#ffd7d7
MistyRose1
+ - ■
#ffd7ff
Thistle1
+ - ■
#ffff00
Yellow1
+ - ■
#ffff5f
LightGoldenrod1
+ - ■
#ffff87
Khaki1
+ - ■
#ffffaf
Wheat1
+ - ■
#ffffd7
Cornsilk1
+ - ■
#ffffff
Grey100
+ - ■
#080808
Grey3
+ - ■
#121212
Grey7
+ - ■
#1c1c1c
Grey11
+ - ■
#262626
Grey15
+ - ■
#303030
Grey19
+ - ■
#3a3a3a
Grey23
+ - ■
#444444
Grey27
+ - ■
#4e4e4e
Grey30
+ - ■
#585858
Grey35
+ - ■
#626262
Grey39
+ - ■
#6c6c6c
Grey42
+ - ■
#767676
Grey46
+ - ■
#808080
Grey50
+ - ■
#8a8a8a
Grey54
+ - ■
#949494
Grey58
+ - ■
#9e9e9e
Grey62
+ - ■
#a8a8a8
Grey66
+ - ■
#b2b2b2
Grey70
+ - ■
#bcbcbc
Grey74
+ - ■
#c6c6c6
Grey78
+ - ■
#d0d0d0
Grey82
+ - ■
#dadada
Grey85
+ - ■
#e4e4e4
Grey89
+ - ■
#eeeeee
Grey93
+
+
+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 @@
+
+ - ■
#000000
Black
+ - ■
#800000
Red
+ - ■
#008000
Green
+ - ■
#808000
Yellow
+ - ■
#000080
Blue
+ - ■
#800080
Magenta
+ - ■
#008080
Cyan
+ - ■
#c0c0c0
LightGray
+ - ■
#808080
DarkGray
+ - ■
#ff0000
LightRed
+ - ■
#00ff00
LightGreen
+ - ■
#ffff00
LightYellow
+ - ■
#0000ff
LightBlue
+ - ■
#ff00ff
LightMagenta
+ - ■
#00ffff
LightCyan
+ - ■
#ffffff
White
+ - ■
#000000
Grey0
+ - ■
#00005f
NavyBlue
+ - ■
#000087
DarkBlue
+ - ■
#0000af
Blue3
+ - ■
#0000d7
Blue3
+ - ■
#0000ff
Blue1
+ - ■
#005f00
DarkGreen
+ - ■
#005f5f
DeepSkyBlue4
+ - ■
#005f87
DeepSkyBlue4
+ - ■
#005faf
DeepSkyBlue4
+ - ■
#005fd7
DodgerBlue3
+ - ■
#005fff
DodgerBlue2
+ - ■
#008700
Green4
+ - ■
#00875f
SpringGreen4
+ - ■
#008787
Turquoise4
+ - ■
#0087af
DeepSkyBlue3
+ - ■
#0087d7
DeepSkyBlue3
+ - ■
#0087ff
DodgerBlue1
+ - ■
#00af00
Green3
+ - ■
#00af5f
SpringGreen3
+ - ■
#00af87
DarkCyan
+ - ■
#00afaf
LightSeaGreen
+ - ■
#00afd7
DeepSkyBlue2
+ - ■
#00afff
DeepSkyBlue1
+ - ■
#00d700
Green3
+ - ■
#00d75f
SpringGreen3
+ - ■
#00d787
SpringGreen2
+ - ■
#00d7af
Cyan3
+ - ■
#00d7d7
DarkTurquoise
+ - ■
#00d7ff
Turquoise2
+ - ■
#00ff00
Green1
+ - ■
#00ff5f
SpringGreen2
+ - ■
#00ff87
SpringGreen1
+ - ■
#00ffaf
MediumSpringGreen
+ - ■
#00ffd7
Cyan2
+ - ■
#00ffff
Cyan1
+ - ■
#5f0000
DarkRed
+ - ■
#5f005f
DeepPink4
+ - ■
#5f0087
Purple4
+ - ■
#5f00af
Purple4
+ - ■
#5f00d7
Purple3
+ - ■
#5f00ff
BlueViolet
+ - ■
#5f5f00
Orange4
+ - ■
#5f5f5f
Grey37
+ - ■
#5f5f87
MediumPurple4
+ - ■
#5f5faf
SlateBlue3
+ - ■
#5f5fd7
SlateBlue3
+ - ■
#5f5fff
RoyalBlue1
+ - ■
#5f8700
Chartreuse4
+ - ■
#5f875f
DarkSeaGreen4
+ - ■
#5f8787
PaleTurquoise4
+ - ■
#5f87af
SteelBlue
+ - ■
#5f87d7
SteelBlue3
+ - ■
#5f87ff
CornflowerBlue
+ - ■
#5faf00
Chartreuse3
+ - ■
#5faf5f
DarkSeaGreen4
+ - ■
#5faf87
CadetBlue
+ - ■
#5fafaf
CadetBlue
+ - ■
#5fafd7
SkyBlue3
+ - ■
#5fafff
SteelBlue1
+ - ■
#5fd700
Chartreuse3
+ - ■
#5fd75f
PaleGreen3
+ - ■
#5fd787
SeaGreen3
+ - ■
#5fd7af
Aquamarine3
+ - ■
#5fd7d7
MediumTurquoise
+ - ■
#5fd7ff
SteelBlue1
+ - ■
#5fff00
Chartreuse2
+ - ■
#5fff5f
SeaGreen2
+ - ■
#5fff87
SeaGreen1
+ - ■
#5fffaf
SeaGreen1
+ - ■
#5fffd7
Aquamarine1
+ - ■
#5fffff
DarkSlateGray2
+ - ■
#870000
DarkRed
+ - ■
#87005f
DeepPink4
+ - ■
#870087
DarkMagenta
+ - ■
#8700af
DarkMagenta
+ - ■
#8700d7
DarkViolet
+ - ■
#8700ff
Purple
+ - ■
#875f00
Orange4
+ - ■
#875f5f
LightPink4
+ - ■
#875f87
Plum4
+ - ■
#875faf
MediumPurple3
+ - ■
#875fd7
MediumPurple3
+ - ■
#875fff
SlateBlue1
+ - ■
#878700
Yellow4
+ - ■
#87875f
Wheat4
+ - ■
#878787
Grey53
+ - ■
#8787af
LightSlateGrey
+ - ■
#8787d7
MediumPurple
+ - ■
#8787ff
LightSlateBlue
+ - ■
#87af00
Yellow4
+ - ■
#87af5f
DarkOliveGreen3
+ - ■
#87af87
DarkSeaGreen
+ - ■
#87afaf
LightSkyBlue3
+ - ■
#87afd7
LightSkyBlue3
+ - ■
#87afff
SkyBlue2
+ - ■
#87d700
Chartreuse2
+ - ■
#87d75f
DarkOliveGreen3
+ - ■
#87d787
PaleGreen3
+ - ■
#87d7af
DarkSeaGreen3
+ - ■
#87d7d7
DarkSlateGray3
+ - ■
#87d7ff
SkyBlue1
+ - ■
#87ff00
Chartreuse1
+ - ■
#87ff5f
LightGreen
+ - ■
#87ff87
LightGreen
+ - ■
#87ffaf
PaleGreen1
+ - ■
#87ffd7
Aquamarine1
+ - ■
#87ffff
DarkSlateGray1
+ - ■
#af0000
Red3
+ - ■
#af005f
DeepPink4
+ - ■
#af0087
MediumVioletRed
+ - ■
#af00af
Magenta3
+ - ■
#af00d7
DarkViolet
+ - ■
#af00ff
Purple
+ - ■
#af5f00
DarkOrange3
+ - ■
#af5f5f
IndianRed
+ - ■
#af5f87
HotPink3
+ - ■
#af5faf
MediumOrchid3
+ - ■
#af5fd7
MediumOrchid
+ - ■
#af5fff
MediumPurple2
+ - ■
#af8700
DarkGoldenrod
+ - ■
#af875f
LightSalmon3
+ - ■
#af8787
RosyBrown
+ - ■
#af87af
Grey63
+ - ■
#af87d7
MediumPurple2
+ - ■
#af87ff
MediumPurple1
+ - ■
#afaf00
Gold3
+ - ■
#afaf5f
DarkKhaki
+ - ■
#afaf87
NavajoWhite3
+ - ■
#afafaf
Grey69
+ - ■
#afafd7
LightSteelBlue3
+ - ■
#afafff
LightSteelBlue
+ - ■
#afd700
Yellow3
+ - ■
#afd75f
DarkOliveGreen3
+ - ■
#afd787
DarkSeaGreen3
+ - ■
#afd7af
DarkSeaGreen2
+ - ■
#afd7d7
LightCyan3
+ - ■
#afd7ff
LightSkyBlue1
+ - ■
#afff00
GreenYellow
+ - ■
#afff5f
DarkOliveGreen2
+ - ■
#afff87
PaleGreen1
+ - ■
#afffaf
DarkSeaGreen2
+ - ■
#afffd7
DarkSeaGreen1
+ - ■
#afffff
PaleTurquoise1
+ - ■
#d70000
Red3
+ - ■
#d7005f
DeepPink3
+ - ■
#d70087
DeepPink3
+ - ■
#d700af
Magenta3
+ - ■
#d700d7
Magenta3
+ - ■
#d700ff
Magenta2
+ - ■
#d75f00
DarkOrange3
+ - ■
#d75f5f
IndianRed
+ - ■
#d75f87
HotPink3
+ - ■
#d75faf
HotPink2
+ - ■
#d75fd7
Orchid
+ - ■
#d75fff
MediumOrchid1
+ - ■
#d78700
Orange3
+ - ■
#d7875f
LightSalmon3
+ - ■
#d78787
LightPink3
+ - ■
#d787af
Pink3
+ - ■
#d787d7
Plum3
+ - ■
#d787ff
Violet
+ - ■
#d7af00
Gold3
+ - ■
#d7af5f
LightGoldenrod3
+ - ■
#d7af87
Tan
+ - ■
#d7afaf
MistyRose3
+ - ■
#d7afd7
Thistle3
+ - ■
#d7afff
Plum2
+ - ■
#d7d700
Yellow3
+ - ■
#d7d75f
Khaki3
+ - ■
#d7d787
LightGoldenrod2
+ - ■
#d7d7af
LightYellow3
+ - ■
#d7d7d7
Grey84
+ - ■
#d7d7ff
LightSteelBlue1
+ - ■
#d7ff00
Yellow2
+ - ■
#d7ff5f
DarkOliveGreen1
+ - ■
#d7ff87
DarkOliveGreen1
+ - ■
#d7ffaf
DarkSeaGreen1
+ - ■
#d7ffd7
Honeydew2
+ - ■
#d7ffff
LightCyan1
+ - ■
#ff0000
Red1
+ - ■
#ff005f
DeepPink2
+ - ■
#ff0087
DeepPink1
+ - ■
#ff00af
DeepPink1
+ - ■
#ff00d7
Magenta2
+ - ■
#ff00ff
Magenta1
+ - ■
#ff5f00
OrangeRed1
+ - ■
#ff5f5f
IndianRed1
+ - ■
#ff5f87
IndianRed1
+ - ■
#ff5faf
HotPink
+ - ■
#ff5fd7
HotPink
+ - ■
#ff5fff
MediumOrchid1
+ - ■
#ff8700
DarkOrange
+ - ■
#ff875f
Salmon1
+ - ■
#ff8787
LightCoral
+ - ■
#ff87af
PaleVioletRed1
+ - ■
#ff87d7
Orchid2
+ - ■
#ff87ff
Orchid1
+ - ■
#ffaf00
Orange1
+ - ■
#ffaf5f
SandyBrown
+ - ■
#ffaf87
LightSalmon1
+ - ■
#ffafaf
LightPink1
+ - ■
#ffafd7
Pink1
+ - ■
#ffafff
Plum1
+ - ■
#ffd700
Gold1
+ - ■
#ffd75f
LightGoldenrod2
+ - ■
#ffd787
LightGoldenrod2
+ - ■
#ffd7af
NavajoWhite1
+ - ■
#ffd7d7
MistyRose1
+ - ■
#ffd7ff
Thistle1
+ - ■
#ffff00
Yellow1
+ - ■
#ffff5f
LightGoldenrod1
+ - ■
#ffff87
Khaki1
+ - ■
#ffffaf
Wheat1
+ - ■
#ffffd7
Cornsilk1
+ - ■
#ffffff
Grey100
+ - ■
#080808
Grey3
+ - ■
#121212
Grey7
+ - ■
#1c1c1c
Grey11
+ - ■
#262626
Grey15
+ - ■
#303030
Grey19
+ - ■
#3a3a3a
Grey23
+ - ■
#444444
Grey27
+ - ■
#4e4e4e
Grey30
+ - ■
#585858
Grey35
+ - ■
#626262
Grey39
+ - ■
#6c6c6c
Grey42
+ - ■
#767676
Grey46
+ - ■
#808080
Grey50
+ - ■
#8a8a8a
Grey54
+ - ■
#949494
Grey58
+ - ■
#9e9e9e
Grey62
+ - ■
#a8a8a8
Grey66
+ - ■
#b2b2b2
Grey70
+ - ■
#bcbcbc
Grey74
+ - ■
#c6c6c6
Grey78
+ - ■
#d0d0d0
Grey82
+ - ■
#dadada
Grey85
+ - ■
#e4e4e4
Grey89
+ - ■
#eeeeee
Grey93
+
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
-
-
- - ■
#000000
Black
- - ■
#800000
Red
- - ■
#008000
Green
- - ■
#808000
Yellow
- - ■
#000080
Blue
- - ■
#800080
Magenta
- - ■
#008080
Cyan
- - ■
#c0c0c0
LightGray
- - ■
#808080
DarkGray
- - ■
#ff0000
LightRed
- - ■
#00ff00
LightGreen
- - ■
#ffff00
LightYellow
- - ■
#0000ff
LightBlue
- - ■
#ff00ff
LightMagenta
- - ■
#00ffff
LightCyan
- - ■
#ffffff
White
- - ■
#000000
Grey0
- - ■
#00005f
NavyBlue
- - ■
#000087
DarkBlue
- - ■
#0000af
Blue3
- - ■
#0000d7
Blue3
- - ■
#0000ff
Blue1
- - ■
#005f00
DarkGreen
- - ■
#005f5f
DeepSkyBlue4
- - ■
#005f87
DeepSkyBlue4
- - ■
#005faf
DeepSkyBlue4
- - ■
#005fd7
DodgerBlue3
- - ■
#005fff
DodgerBlue2
- - ■
#008700
Green4
- - ■
#00875f
SpringGreen4
- - ■
#008787
Turquoise4
- - ■
#0087af
DeepSkyBlue3
- - ■
#0087d7
DeepSkyBlue3
- - ■
#0087ff
DodgerBlue1
- - ■
#00af00
Green3
- - ■
#00af5f
SpringGreen3
- - ■
#00af87
DarkCyan
- - ■
#00afaf
LightSeaGreen
- - ■
#00afd7
DeepSkyBlue2
- - ■
#00afff
DeepSkyBlue1
- - ■
#00d700
Green3
- - ■
#00d75f
SpringGreen3
- - ■
#00d787
SpringGreen2
- - ■
#00d7af
Cyan3
- - ■
#00d7d7
DarkTurquoise
- - ■
#00d7ff
Turquoise2
- - ■
#00ff00
Green1
- - ■
#00ff5f
SpringGreen2
- - ■
#00ff87
SpringGreen1
- - ■
#00ffaf
MediumSpringGreen
- - ■
#00ffd7
Cyan2
- - ■
#00ffff
Cyan1
- - ■
#5f0000
DarkRed
- - ■
#5f005f
DeepPink4
- - ■
#5f0087
Purple4
- - ■
#5f00af
Purple4
- - ■
#5f00d7
Purple3
- - ■
#5f00ff
BlueViolet
- - ■
#5f5f00
Orange4
- - ■
#5f5f5f
Grey37
- - ■
#5f5f87
MediumPurple4
- - ■
#5f5faf
SlateBlue3
- - ■
#5f5fd7
SlateBlue3
- - ■
#5f5fff
RoyalBlue1
- - ■
#5f8700
Chartreuse4
- - ■
#5f875f
DarkSeaGreen4
- - ■
#5f8787
PaleTurquoise4
- - ■
#5f87af
SteelBlue
- - ■
#5f87d7
SteelBlue3
- - ■
#5f87ff
CornflowerBlue
- - ■
#5faf00
Chartreuse3
- - ■
#5faf5f
DarkSeaGreen4
- - ■
#5faf87
CadetBlue
- - ■
#5fafaf
CadetBlue
- - ■
#5fafd7
SkyBlue3
- - ■
#5fafff
SteelBlue1
- - ■
#5fd700
Chartreuse3
- - ■
#5fd75f
PaleGreen3
- - ■
#5fd787
SeaGreen3
- - ■
#5fd7af
Aquamarine3
- - ■
#5fd7d7
MediumTurquoise
- - ■
#5fd7ff
SteelBlue1
- - ■
#5fff00
Chartreuse2
- - ■
#5fff5f
SeaGreen2
- - ■
#5fff87
SeaGreen1
- - ■
#5fffaf
SeaGreen1
- - ■
#5fffd7
Aquamarine1
- - ■
#5fffff
DarkSlateGray2
- - ■
#870000
DarkRed
- - ■
#87005f
DeepPink4
- - ■
#870087
DarkMagenta
- - ■
#8700af
DarkMagenta
- - ■
#8700d7
DarkViolet
- - ■
#8700ff
Purple
- - ■
#875f00
Orange4
- - ■
#875f5f
LightPink4
- - ■
#875f87
Plum4
- - ■
#875faf
MediumPurple3
- - ■
#875fd7
MediumPurple3
- - ■
#875fff
SlateBlue1
- - ■
#878700
Yellow4
- - ■
#87875f
Wheat4
- - ■
#878787
Grey53
- - ■
#8787af
LightSlateGrey
- - ■
#8787d7
MediumPurple
- - ■
#8787ff
LightSlateBlue
- - ■
#87af00
Yellow4
- - ■
#87af5f
DarkOliveGreen3
- - ■
#87af87
DarkSeaGreen
- - ■
#87afaf
LightSkyBlue3
- - ■
#87afd7
LightSkyBlue3
- - ■
#87afff
SkyBlue2
- - ■
#87d700
Chartreuse2
- - ■
#87d75f
DarkOliveGreen3
- - ■
#87d787
PaleGreen3
- - ■
#87d7af
DarkSeaGreen3
- - ■
#87d7d7
DarkSlateGray3
- - ■
#87d7ff
SkyBlue1
- - ■
#87ff00
Chartreuse1
- - ■
#87ff5f
LightGreen
- - ■
#87ff87
LightGreen
- - ■
#87ffaf
PaleGreen1
- - ■
#87ffd7
Aquamarine1
- - ■
#87ffff
DarkSlateGray1
- - ■
#af0000
Red3
- - ■
#af005f
DeepPink4
- - ■
#af0087
MediumVioletRed
- - ■
#af00af
Magenta3
- - ■
#af00d7
DarkViolet
- - ■
#af00ff
Purple
- - ■
#af5f00
DarkOrange3
- - ■
#af5f5f
IndianRed
- - ■
#af5f87
HotPink3
- - ■
#af5faf
MediumOrchid3
- - ■
#af5fd7
MediumOrchid
- - ■
#af5fff
MediumPurple2
- - ■
#af8700
DarkGoldenrod
- - ■
#af875f
LightSalmon3
- - ■
#af8787
RosyBrown
- - ■
#af87af
Grey63
- - ■
#af87d7
MediumPurple2
- - ■
#af87ff
MediumPurple1
- - ■
#afaf00
Gold3
- - ■
#afaf5f
DarkKhaki
- - ■
#afaf87
NavajoWhite3
- - ■
#afafaf
Grey69
- - ■
#afafd7
LightSteelBlue3
- - ■
#afafff
LightSteelBlue
- - ■
#afd700
Yellow3
- - ■
#afd75f
DarkOliveGreen3
- - ■
#afd787
DarkSeaGreen3
- - ■
#afd7af
DarkSeaGreen2
- - ■
#afd7d7
LightCyan3
- - ■
#afd7ff
LightSkyBlue1
- - ■
#afff00
GreenYellow
- - ■
#afff5f
DarkOliveGreen2
- - ■
#afff87
PaleGreen1
- - ■
#afffaf
DarkSeaGreen2
- - ■
#afffd7
DarkSeaGreen1
- - ■
#afffff
PaleTurquoise1
- - ■
#d70000
Red3
- - ■
#d7005f
DeepPink3
- - ■
#d70087
DeepPink3
- - ■
#d700af
Magenta3
- - ■
#d700d7
Magenta3
- - ■
#d700ff
Magenta2
- - ■
#d75f00
DarkOrange3
- - ■
#d75f5f
IndianRed
- - ■
#d75f87
HotPink3
- - ■
#d75faf
HotPink2
- - ■
#d75fd7
Orchid
- - ■
#d75fff
MediumOrchid1
- - ■
#d78700
Orange3
- - ■
#d7875f
LightSalmon3
- - ■
#d78787
LightPink3
- - ■
#d787af
Pink3
- - ■
#d787d7
Plum3
- - ■
#d787ff
Violet
- - ■
#d7af00
Gold3
- - ■
#d7af5f
LightGoldenrod3
- - ■
#d7af87
Tan
- - ■
#d7afaf
MistyRose3
- - ■
#d7afd7
Thistle3
- - ■
#d7afff
Plum2
- - ■
#d7d700
Yellow3
- - ■
#d7d75f
Khaki3
- - ■
#d7d787
LightGoldenrod2
- - ■
#d7d7af
LightYellow3
- - ■
#d7d7d7
Grey84
- - ■
#d7d7ff
LightSteelBlue1
- - ■
#d7ff00
Yellow2
- - ■
#d7ff5f
DarkOliveGreen1
- - ■
#d7ff87
DarkOliveGreen1
- - ■
#d7ffaf
DarkSeaGreen1
- - ■
#d7ffd7
Honeydew2
- - ■
#d7ffff
LightCyan1
- - ■
#ff0000
Red1
- - ■
#ff005f
DeepPink2
- - ■
#ff0087
DeepPink1
- - ■
#ff00af
DeepPink1
- - ■
#ff00d7
Magenta2
- - ■
#ff00ff
Magenta1
- - ■
#ff5f00
OrangeRed1
- - ■
#ff5f5f
IndianRed1
- - ■
#ff5f87
IndianRed1
- - ■
#ff5faf
HotPink
- - ■
#ff5fd7
HotPink
- - ■
#ff5fff
MediumOrchid1
- - ■
#ff8700
DarkOrange
- - ■
#ff875f
Salmon1
- - ■
#ff8787
LightCoral
- - ■
#ff87af
PaleVioletRed1
- - ■
#ff87d7
Orchid2
- - ■
#ff87ff
Orchid1
- - ■
#ffaf00
Orange1
- - ■
#ffaf5f
SandyBrown
- - ■
#ffaf87
LightSalmon1
- - ■
#ffafaf
LightPink1
- - ■
#ffafd7
Pink1
- - ■
#ffafff
Plum1
- - ■
#ffd700
Gold1
- - ■
#ffd75f
LightGoldenrod2
- - ■
#ffd787
LightGoldenrod2
- - ■
#ffd7af
NavajoWhite1
- - ■
#ffd7d7
MistyRose1
- - ■
#ffd7ff
Thistle1
- - ■
#ffff00
Yellow1
- - ■
#ffff5f
LightGoldenrod1
- - ■
#ffff87
Khaki1
- - ■
#ffffaf
Wheat1
- - ■
#ffffd7
Cornsilk1
- - ■
#ffffff
Grey100
- - ■
#080808
Grey3
- - ■
#121212
Grey7
- - ■
#1c1c1c
Grey11
- - ■
#262626
Grey15
- - ■
#303030
Grey19
- - ■
#3a3a3a
Grey23
- - ■
#444444
Grey27
- - ■
#4e4e4e
Grey30
- - ■
#585858
Grey35
- - ■
#626262
Grey39
- - ■
#6c6c6c
Grey42
- - ■
#767676
Grey46
- - ■
#808080
Grey50
- - ■
#8a8a8a
Grey54
- - ■
#949494
Grey58
- - ■
#9e9e9e
Grey62
- - ■
#a8a8a8
Grey66
- - ■
#b2b2b2
Grey70
- - ■
#bcbcbc
Grey74
- - ■
#c6c6c6
Grey78
- - ■
#d0d0d0
Grey82
- - ■
#dadada
Grey85
- - ■
#e4e4e4
Grey89
- - ■
#eeeeee
Grey93
-
+ :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' - ■
' + val
+ + r'
' + name
+ + r' ')
+ 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' - ■
' + 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(" - {0}
{1}
{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 += '' + self.attribute_names[attr].split()[0] + '>'
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(" - {0}
{1}
{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 += '' + self.attribute_names[attr].split(" ")[0] + '>'
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 @@
+
+
- ■
#000000
Black
- ■
#800000
Red
@@ -256,3 +297,5 @@
- ■
#e4e4e4
Grey89
- ■
#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 @@
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' - ■
' + 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' - ■
' + val
- + r'
' + name
- + r' ')
- 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 @@
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 += '' + self.attribute_names[attr].split(" ")[0] + '>'
- 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: