diff options
author | Martin Matusiak <numerodix@gmail.com> | 2021-05-05 14:53:05 +1000 |
---|---|---|
committer | Martin Matusiak <numerodix@gmail.com> | 2021-05-05 14:53:05 +1000 |
commit | 90a8a7ea912833f23ac900d3224a545a677b083c (patch) | |
tree | 26c0cf191bd7243ce09e4505abc6a6668571d595 | |
parent | c703fabb5d222dbfd2b845b8f3695f19774200e9 (diff) | |
parent | 41066f3dab3040e7c57f0f40764c13800707c513 (diff) | |
download | ansicolor-90a8a7ea912833f23ac900d3224a545a677b083c.tar.gz |
Merge branch 'develop'
-rw-r--r-- | .github/workflows/github-actions.yml | 19 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .travis.yml | 15 | ||||
-rw-r--r-- | CONTRIBUTORS | 7 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | README.rst | 111 | ||||
-rw-r--r-- | ansicolor/__init__.py | 79 | ||||
-rw-r--r-- | ansicolor/ansicolor.py | 206 | ||||
-rw-r--r-- | ansicolor/demos.py | 114 | ||||
-rw-r--r-- | dev-requirements.txt | 4 | ||||
-rw-r--r-- | setup.cfg | 3 | ||||
-rw-r--r-- | setup.py | 29 | ||||
-rw-r--r-- | tests/test_colors.py | 340 | ||||
-rw-r--r-- | tests/test_imports.py | 1 | ||||
-rw-r--r-- | tox.ini | 4 |
15 files changed, 657 insertions, 279 deletions
diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..57c3b47 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,19 @@ +name: ansicolor github actions CI +on: [push] +jobs: + run-unit-tests: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" + - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." + - name: Check out repository code + uses: actions/checkout@v2 + - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: List files in the repository + run: | + ls ${{ github.workspace }} + - run: pip3 install -r dev-requirements.txt + - run: py.test -v + - run: echo "🍏 This job's status is ${{ job.status }}."
\ No newline at end of file @@ -1,9 +1,11 @@ *.egg-info/ *.pyc .cache/ +.coverage .tox/ build/ dist/ docs/_build/ +htmlcov/ .vscode/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5dd83ab..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: python - -python: 2.7 - -env: - - TOX_ENV=py27 - - TOX_ENV=py34 - -install: - - pip install tox - -script: tox -e $TOX_ENV - -notifications: - email: true diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..398ea13 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,7 @@ +Original author: + Martin Matusiak <numerodix@gmail.com> + +Contributors: + Daniel Axtens <daniel@axtens.net> + Hypnoes <hypnoes@qq.com> + Michael Overmeyer <m.overmeyer@yahoo.ca> @@ -1,4 +1,4 @@ -Copyright 2008-2014 Martin Matusiak +Copyright 2008-2021 Martin Matusiak Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -4,16 +4,13 @@ ansicolor .. image:: https://badge.fury.io/py/ansicolor.png :target: https://badge.fury.io/py/ansicolor -.. image:: https://travis-ci.org/numerodix/ansicolor.png?branch=master - :target: https://travis-ci.org/numerodix/ansicolor - .. image:: https://img.shields.io/pypi/wheel/ansicolor.svg :target: https://pypi.python.org/pypi/ansicolor/ .. image:: https://img.shields.io/pypi/l/ansicolor.svg :target: https://pypi.python.org/pypi/ansicolor/ -Python version support: CPython 2.6, 2.7, 3.2, 3.3, 3.4 and PyPy. +Python version support: CPython 2.7, 3.2+. Introduction @@ -30,6 +27,8 @@ Installation $ pip install ansicolor +You can also download `tarballs from Github`_. + Documentation ------------- @@ -49,4 +48,108 @@ Take a look at the ``demos`` to see what's possible. $ python -m ansicolor.demos --diff + +Maintenance tasks +----------------- + + +Setting up a development environment (Ubuntu) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + # if you don't have `mkvirtualenv` & `workon` functions in your shell + $ sudo apt install virtualenvwrapper + + # creating the virtual env the first time + $ mkvirtualenv ansicolor + (ansicolor) $ pip install -r dev-requirements.txt + + # re-activating the virtual env next time + $ workon ansicolor + +All the steps below assume you have an activated virtual env (even though the +``(ansicolor)`` prompt is not shown). + + +Running unit tests +^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + $ py.test + + +Measuring code coverage +^^^^^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + $ py.test --cov=ansicolor.ansicolor + $ coverage html + # open htmlcov/index.html in the browser + + +Running all possible tests under tox +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +We use ``tox`` to run both the unit tests and the demos under several different +Python interpreter versions. Depending on which interpreters you have installed +(this is managed system-wide and not covered in this README) ``tox`` will most +likely give you a partial success. + +.. code:: bash + + # to run against all interpreters + $ tox + + # to run only against selected interpreters + $ tox -e py27,py38 + + +Checking code style +^^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + $ flake8 ansicolor + + +Re-formatting code +^^^^^^^^^^^^^^^^^^ + +.. code:: bash + + $ black ansicolor tests + + +Release a new version +^^^^^^^^^^^^^^^^^^^^^ + +1. Make sure ``develop`` branch is in sync with ``master``. +2. Make all the changes on ``develop``. + +Quality assurance (see steps above for how to): + +1. Make sure all tests are passing. +2. Make sure test coverage has not declined. +3. Make sure ``tox`` run succeeds on all (installed) interpreters. +4. Make sure ``flake8`` checker passes. +5. Make sure ``black`` formatter has no changes to make. + +Doing a release: + +1. Bump version in ``ansicolor/__init__.py``. +2. Git tag the new version and push the tag. This allows users/packagers to + download an auto-generated zip/tarball of the tagged release from Github. +3. ``python setup.py sdist`` +4. ``python setup.py bdist_wheel`` +5. ``twine upload dist/*`` + +Finally: + +1. Merge ``develop`` into ``master``. + + .. _`documentation`: https://ansicolor.readthedocs.org/ +.. _`tarballs from Github`: https://github.com/numerodix/ansicolor/tags diff --git a/ansicolor/__init__.py b/ansicolor/__init__.py index b423896..c56a499 100644 --- a/ansicolor/__init__.py +++ b/ansicolor/__init__.py @@ -1,36 +1,59 @@ from __future__ import absolute_import -from ansicolor.ansicolor import * # noqa +from ansicolor.ansicolor import black +from ansicolor.ansicolor import blue +from ansicolor.ansicolor import cyan +from ansicolor.ansicolor import green +from ansicolor.ansicolor import magenta +from ansicolor.ansicolor import red +from ansicolor.ansicolor import white +from ansicolor.ansicolor import yellow + +from ansicolor.ansicolor import colorize +from ansicolor.ansicolor import colorize_v2 +from ansicolor.ansicolor import get_code +from ansicolor.ansicolor import get_code_v2 +from ansicolor.ansicolor import wrap_string + +from ansicolor.ansicolor import highlight_string +from ansicolor.ansicolor import get_highlighter + +from ansicolor.ansicolor import strip_escapes +from ansicolor.ansicolor import justify_formatted + +from ansicolor.ansicolor import colordiff +from ansicolor.ansicolor import set_term_title +from ansicolor.ansicolor import write_out +from ansicolor.ansicolor import write_err + +from ansicolor.ansicolor import Colors __all__ = [ - 'black', - 'blue', - 'cyan', - 'green', - 'magenta', - 'red', - 'white', - 'yellow', - - 'colorize', - 'wrap_string', - 'get_code', - - 'highlight_string', - 'get_highlighter', - - 'strip_escapes', - 'justify_formatted', - - 'colordiff', - 'set_term_title', - 'write_out', - 'write_err', - - 'Colors', + "black", + "blue", + "cyan", + "green", + "magenta", + "red", + "white", + "yellow", + "colorize", + "colorize_v2", + "get_code", + "get_code_v2", + "wrap_string", + "highlight_string", + "get_highlighter", + "strip_escapes", + "justify_formatted", + "colordiff", + "set_term_title", + "write_out", + "write_err", + "Colors", ] -__major_version__ = "0.2" -__release__ = "6" +__major_version__ = "0.3" +__release__ = "0" __version__ = "%s.%s" % (__major_version__, __release__) diff --git a/ansicolor/ansicolor.py b/ansicolor/ansicolor.py index f84a938..75f34e6 100644 --- a/ansicolor/ansicolor.py +++ b/ansicolor/ansicolor.py @@ -8,33 +8,28 @@ import warnings __all__ = [ # noqa - 'black', - 'blue', - 'cyan', - 'green', - 'magenta', - 'red', - 'white', - 'yellow', - - 'colorize', - 'colorize_v2', - 'wrap_string', - 'get_code', - 'get_code_v2', - - 'highlight_string', - 'get_highlighter', - - 'strip_escapes', - 'justify_formatted', - - 'colordiff', - 'set_term_title', - 'write_out', - 'write_err', - - 'Colors', + "black", + "blue", + "cyan", + "green", + "magenta", + "red", + "white", + "yellow", + "colorize", + "colorize_v2", + "wrap_string", + "get_code", + "get_code_v2", + "highlight_string", + "get_highlighter", + "strip_escapes", + "justify_formatted", + "colordiff", + "set_term_title", + "write_out", + "write_err", + "Colors", ] @@ -43,7 +38,8 @@ _disabled = (not os.environ.get("TERM")) or (os.environ.get("TERM") == "dumb") class Colors(object): - '''Container class for colors''' + """Container class for colors""" + @classmethod def new(cls, colorname): try: @@ -62,7 +58,8 @@ class Colors(object): for color in cls._colorlist: yield color -## Define Colors members + +# Define Colors members Colors.new("Black") Colors.new("Red") Colors.new("Green") @@ -73,32 +70,37 @@ Colors.new("Cyan") Colors.new("White") -## Define coloring shorthands +# Define coloring shorthands def make_func(color): def f(s, bold=False, reverse=False): return colorize(s, color, bold=bold, reverse=reverse) - f.__doc__ = """ + + f.__doc__ = ( + """ Colorize string in %s :param string s: The string to colorize. :param bool bold: Whether to mark up in bold. :param bool reverse: Whether to mark up in reverse video. :rtype: string - """ % color.__name__.lower() + """ + % color.__name__.lower() + ) return f + for color in Colors.iter(): globals()[color.__name__.lower()] = make_func(color) -## Define highlighting colors +# Define highlighting colors highlights = [ Colors.Green, Colors.Yellow, Colors.Cyan, Colors.Blue, Colors.Magenta, - Colors.Red + Colors.Red, ] highlight_map = {} @@ -106,7 +108,7 @@ for (n, h) in enumerate(highlights): highlight_map[n] = [color for color in Colors.iter() if h == color].pop() -## Coloring functions +# Coloring functions def get_highlighter(colorid): """ Map a color index to a highlighting color. @@ -117,6 +119,7 @@ def get_highlighter(colorid): return highlight_map[colorid % len(highlights)] + def get_code(color, bold=False, reverse=False): """ Returns the escape code for styling with the given color, @@ -132,16 +135,17 @@ def get_code(color, bold=False, reverse=False): if _disabled: return "" - fmt = '0;0' + fmt = "0;0" if bold and reverse: - fmt = '1;7' + fmt = "1;7" elif reverse: - fmt = '0;7' + fmt = "0;7" elif bold: - fmt = '0;1' - color = (color is not None) and ';3%s' % color.id or '' + fmt = "0;1" + color = (color is not None) and ";3%s" % color.id or "" + + return "\033[" + fmt + color + "m" - return '\033[' + fmt + color + 'm' def get_code_v2(color, bold=False, reverse=False, underline=False, blink=False): """ @@ -159,18 +163,23 @@ def get_code_v2(color, bold=False, reverse=False, underline=False, blink=False): if _disabled: return "" - fmt = '0' - l = [] - if bold: l.append('1') - if underline: l.append('4') - if blink: l.append('5') - if reverse: l.append('7') - if len(l) != 0: - fmt = ';'.join(l) + fmt = "0" + items = [] + if bold: + items.append("1") + if underline: + items.append("4") + if blink: + items.append("5") + if reverse: + items.append("7") + if len(items) != 0: + fmt = ";".join(items) - color = (color is not None) and ';3%s' % color.id or '' + color = (color is not None) and ";3%s" % color.id or "" + + return "\033[" + fmt + color + "m" - return '\033[' + fmt + color + 'm' def colorize(s, color, bold=False, reverse=False, start=None, end=None): """ @@ -193,14 +202,25 @@ def colorize(s, color, bold=False, reverse=False, start=None, end=None): between = s[start:end] after = s[end:] - return ("%s%s%s%s%s" % (before, - get_code(color, bold=bold, reverse=reverse), - between, - get_code(None), - after)) - -def colorize_v2(s, color, bold=False, reverse=False, underline=False, blink=False, - start=None, end=None): + return "%s%s%s%s%s" % ( + before, + get_code(color, bold=bold, reverse=reverse), + between, + get_code(None), + after, + ) + + +def colorize_v2( + s, + color, + bold=False, + reverse=False, + underline=False, + blink=False, + start=None, + end=None, +): """ Colorize a string with the color given. :param string s: The string to colorize. @@ -222,14 +242,15 @@ def colorize_v2(s, color, bold=False, reverse=False, underline=False, blink=Fals between = s[start:end] after = s[end:] - return ("%s%s%s%s%s" % (before, - get_code_v2(color, bold=bold, - underline=underline, - blink=blink, - reverse=reverse), - between, - get_code_v2(None), - after)) + return "%s%s%s%s%s" % ( + before, + get_code_v2( + color, bold=bold, underline=underline, blink=blink, reverse=reverse + ), + between, + get_code_v2(None), + after, + ) def wrap_string(s, pos, color, bold=False, reverse=False): @@ -253,12 +274,14 @@ def wrap_string(s, pos, color, bold=False, reverse=False): if _disabled: if pos == 0: pos = 1 - return s[:pos - 1] + "|" + s[pos:] + return s[: pos - 1] + "|" + s[pos:] - return "%s%s%s%s" % (get_code(color, bold=bold, reverse=reverse), - s[:pos], - get_code(None), - s[pos:]) + return "%s%s%s%s" % ( + get_code(color, bold=bold, reverse=reverse), + s[:pos], + get_code(None), + s[pos:], + ) def highlight_string(s, *spanlists, **kw): @@ -276,7 +299,7 @@ def highlight_string(s, *spanlists, **kw): The `color` parameter has been deprecated in favor of `colors`. """ - colors = kw.get('colors', []) + colors = kw.get("colors", []) # pair span with color and id of the list -> (span, color, list_id) tuples = [] @@ -335,13 +358,13 @@ def highlight_string(s, *spanlists, **kw): # allow bold/reverse/nocolor styling as parameters if color: - if kw.get('color'): - color = kw.get('color') + if kw.get("color"): + color = kw.get("color") warnings.warn("color is deprecated", DeprecationWarning, 2) - elif kw.get('nocolor'): + elif kw.get("nocolor"): color = None - bold = kw.get('bold') or bold - reverse = kw.get('reverse') or reverse + bold = kw.get("bold") or bold + reverse = kw.get("reverse") or reverse if layer == 2: bold = True @@ -357,7 +380,7 @@ def highlight_string(s, *spanlists, **kw): cursor = pos segments.append(s[cursor:]) - return ''.join(segments) + return "".join(segments) def colordiff(x, y, color_x=Colors.Cyan, color_y=Colors.Green, debug=False): @@ -382,23 +405,25 @@ def colordiff(x, y, color_x=Colors.Cyan, color_y=Colors.Green, debug=False): rather than the longest common subsequence, but this just causes the diff to show more changed characters, the result is still correct""" sm = difflib.SequenceMatcher(None, x, y) - seq = '' + seq = "" for match in sm.get_matching_blocks(): - seq += x[match.a:match.a + match.size] + seq += x[match.a : match.a + match.size] return seq def make_generator(it): g = ((i, e) for (i, e) in enumerate(it)) + def f(): try: return next(g) except StopIteration: return (-1, None) + return f def log(s): if debug: - print(s) + print(s) # pragma: no cover seq = compute_seq(x, y) log(">>> %s , %s -> %s" % (x, y, seq)) @@ -421,31 +446,31 @@ def colordiff(x, y, color_x=Colors.Cyan, color_y=Colors.Green, debug=False): # character the same in all sets # -> unchanged if s == a == b: - log(' %s' % s) + log(" %s" % s) (sid, s) = it_seq() (aid, a) = it_x() (bid, b) = it_y() # character the same in orig and common # -> added in new elif s == a: - log('+%s' % b) + log("+%s" % b) y_spans.append((bid, bid + 1)) (bid, b) = it_y() # character the same in new and common # -> removed in orig elif s == b: - log('-%s' % a) + log("-%s" % a) x_spans.append((aid, aid + 1)) (aid, a) = it_x() # character not the same (eg. case change) # -> removed in orig and added in new elif a != b: if a: - log('-%s' % a) + log("-%s" % a) x_spans.append((aid, aid + 1)) (aid, a) = it_x() if b: - log('+%s' % b) + log("+%s" % b) y_spans.append((bid, bid + 1)) (bid, b) = it_y() @@ -477,10 +502,10 @@ def strip_escapes(s): :rtype: string """ - return re.sub('\033[[](?:(?:[0-9]*;)*)(?:[0-9]*m)', '', s) + return re.sub("\033\[(?:(?:[0-9]*;)*)(?:[0-9]*m)", "", s) -## Output functions +# Output functions def set_term_title(s): """ Set the title of a terminal window. @@ -491,6 +516,7 @@ def set_term_title(s): if not _disabled: sys.stdout.write("\033]2;%s\007" % s) + def write_to(target, s): # assuming we have escapes in the string if not _disabled: @@ -499,6 +525,7 @@ def write_to(target, s): target.write(s) target.flush() + def write_out(s): """ Write a string to ``sys.stdout``, strip escapes if output is a pipe. @@ -508,6 +535,7 @@ def write_out(s): write_to(sys.stdout, s) + def write_err(s): """ Write a string to ``sys.stderr``, strip escapes if output is a pipe. diff --git a/ansicolor/demos.py b/ansicolor/demos.py index d724250..69bba6b 100644 --- a/ansicolor/demos.py +++ b/ansicolor/demos.py @@ -17,92 +17,129 @@ def demo_color(): lst = [] - lst.extend([[], ['>>> Without colors'], []]) + lst.extend([[], [">>> Without colors"], []]) line = [] line.append(colorize("Standard".ljust(width), None)) line.append(colorize("Bold".ljust(width), None, bold=True)) line.append(colorize("Reverse".ljust(width), None, reverse=True)) - line.append(colorize("Bold & Rev".ljust(width), None, bold=True, reverse=True)) # noqa + line.append( + colorize("Bold & Rev".ljust(width), None, bold=True, reverse=True) + ) # noqa lst.append(line) - lst.extend([[], ['>>> Using colors'], []]) + lst.extend([[], [">>> Using colors"], []]) for color in Colors.iter(): line = [] line.append(colorize(color.__name__.ljust(width), color)) line.append(colorize(color.__name__.ljust(width), color, bold=True)) # noqa line.append(colorize(color.__name__.ljust(width), color, reverse=True)) # noqa - line.append(colorize(color.__name__.ljust(width), color, bold=True, reverse=True)) # noqa + line.append( + colorize(color.__name__.ljust(width), color, bold=True, reverse=True) + ) # noqa lst.append(line) - lst.extend([[], ['>>> Using highlighting colors'], []]) + lst.extend([[], [">>> Using highlighting colors"], []]) for color in Colors.iter(): color = get_highlighter(color.id) line = [] line.append(colorize(color.__name__.ljust(width), color)) line.append(colorize(color.__name__.ljust(width), color, bold=True)) # noqa line.append(colorize(color.__name__.ljust(width), color, reverse=True)) # noqa - line.append(colorize(color.__name__.ljust(width), color, bold=True, reverse=True)) # noqa + line.append( + colorize(color.__name__.ljust(width), color, bold=True, reverse=True) + ) # noqa lst.append(line) for line in lst: for item in line: - write_out('%s ' % item) + write_out("%s " % item) write_out("\n") + def demo_color_v2(): width = 10 lst = [] - lst.extend([[], ['>>> Without colors'], []]) + lst.extend([[], [">>> Without colors"], []]) line = [] line.append(colorize_v2("Standard".ljust(width), None)) line.append(colorize_v2("Bold".ljust(width), None, bold=True)) line.append(colorize_v2("Underline".ljust(width), None, underline=True)) line.append(colorize_v2("Blink".ljust(width), None, blink=True)) line.append(colorize_v2("Reverse".ljust(width), None, reverse=True)) - line.append(colorize_v2("Bold & Rev".ljust(width), None, bold=True, underline=True, reverse=True)) # noqa + line.append( + colorize_v2( + "Bold & Rev".ljust(width), None, bold=True, underline=True, reverse=True + ) + ) # noqa lst.append(line) - lst.extend([[], ['>>> Using colors_v2'], []]) + lst.extend([[], [">>> Using colors_v2"], []]) for color in Colors.iter(): line = [] line.append(colorize_v2(color.__name__.ljust(width), color)) line.append(colorize_v2(color.__name__.ljust(width), color, bold=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, underline=True)) # noqa + line.append( + colorize_v2(color.__name__.ljust(width), color, underline=True) + ) # noqa line.append(colorize_v2(color.__name__.ljust(width), color, blink=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, reverse=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, bold=True, underline=True, reverse=True)) # noqa + line.append( + colorize_v2(color.__name__.ljust(width), color, reverse=True) + ) # noqa + line.append( + colorize_v2( + color.__name__.ljust(width), + color, + bold=True, + underline=True, + reverse=True, + ) + ) # noqa lst.append(line) - lst.extend([[], ['>>> Using highlighting colors_v2'], []]) + lst.extend([[], [">>> Using highlighting colors_v2"], []]) for color in Colors.iter(): color = get_highlighter(color.id) line = [] line.append(colorize_v2(color.__name__.ljust(width), color)) line.append(colorize_v2(color.__name__.ljust(width), color, bold=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, underline=True)) # noqa + line.append( + colorize_v2(color.__name__.ljust(width), color, underline=True) + ) # noqa line.append(colorize_v2(color.__name__.ljust(width), color, blink=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, reverse=True)) # noqa - line.append(colorize_v2(color.__name__.ljust(width), color, bold=True, underline=True, reverse=True)) # noqa + line.append( + colorize_v2(color.__name__.ljust(width), color, reverse=True) + ) # noqa + line.append( + colorize_v2( + color.__name__.ljust(width), + color, + bold=True, + underline=True, + reverse=True, + ) + ) # noqa lst.append(line) for line in lst: for item in line: - write_out('%s ' % item) + write_out("%s " % item) write_out("\n") + def _demo_highlight(reverse=False): rxs = [ - '(b+).*\\1', - '(c+).*\\1', - '(d+).*\\1', - '(e+).*\\1', + "(b+).*\\1", + "(c+).*\\1", + "(d+).*\\1", + "(e+).*\\1", ] s = """\ aaabbbcccdddeeefffeeedddcccbbbaaa fffeeedddcccbbbaaabbbcccdddeeefff """ + def display(rxs, s): spanlists = [] colors = [] @@ -122,17 +159,19 @@ fffeeedddcccbbbaaabbbcccdddeeefff for (i, rx) in enumerate(rxs): color = get_highlighter(i) color = colorize(color.__name__.ljust(10), color) - write_out('Regex %s: %s %s\n' % (i, color, rx)) + write_out("Regex %s: %s %s\n" % (i, color, rx)) write_out(s) for i in range(0, len(rxs) + 1): - write_out('\n') + write_out("\n") display(rxs[:i], s) + def demo_highlight(): _demo_highlight() + def demo_highlight_reverse(): _demo_highlight(reverse=True) @@ -140,31 +179,30 @@ def demo_highlight_reverse(): def demo_diff(): def display_diff(s, t): (s_fmt, t_fmt) = colordiff(s, t) - write_out('>>> %s\n' % s_fmt) - write_out(' %s\n\n' % t_fmt) - - display_diff('first last', 'First Last') - display_diff('the the boss', 'the boss') - display_diff('the coder', 'the first coder') - display_diff('agcat', 'gac') - display_diff('XMJYAUZ', 'MZJAWXU') - display_diff('abcdfghjqz', 'abcdefgijkrxyz') + write_out(">>> %s\n" % s_fmt) + write_out(" %s\n\n" % t_fmt) + display_diff("first last", "First Last") + display_diff("the the boss", "the boss") + display_diff("the coder", "the first coder") + display_diff("agcat", "gac") + display_diff("XMJYAUZ", "MZJAWXU") + display_diff("abcdfghjqz", "abcdefgijkrxyz") -if __name__ == '__main__': +if __name__ == "__main__": try: action = sys.argv[1] except IndexError: print("Usage: %s [ --color | --highlight | --diff ]" % sys.argv[0]) sys.exit(1) - if action == '--color': + if action == "--color": demo_color() demo_color_v2() - elif action == '--highlight': + elif action == "--highlight": demo_highlight() - elif action == '--highlight-reverse': + elif action == "--highlight-reverse": demo_highlight_reverse() - elif action == '--diff': + elif action == "--diff": demo_diff() diff --git a/dev-requirements.txt b/dev-requirements.txt index 7735157..1d9fe17 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,10 @@ +black cheesecake flake8 +ipdb pytest +pytest-cov +pytest-randomly sphinx tox twine @@ -1,6 +1,7 @@ [flake8] exclude = .tox/,build/,docs/ -ignore = E301,E302,E303 +ignore = E301,E302,E303,W503,W605 +max_line_length = 88 [wheel] universal = 1 @@ -4,29 +4,24 @@ import ansicolor setup( - name='ansicolor', + name="ansicolor", version=ansicolor.__version__, description=( - 'A library to produce ansi color output ' - 'and colored highlighting and diffing' + "A library to produce ansi color output and colored highlighting and diffing" ), - author='Martin Matusiak', - author_email='numerodix@gmail.com', - url='https://github.com/numerodix/ansicolor', - + author="Martin Matusiak", + author_email="numerodix@gmail.com", + url="https://github.com/numerodix/ansicolor", packages=[ - 'ansicolor', + "ansicolor", ], classifiers=[ - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: CPython", ], # don't install as zipped egg zip_safe=False, diff --git a/tests/test_colors.py b/tests/test_colors.py index ea1fb38..37c53e3 100644 --- a/tests/test_colors.py +++ b/tests/test_colors.py @@ -1,153 +1,325 @@ +import contextlib + from ansicolor import Colors from ansicolor import blue from ansicolor import colordiff from ansicolor import colorize +from ansicolor import colorize_v2 from ansicolor import get_code +from ansicolor import get_code_v2 from ansicolor import get_highlighter +from ansicolor import highlight_string from ansicolor import justify_formatted from ansicolor import red +from ansicolor import set_term_title from ansicolor import strip_escapes from ansicolor import wrap_string -from ansicolor import get_code_v2 -from ansicolor import colorize_v2 +from ansicolor import write_err +from ansicolor import write_out +import ansicolor + + +@contextlib.contextmanager +def disabled_state(): + ansicolor.ansicolor._disabled = True + + try: + # run the test with colors disabled + yield + + finally: + ansicolor.ansicolor._disabled = False def test_codes(): # reset code - assert '\033[0;0m' == get_code(None) + assert "\033[0;0m" == get_code(None) # plain color codes - assert '\033[0;0;30m' == get_code(Colors.Black) - assert '\033[0;0;31m' == get_code(Colors.Red) - assert '\033[0;0;32m' == get_code(Colors.Green) - assert '\033[0;0;33m' == get_code(Colors.Yellow) - assert '\033[0;0;34m' == get_code(Colors.Blue) - assert '\033[0;0;35m' == get_code(Colors.Magenta) - assert '\033[0;0;36m' == get_code(Colors.Cyan) - assert '\033[0;0;37m' == get_code(Colors.White) + assert "\033[0;0;30m" == get_code(Colors.Black) + assert "\033[0;0;31m" == get_code(Colors.Red) + assert "\033[0;0;32m" == get_code(Colors.Green) + assert "\033[0;0;33m" == get_code(Colors.Yellow) + assert "\033[0;0;34m" == get_code(Colors.Blue) + assert "\033[0;0;35m" == get_code(Colors.Magenta) + assert "\033[0;0;36m" == get_code(Colors.Cyan) + assert "\033[0;0;37m" == get_code(Colors.White) # bold color - assert '\033[0;1;31m' == get_code(Colors.Red, bold=True) + assert "\033[0;1;31m" == get_code(Colors.Red, bold=True) # reverse color - assert '\033[0;7;31m' == get_code(Colors.Red, reverse=True) + assert "\033[0;7;31m" == get_code(Colors.Red, reverse=True) # bold + reverse color - assert '\033[1;7;31m' == get_code(Colors.Red, bold=True, reverse=True) + assert "\033[1;7;31m" == get_code(Colors.Red, bold=True, reverse=True) + + +def test_codes_disabled(): + with disabled_state(): + assert "" == get_code(Colors.Black) + def test_codes_v2(): # reset code - assert '\033[0m' == get_code_v2(None) + assert "\033[0m" == get_code_v2(None) # plain color codes - assert '\033[0;30m' == get_code_v2(Colors.Black) - assert '\033[0;31m' == get_code_v2(Colors.Red) - assert '\033[0;32m' == get_code_v2(Colors.Green) - assert '\033[0;33m' == get_code_v2(Colors.Yellow) - assert '\033[0;34m' == get_code_v2(Colors.Blue) - assert '\033[0;35m' == get_code_v2(Colors.Magenta) - assert '\033[0;36m' == get_code_v2(Colors.Cyan) - assert '\033[0;37m' == get_code_v2(Colors.White) + assert "\033[0;30m" == get_code_v2(Colors.Black) + assert "\033[0;31m" == get_code_v2(Colors.Red) + assert "\033[0;32m" == get_code_v2(Colors.Green) + assert "\033[0;33m" == get_code_v2(Colors.Yellow) + assert "\033[0;34m" == get_code_v2(Colors.Blue) + assert "\033[0;35m" == get_code_v2(Colors.Magenta) + assert "\033[0;36m" == get_code_v2(Colors.Cyan) + assert "\033[0;37m" == get_code_v2(Colors.White) # bold, underline, blink, reverse color - assert '\033[1;31m' == get_code_v2(Colors.Red, bold=True) - assert '\033[4;31m' == get_code_v2(Colors.Red, underline=True) - assert '\033[5;31m' == get_code_v2(Colors.Red, blink=True) - assert '\033[7;31m' == get_code_v2(Colors.Red, reverse=True) + assert "\033[1;31m" == get_code_v2(Colors.Red, bold=True) + assert "\033[4;31m" == get_code_v2(Colors.Red, underline=True) + assert "\033[5;31m" == get_code_v2(Colors.Red, blink=True) + assert "\033[7;31m" == get_code_v2(Colors.Red, reverse=True) # mixed color - assert '\033[1;4;31m' == get_code_v2(Colors.Red, bold=True, underline=True) - assert '\033[1;5;31m' == get_code_v2(Colors.Red, bold=True, blink=True) - assert '\033[1;7;31m' == get_code_v2(Colors.Red, bold=True, reverse=True) - - assert '\033[4;5;31m' == get_code_v2(Colors.Red, underline=True, blink=True) - assert '\033[4;7;31m' == get_code_v2(Colors.Red, underline=True, reverse=True) - - assert '\033[5;7;31m' == get_code_v2(Colors.Red, blink=True, reverse=True) - - assert '\033[1;4;5;31m' == get_code_v2(Colors.Red, bold=True, underline=True, blink=True) - assert '\033[1;4;7;31m' == get_code_v2(Colors.Red, bold=True, underline=True, reverse=True) - assert '\033[1;5;7;31m' == get_code_v2(Colors.Red, bold=True, blink=True, reverse=True) - assert '\033[1;4;5;7;31m' == get_code_v2(Colors.Red, bold=True, underline=True, blink=True, reverse=True) + assert "\033[1;4;31m" == get_code_v2(Colors.Red, bold=True, underline=True) + assert "\033[1;5;31m" == get_code_v2(Colors.Red, bold=True, blink=True) + assert "\033[1;7;31m" == get_code_v2(Colors.Red, bold=True, reverse=True) + + assert "\033[4;5;31m" == get_code_v2(Colors.Red, underline=True, blink=True) + assert "\033[4;7;31m" == get_code_v2(Colors.Red, underline=True, reverse=True) + + assert "\033[5;7;31m" == get_code_v2(Colors.Red, blink=True, reverse=True) + + assert "\033[1;4;5;31m" == get_code_v2( + Colors.Red, bold=True, underline=True, blink=True + ) + assert "\033[1;4;7;31m" == get_code_v2( + Colors.Red, bold=True, underline=True, reverse=True + ) + assert "\033[1;5;7;31m" == get_code_v2( + Colors.Red, bold=True, blink=True, reverse=True + ) + assert "\033[1;4;5;7;31m" == get_code_v2( + Colors.Red, bold=True, underline=True, blink=True, reverse=True + ) + + +def test_codes_v2_disabled(): + with disabled_state(): + assert "" == get_code_v2(Colors.Black) + def test_coloring(): - assert '\033[0;0;31m' + 'hi' + '\033[0;0m' == red('hi') + assert "\033[0;0;31m" + "hi" + "\033[0;0m" == red("hi") -def test_highlights(): +def test_get_hightlighter(): # can I get a highlighter? assert Colors.Green == get_highlighter(0) assert Colors.Yellow == get_highlighter(1) -def test_colorize1(): +def test_highlight_string_one_layer(): + text = "aaabbbaaa" + spanlists = [ + [(3, 6)], + ] + assert ( - get_code(Colors.Red) - + "Hi there" - + get_code(None) - ) == colorize("Hi there", Colors.Red) + "aaa" + get_code(Colors.Green) + "bbb" + get_code(None) + "aaa" + ) == highlight_string(text, *spanlists) + + +def test_highlight_string_one_color_chosen(): + text = "aaabbbaaa" + spanlists = [ + [(3, 6)], + ] -def test_colorize2(): assert ( - "H" - + get_code(Colors.Red) - + "i ther" - + get_code(None) - + "e" - ) == colorize("Hi there", Colors.Red, start=1, end=7) + "aaa" + get_code(Colors.Cyan) + "bbb" + get_code(None) + "aaa" + ) == highlight_string(text, *spanlists, color=Colors.Cyan) + + +def test_highlight_string_nocolor(): + text = "aaabbbaaa" + spanlists = [ + [(3, 6)], + ] -def test_colorize_v2(): assert ( - get_code(Colors.Red) - + "Hi there" - + get_code(None) - ) == colorize("Hi there", Colors.Red) + "aaa" + get_code(None) + "bbb" + get_code(None) + "aaa" + ) == highlight_string(text, *spanlists, nocolor=True) + + +def test_highlight_string_four_layers_inside_out(): + text = "aaabbbcccdddeeefffeeedddcccbbbaaa" + spanlists = [ + [(3, 30)], + [(6, 27)], + [(9, 24)], + [(12, 21)], + ] + colors = [ + Colors.Green, + Colors.Yellow, + Colors.Cyan, + Colors.Blue, + ] assert ( - "H" - + get_code(Colors.Red) - + "i ther" + "aaa" + + get_code(Colors.Green) + + "bbb" + + get_code(Colors.Yellow, bold=True) + + "ccc" + + get_code(Colors.Cyan, reverse=True) + + "ddd" + + get_code(Colors.Blue, bold=True, reverse=True) + + "eeefffeee" + + get_code(Colors.Cyan, reverse=True) + + "ddd" + + get_code(Colors.Yellow, bold=True) + + "ccc" + + get_code(Colors.Green) + + "bbb" + get_code(None) - + "e" - ) == colorize("Hi there", Colors.Red, start=1, end=7) + + "aaa" + ) == highlight_string(text, *spanlists, colors=colors) -def test_wrap_string(): +def test_highlight_string_four_layers_outside_in(): + text = "fffeeedddcccbbbaaabbbcccdddeeefff" + spanlists = [[(12, 21)], [(9, 24)], [(6, 27)], [(3, 30)]] + colors = [ + Colors.Green, + Colors.Yellow, + Colors.Cyan, + Colors.Blue, + ] + assert ( - get_code(Colors.Red) - + "Hi " + "fff" + + get_code(Colors.Blue) + + "eee" + + get_code(Colors.Blue, bold=True) + + "ddd" + + get_code(Colors.Blue, reverse=True) + + "ccc" + + get_code(Colors.Blue, bold=True, reverse=True) + + "bbbaaabbb" + + get_code(Colors.Blue, reverse=True) + + "ccc" + + get_code(Colors.Blue, bold=True) + + "ddd" + + get_code(Colors.Blue) + + "eee" + get_code(None) - + "there" - ) == wrap_string("Hi there", 3, Colors.Red) + + "fff" + ) == highlight_string(text, *spanlists, colors=colors) + + +def test_colorize(): + assert (get_code(Colors.Red) + "Hi there" + get_code(None)) == colorize( + "Hi there", Colors.Red + ) + + +def test_colorize_with_start_end(): + assert ("H" + get_code(Colors.Red) + "i ther" + get_code(None) + "e") == colorize( + "Hi there", Colors.Red, start=1, end=7 + ) + + +def test_colorize_v2(): + assert (get_code_v2(Colors.Red) + "Hi there" + get_code_v2(None)) == colorize_v2( + "Hi there", Colors.Red + ) + + assert ( + "H" + get_code_v2(Colors.Red) + "i ther" + get_code_v2(None) + "e" + ) == colorize_v2("Hi there", Colors.Red, start=1, end=7) + + +def test_wrap_string(): + assert (get_code(Colors.Red) + "Hi " + get_code(None) + "there") == wrap_string( + "Hi there", 3, Colors.Red + ) + + +def test_wrap_string_disabled(): + with disabled_state(): + assert "Hi|there" == wrap_string("Hi there", 3, Colors.Red) + assert "|i there" == wrap_string("Hi there", 0, Colors.Red) def test_strip_escapes(): - assert "Hi there" == strip_escapes( - colorize("Hi there", Colors.Red, start=3) + assert "Hi there" == strip_escapes(colorize("Hi there", Colors.Red, start=3)) + + assert ( + strip_escapes( + colorize("Hi", None, bold=True) + + " there, " + + colorize("stranger", Colors.Green, bold=True) + ) + == "Hi there, stranger" ) - assert strip_escapes( - colorize("Hi", None, bold=True) + - " there, " + - colorize("stranger", Colors.Green, bold=True) - ) == "Hi there, stranger" +def test_colordiff_different(): + x, y = colordiff("hi bob", "hi there", color_x=Colors.Red, color_y=Colors.Blue) -def test_colordiff(): - x, y = colordiff("hi bob", "hi there", - color_x=Colors.Red, color_y=Colors.Blue) + def fx(s): + return red(s, reverse=True) - fx = lambda s: red(s, reverse=True) - fy = lambda s: blue(s, reverse=True) + def fy(s): + return blue(s, reverse=True) assert x == "hi " + fx("b") + fx("o") + fx("b") assert y == "hi " + fy("t") + fy("h") + fy("e") + fy("r") + fy("e") +def test_colordiff_edited(): + x, y = colordiff("hi bobby", "hi bob", color_x=Colors.Red, color_y=Colors.Blue) + + def fx(s): + return red(s, reverse=True) + + assert x == "hi bob" + fx("b") + fx("y") + assert y == "hi bob" + + def test_justify_formatted(): def rjust(s, width): return s.rjust(width) - assert justify_formatted( - red("hi"), rjust, 10 - ) == " " + red("hi") + assert justify_formatted(red("hi"), rjust, 10) == " " + red("hi") + + +def test_set_term_title(capsys): + set_term_title("ansicolor demo") + + captured = capsys.readouterr() + assert "\033]2;ansicolor demo\007" == captured.out + + +def test_write_out(capfd): + text = colorize("Hi there", Colors.Red, start=3, end=6) + + # escapes will be stripped since we rely on capfd and os.isatty detects a + # tty device on the other end + write_out(text) + + captured = capfd.readouterr() + assert "Hi there" == captured.out + + +def test_write_err(capfd): + text = colorize("Hi there", Colors.Red, start=3, end=6) + + # escapes will be stripped since we rely on capfd and os.isatty detects a + # tty device on the other end + write_err(text) + + captured = capfd.readouterr() + assert "Hi there" == captured.err diff --git a/tests/test_imports.py b/tests/test_imports.py index ed5f654..4b07ce6 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -9,6 +9,7 @@ def test_importability(): from ansicolor import yellow # noqa from ansicolor import Colors # noqa + Colors.Black Colors.Blue Colors.Cyan @@ -1,5 +1,5 @@ [tox] -envlist=py26, py27, py32, py33, py34, pypy +envlist=py27, py38, py39 develop=true [testenv] @@ -7,7 +7,7 @@ setenv= TERM=xterm commands= - pip install pytest flake8 + pip install pytest python -m ansicolor.demos --color python -m ansicolor.demos --diff python -m ansicolor.demos --highlight |