#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
An efficient and simple ANSI colors module (and also a powerful script), with functions to print text using colors.
https://bitbucket.org/lbesson/ansicolortags.py
The names of the colors follow these conventions:
* for the eight ANSI colors (:black:`black`, :red:`red`, :green:`green`, :yellow:`yellow`, :blue:`blue`, :magenta:`magenta`, :cyan:`cyan`, :white:`white`):
+ the name in minuscule is for color **with bold** (example ':yellow:`yellow`'),
+ the name starting with 'B' is for color **without bold** (example ':yellow:`Byellow`'),
+ the name starting with a **capital** letter is for the background color (example ':yellow:`Yellow`').
* for the special effects (:blink:`blink`, *italic*, **bold**, :under:`underline`, negative), they might not always be supported, but they usually are:
+ the name in minuscule is used to turn *on* the effect (example 'i' to turn on italic),
+ the name starting in capital letter is used to turn *down* the effect (example 'I' to turn off italic).
* for the other special effects (``nocolors``, ``default``, ``Default``, ``clear``, ``el``), the effect is **immediate** (and seems to be well supported).
List of functions
=================
To print a string
-----------------
* :py:func:`sprint`: give a string,
* :py:func:`printc`: like :py:func:`print`, but with interpreting tags to put colors. **This is the most useful function in this module !**
* :py:func:`writec`: like printc, but using any file object (and no new line added at the end of the string).
To clean the terminal or the line
---------------------------------
* :py:func:`erase`: erase all ANSI colors tags in the string (like sprint, but erasing and not interpreting color tags),
* :py:func:`clearLine`, :py:func:`clearScreen`: to clear the current line or screen,
* :py:func:`Reset`: to return to default foreground and background, and stopping all *fancy* effects (like blinking or reverse video).
Others functions
----------------
* :py:func:`notify`: try to display a *system* notification. **Only on GNU/Linux with notify-send installed.**
* :py:func:`xtitle`: try to set the *title* of the terminal. Warning: **not always supported**.
Example of use (module)
=======================
To store a string, use :py:func:`sprint` (i.e. print to a string, *sprint*), like this: ::
>>> example = sprint("France flag is <blue>blue<white>white<red>red<white>, Italy flag have <green>green on it<white>.")
>>> example
'France flag is \x1b[01;34mblue\x1b[01;37mwhite\x1b[01;31mred\x1b[01;37m, Italy flag have \x1b[01;32mgreen on it\x1b[01;37m.'
The string ``example`` can then be printed, with colors, with: ::
>>> print(example) # Sorry, but in the documentation it is hard to show colors :)
France flag is bluewhitered, Italy flag have green on it.
To directly print a string colored by tags, use :py:func:`printc` (colors will be there if you try this in your terminal): ::
>>> printc("<reset><white>Batman's costum is <black>black<white>, Aquaman's costum is <blue>blue<white> and <green>green<white>.<reset>")
Batman's costum is black, Aquaman's costume is blue and green.
.. seealso::
This is the most useful function. To do the same, but on any file, use :py:func:`writec`.
Moreover, the function :py:func:`erase` can also be useful to simply delete all *valid* color tags: ::
>>> print(erase("<reset>Batman's costum is <black>black<white>, Aquaman's costum is <blue>blue<white> and <green>green<white>, and this is a non-valid <tag>, so it is kept like this.<reset>"))
Batman's costum is black, Aquaman's costum is blue and green, and this is a non-valid <tag>, so it is kept like this
In this last example, an ``<el>`` tag (:py:data:`el`) is used to erase the current content of the line, useful to make a *dynamical* print: ::
>>> writec("<reset><red>Computing <u>len(str(2**562017))<reset>...."); tmp = len(str(2**562017)); writec("<el><green>Done !<reset>")
Done !
The first part of the line 'Computing len(str(2**562017))....' have disappeared after the computation! (which takes about one second).
Example of use (script)
=======================
* To show the help :code:`$ ansicolortags.py --help`;
* To run a test :code:`$ ansicolortags.py --test`;
* To produce a `GNU Bash color aliases file <https://bitbucket.org/lbesson/bin/src/master/.color.sh>`_ :code:`$ ansicolortags.py --generate --file ~/.color_aliases.sh`.
Auto detection
==============
This script can normally detect if ANSI codes are supported :
1. ``$ ansicolortags.py --help`` : will print with colors if colors seems to be supported;
2. ``$ ansicolortags.py --help --noANSI`` : will print without any colors, even if it is possible;
3. ``$ ansicolortags.py --help --ANSI`` : will force the use of colors, even if they seems to be not supported.
And, the module part behaves exactly like the script part.
------------------------------------------------------------------------------
Elsewhere online
================
This project can be found on-line:
* here on BitBucket: `<https://bitbucket.org/lbesson/ansicolortags.py>`_
* here on PyPi: `<https://pypi.python.org/pypi/ansicolortags>`_
And some documentation on ANSI codes:
* The reference page for ANSI code is : `here on Wikipedia <https://en.wikipedia.org/wiki/ANSI_escape_code>`_.
* A reference page for XTitle escape code is : `here <http://www.faqs.org/docs/Linux-mini/Xterm-Title.html>`_.
Copyrigth
=========
© Lilian Besson, 2012-2017.
Complete documentation
======================
.. note:: The doc is available on-line, on `Read the Docs <https://www.readthedocs.org/>`_: `<http://ansicolortags.readthedocs.io/>`_.
"""
from __future__ import print_function, absolute_import
# %% Usual Modules
# Should we make them hidden from the interface of the script. Idea : remove from __all__ ?
import os
import sys
from subprocess import Popen
try:
from time import sleep
except ImportError:
def sleep(f):
""" Replacement of time.sleep()."""
print("time.sleep({}) should have been used.".format(f))
__author__ = 'Lilian Besson'
__version__ = '0.4'
__date__ = '2017-08-09T10:33:39'
# %% Program part
documentation_list_of_colors = """
List of all colors
==================
Bold colors: black, red, green, yellow, blue, magenta, cyan, white.
Normal colors (no bold): Bblack, Bred, Bgreen, Byellow, Bblue, Bmagenta, Bcyan, Bwhite.
Background colors: Black, Red, Green, Yellow, Blue, Magenta, Cyan, White.
Blink special caracters (Blink is faster than blink): blink, Blink:
.. warning::
Those are **not supported by all terminal emulator**.
For example, gnome-terminal and terminator **doesn't** support it,
but mintty.exe (Cygwin Windows terminal) support it.
Special characters to reinitialized ANSI codes buffer, or to do nothing: reset, nocolors.
Default foreground color, default background color: default, Default.
Italic on, off: italic, Italic. *Not always supported**
Bold on, off: b, B.
Underline on, off: u, U.
Reverse video on, off: neg, Neg. **Not always supported**.
Try to clear the screen: clear. **Not always supported**.
Try to erase the current line : el. **Not always supported**. Useful to use with ``sys.stdout.write`` and make the current printed line change !
Try to make an alarm sound. Also used to end the *xtitle* sequence: bell.
aliases for classic markup (/!\\, /?\\, 'WARNING', 'INFO' and 'ERROR').
warning, question, WARNING, INFO, ERROR:
"""
# %% Default values for new parsers
# FIXME switch to use docopt (https://github.com/docopt/docopt) instead of argparse
def _default_epilogue(version):
""" Default epilogue used by a new parser."""
return """\n\
<yellow>Copyrigth
=========<reset>
Version %s, (C) 2012-2017, Lilian Besson.""" % version
#: The default description, used when generate a parser by _parser_default function !
_default_description = "WARNING: No description had been given to _parser_default..."
def _add_default_options(parser, version=__version__):
""" _parser_default(parser, version, date, author) -> argparse.ArgumentParser instance.
Return the parser *parser*, modified by adding default options for the project,
which put the options : ``--version, ``--verbose, ``--noANSI and ``--ANSI`` and others basic options.
"""
parser.add_argument('--version', action='version', version='%(prog)s ' + version)
# Let those two lines, just to remember that others stuffs.
parser.add_argument('--noANSI', help="If present, ANSI escape code from ansicolortags are *disabled*.", action='store_true', default=False)
parser.add_argument('--ANSI', help="If present, ANSI escape code from ansicolortags are *forced* to be printed (even if the output is detected to be a pipe).", action='store_true', default=False)
return parser
# To make a default parser.
def _parser_default(description=_default_description,
epilogue="WARNING: No extra epilogue had been given to _parser_default...",
version=__version__, preprocessor=str):
""" _parser_default(parser, version, date, author) -> argparse.ArgumentParser instance.
Make a new *parser*, initialized by adding default options for the project (with :py:func:`_add_default_options`).
* The default description is :py:data:`_default_description`,
* The epilogue will be *epilogue*, then _default_epilogue(version, date, author).
* preprocessor can be :py:func:`sprint` or :py:func:`str` (default value), *i.e.* a string -> string function, and it will be used as a **preprocessor** for ``description`` and ``epilogue`` value.
Example:
>>> parser = _parser_default(description='<DELETE>A description.',
... epilogue='The description will no begin by the tag DELETE, thanks to sprint preprocessing.',
... preprocessor=lambda s: s.replace('<DELETE>', ''))
"""
# Passing RawDescriptionHelpFormatter as formatter_class= indicates that description and epilogue are already correctly formatted and should not be line-wrapped:
# RawTextHelpFormatter maintains whitespace for all sorts of help text, including argument descriptions.
# The other formatter class available, ArgumentDefaultsHelpFormatter, will add information about the default value of each of the arguments:
try:
import argparse
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
description=preprocessor(description),
prefix_chars='-+',
epilog=preprocessor(epilogue + _default_epilogue(version)))
# change the function *_add_default_options*, not this one.
parser = _add_default_options(parser, version)
return parser
except ImportError:
sys.stderr.write("""ERROR : when I tried to import the 'argparse' module.
The first possible reason is that you are using a version of Python too old (< 2.7).
The other possible reason is a other version of Python that the usual CPython :
- Jython,
- IronPython,
- PyPy,
for instance, are NOT supported.
""")
sys.stderr.flush()
sys.exit(1)
# %% Auto detection ?
ANSISupported = True
try:
#: If false, the module do almost NOTHING.
ANSISupported = 'TERM' in os.environ and os.environ['TERM'] != 'unknown'
if ('--noANSI' in sys.argv) or (not sys.stdout.isatty()):
ANSISupported = False
if '--ANSI' in sys.argv:
ANSISupported = True
except Exception as e:
print("I failed badly when trying to detect if ansicolortags are supported, reason = %s" % e)
ANSISupported = False
# print("DEBUG: ANSISupported =", ANSISupported)
# colors bold
black = "\033[01;30m" #: :black:`Black` and bold.
red = "\033[01;31m" #: :red:`Red` and bold.
green = "\033[01;32m" #: :green:`Green` and bold.
yellow = "\033[01;33m" #: :yellow:`Yellow` and bold.
blue = "\033[01;34m" #: :blue:`Blue` and bold.
magenta = "\033[01;35m" #: :magenta:`Magenta` and bold.
cyan = "\033[01;36m" #: :cyan:`Cyan` and bold.
white = "\033[01;37m" #: :white:`White` and bold.
# colors not bold
Bblack = "\033[02;30m" #: :black:`Black` and not bold.
Bred = "\033[02;31m" #: :red:`Red` and not bold.
Bgreen = "\033[02;32m" #: :green:`Green` and not bold.
Byellow = "\033[02;33m" #: :yellow:`Yellow` and not bold.
Bblue = "\033[02;34m" #: :blue:`Blue` and not bold.
Bmagenta = "\033[02;35m" #: :magenta:`Magenta` and not bold.
Bcyan = "\033[02;36m" #: :cyan:`Cyan` and not bold.
Bwhite = "\033[02;37m" #: :white:`White` and not bold.
# Background colors : not very useful
Black = "\033[40m" #: :black:`Black` background.
Red = "\033[41m" #: :red:`Red` background.
Green = "\033[42m" #: :green:`Green` background.
Yellow = "\033[43m" #: :yellow:`Yellow` background.
Blue = "\033[44m" #: :blue:`Blue` background.
Magenta = "\033[45m" #: :magenta:`Magenta` background.
Cyan = "\033[46m" #: :cyan:`Cyan` background.
White = "\033[47m" #: :white:`White` background.
# Others : blink and Blink are NOT SUPPORTED BY ALL TERMINAL
blink = "\033[05m" #: Make the text blink. NOT SUPPORTED BY ALL TERMINAL. On Windows (with mintty) it's ok. On Linux (with ttys, gnome-terminal or pyterminal, it's not).
Blink = "\033[06m" #: Make the text not blink (*i.e.* stop blinking).
# nocolors, then default, then Default
nocolors = "\033[0m" #: Nothing, base ANSI code.
default = "\033[39m" #: Default foreground.
Default = "\033[49m" #: Default background.
italic = "\033[3m" #: *Italic*.
Italic = "\033[23m" #: No *italic*.
b = "\033[1m" #: **Bold**.
B = "\033[2m" #: No **bold**.
u = "\033[4m" #: :under:`Underline`.
U = "\033[24m" #: No :under:`underline`.
neg = "\033[7m" #: Negative.
Neg = "\033[27m" #: No negative.
# New ones
clear = "\033[2J" #: Clear the screen.
el = "\r\033[K" #: Clear the *current line*.
reset = "\033[0;39;49m" #: Reset the current foreground and background values to default, and disable all effects.
bell = "\007" #: BEL is the bell character (``\007``). It *might* be interpreted and a sound signal might be heard (but not with every terminals).
title = "\033]0;" #: Use it like : ``writec("<title>My title<bell>")``, **and only** with ending the sequence with ``<bell>``.
# Not specially tags, but aliases.
warning = "%s%s/!\\%s%s" % (red, u, U, reset) #: A well colored Warning symbol (/!\\), in :red:`red` and underlined.
question = "%s%s/?\\%s%s" % (yellow, u, U, reset) #: A well colored question symbol (/?\\), in :yellow:`yellow` and underlined.
ERROR = "%s%sERROR%s" % (reset, red, reset) #: A well colored ERROR word, in :red:`red`.
WARNING = "%s%sWARNING%s" % (reset, yellow, reset) #: A well colored WARNING word, in :yellow:`yellow`.
INFO = "%s%sINFO%s" % (reset, blue, reset) #: A well colored INFO word, in :blue:`blue`.
#: List of all authorized colors. The dictionary :py:data:`colorDict` is more used.
colorList = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'Bblack', 'Bred', 'Bgreen', 'Byellow', 'Bblue', 'Bmagenta', 'Bcyan', 'Bwhite', 'Black', 'Red', 'Green', 'Yellow', 'Blue', 'Magenta', 'Cyan', 'White', 'blink', 'Blink', 'nocolors', 'default', 'Default', 'italic', 'Italic', 'b', 'B', 'u', 'U', 'neg', 'Neg', 'clear', 'el', 'reset', 'bell', 'title', 'warning', 'question', 'ERROR', 'WARNING', 'INFO']
#: List of all simple colors.
simpleColorList = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
# # Backup all colors.
# for name in colorList:
# exec('_%s = %s' % (name, name)) # Bad to use exec !
# # exec('colorDict["%s"] = %s' % (name, name))
# FIXED I could avoid this exec by building the colorDict manually
#: The key element of my script... A dictionary mapping color names to ANSI color code. Used in :py:func:`tocolor`.
colorDict = {
'black': black,
'red': red,
'green': green,
'yellow': yellow,
'blue': blue,
'magenta': magenta,
'cyan': cyan,
'white': white,
'Bblack': Bblack,
'Bred': Bred,
'Bgreen': Bgreen,
'Byellow': Byellow,
'Bblue': Bblue,
'Bmagenta': Bmagenta,
'Bcyan': Bcyan,
'Bwhite': Bwhite,
'Black': Black,
'Red': Red,
'Green': Green,
'Yellow': Yellow,
'Blue': Blue,
'Magenta': Magenta,
'Cyan': Cyan,
'White': White,
'blink': blink,
'Blink': Blink,
'nocolors': nocolors,
'default': default,
'Default': Default,
'italic': italic,
'Italic': Italic,
'b': b,
'B': B,
'u': u,
'U': U,
'neg': neg,
'Neg': Neg,
'clear': clear,
'el': el,
'reset': reset,
'bell': bell,
'title': title,
'warning': warning,
'question': question,
'ERROR': ERROR,
'WARNING': WARNING,
'INFO': INFO
}
# Turn off color tags interpretation if they are not supported
if not ANSISupported:
# for name in colorList:
# exec('%s = \"\"' % name) # Bad to use exec !
# print("DEBUG: removing colors!")
for key in colorDict:
colorDict[key] = ''
# print("DEBUG: colorDict =", colorDict)
[docs]def tocolor(mystring):
""" tocolor(mystring) -> string
Convert a string to a color.
``mystring`` **have** to be in :py:data:`colorDict` to be recognized (and interpreted).
Default value if ``mystring`` is not one of the color name is ``""`` the empty string.
"""
res = ""
# if mystring in colorList:
if mystring in colorDict:
# print("DEBUG: Calling exec('res = %s' % {})".format(mystring)) # Bad to use exec !
# exec("res = %s" % mystring) # Bad to use exec !
res = colorDict[mystring]
# print("DEBUG: res =", res)
# print("DEBUG: tocolor({}) -> {}".format(mystring, res))
return res
[docs]def sprint(chainWithTags, left='<', right='>', verbose=False):
""" sprint(chainWithTags, left='<', right='>', verbose=False) -> string
Parse a string containing color tags, when color is one of the previous define name,
and then return it, with color tags changed to concrete ANSI color codes.
**Tags are delimited** by ``left`` and ``right``.
By default, it's `HTML / Pango style <https://developer.gnome.org/pygtk/stable/pango-markup-language.html>`_ whit '<' and '>', but you can change them.
For example, a custom style even closer to HTML could be: ``left='<span color='`` and ``right = '</span>'`` is also possible.
.. warning::
It is more prudent to put nothing else than ANSI Colors (i.e. values in :py:data:`colorList`) between ``'<'`` and ``'>'`` in ``chainWithTags``.
The behavior of the function in case of false tags **is not perfect**.
Moreover, a good idea could be to try not to use '<' or '>' for anything else than tags.
I know, it's not perfect.
But, the syntax of color tags is so simple and so beautiful with this limitation that you will surely forgive me this, *won't you* ;) ?
Example (where unknown tags are left unmodified, and the colors should be there): ::
>>> print(sprint("<reset><blue>This is blue.<white> And <this> is white.<red> Now this is red because I am <angry> !<green><reset>"))
This is blue. And <this> is white. Now this is red because I am <angry> !
This function is used in all the following, so all other function can also use ``left`` and ``right`` arguments.
"""
# TODO improve efficiency of this core algorithm?
ls = chainWithTags.split(left)
if verbose:
print("\tls =", ls)
lls = list()
for s2 in ls:
if verbose:
print("\ts2 =", s2)
inte = s2.split(right)
if verbose:
print("\tinte =", inte)
if inte[0] in colorList:
inte[0] = tocolor(inte[0])
else:
if len(inte) > 1:
inte[0] = left + inte[0] + right
if verbose:
print("\tinte =", inte)
lls.append(inte)
if verbose:
print("\tlls =", lls)
res = ""
for _, llsii in enumerate(lls):
for _, llsiij in enumerate(llsii):
res += llsiij
return res
[docs]def erase(chainWithTags, left='<', right='>', verbose=False):
""" erase(chainWithTags, left='<', right='>', verbose=False) -> string
Parse a string containing color tags, when color is one of the previous define name,
and then return it, with color tags **erased**.
Example:
>>> print(erase("<reset><blue>This is blue.<white> And <this> is white.<red> Now this is red because I am <angry> !<reset>"))
This is blue. And <this> is white. Now this is red because I am <angry> !
This example seems exactly the same that the previous one in the documentation, but it's not (it is impossible to put color in the output of a Python example in Sphinx documentation, so there is **no color in output** in the examples... but be sure there is the real output !).
.. warning:: This function can mess up a string which has unmatched opening and closing tags (``<`` without a ``>`` or ``>`` without a ``<``), use it carefully.
"""
ls = chainWithTags.split(left)
if verbose:
print("\tls =", ls)
lls = list()
for s2 in ls:
if verbose:
print("\ts2 =", s2)
inte = s2.split(right)
if verbose:
print("\tinte =", inte)
if inte[0] in colorList:
inte[0] = '' #: Here the 'erasure' is made.
else:
if len(inte) > 1:
inte[0] = left + inte[0] + right
if verbose:
print("\tinte =", inte)
lls.append(inte)
if verbose:
print("\tlls =", lls)
res = ""
for _, llsii in enumerate(lls):
for _, llsiij in enumerate(llsii):
res += llsiij
return res
# FIXED how to add this *objects in Python 2 ?
# def printc(chainWithTags, *objects, left='<', right='>', sep=' ', end='\n', erase=False, **kwargs):
# I removed the keywords arguments
# left='<', right='>', sep=' ', end='\n', erase=False,
# and define them manually by poping keys from kwargs...
# Cf. https://www.python.org/dev/peps/pep-3102/
# https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists
[docs]def printc(chainWithTags, *objects, **kwargs):
""" printc(chainWithTags, *objects, left='<', right='>', sep=' ', end='\\n', erase=False, **kwargs) -> unit
Basically a shortcut to ``print(sprint(chainWithTags))`` : it analyzes all tags (i.e., it converts the tags like ``<red>`` to their ANSI code value, like :py:data:`red`), and then it prints the result.
Example (in a terminal the colors, and the bold and underlining effects would be there):
>>> printc("<reset><white>« <u>Fifty shades of <red>red<white><U> » could be a <green>good<white> book, <b>if it existed<B>.<reset>")
« Fifty shades of red » could be a good book, if it existed.
It accepts one or more "things" to print, exactly like :py:func:`print`: for each value ``arg_i`` in ``*objects``:
- if ``arg_i`` is a string, it is converted using ``sprint(arg_i, left=left, right=right)`` (:py:func:`sprint`), and then passed to :py:func:`print`.
- otherwise ``arg_i`` is passed to :py:func:`print` without modification (in the same order, of course).
Example with more than one object:
>>> print("OK n =", 17, "and z =", 1 + 5j, ".")
OK n = 17 and z = (1+5j) .
>>> printc("<reset><green>OK<white> n =<magenta>", 17, "<white>and z =<blue>", 1 + 5j, "<reset>.") # in a terminal, the output will have colors:
OK n = 17 and z = (1+5j) .
This is the more useful function in this package.
- If ``erase = True``, then :py:func:`erase` is used instead of :py:func:`sprint`
.. hint::
I suggest to use `ansicolortags.py <https://pypi.python.org/pypi/ansicolortags>`_ in your own project with the following piece of code:
.. code:: python
try:
from ansicolortags import printc
except ImportError:
print("WARNING: ansicolortags was not found, disabling colors instead.\\nPlease install it with 'pip install ansicolortags'")
def printc(*a, **kwargs):
print(*a, **kwargs)
.. hint::
During the last 4 years, `a lot of the small Python scripts I wrote <https://bitbucket.org/lbesson/bin/src/master/>`_ try to use this module to add some colors:
for example, `FreeSMS.py <https://bitbucket.org/lbesson/bin/src/master/FreeSMS.py>`_,
`plotnotes.py <https://bitbucket.org/lbesson/bin/src/master/plotnotes.py>`_,
`strapdown2html.py <https://bitbucket.org/lbesson/bin/src/master/strapdown2html.py>`_,
`calc_interets.py <https://bitbucket.org/lbesson/bin/src/master/calc_interets.py>`_...
"""
left = kwargs.pop('left') if 'left' in kwargs else '<'
right = kwargs.pop('right') if 'right' in kwargs else '>'
sep = kwargs.pop('sep') if 'sep' in kwargs else ' '
end = kwargs.pop('end') if 'end' in kwargs else '\n'
doerase = kwargs.pop('erase') if 'erase' in kwargs else False
# # DEBUG
# print("chainWithTags:")
# print(chainWithTags)
# print("objects:")
# print(objects)
# print("kwargs:")
# print(kwargs)
# print("left:", left)
# print("right:", right)
# print("sep:", sep)
# print("end:", end)
# print("doerase:", doerase)
# DONE for argument handling
if len(objects) == 0:
print(sprint(chainWithTags, left=left, right=right), sep=sep, end=end, **kwargs)
else:
fullargs = (chainWithTags,) + objects
# DEBUG
# print("fullargs:")
# print(fullargs)
if doerase: # XXX cannot be called erase, it is already the function
print(*(erase(s, left=left, right=right) if isinstance(s, str) else s for s in fullargs), sep=sep, end=end, **kwargs)
else:
print(*(sprint(s, left=left, right=right) if isinstance(s, str) else s for s in fullargs), sep=sep, end=end, **kwargs)
[docs]def writec(chainWithTags="", out=sys.stdout, left='<', right='>', flush=True):
""" writec(chainWithTags="", out=sys.stdout, left='<', right='>', flush=True) -> unit
Useful to print colored text **to a file**, represented by the object ``out``.
Also useful to print colored text, but without any trailing '\\n' character.
In this example, before the long computation begin, it prints 'Computing 2**(2**(2**4)).....',
and when the computation is done, erases the current line (with ``<el>`` tag, :py:data:`el`),
and prints ' Done !' in green, and the result of the computation: ::
>>> writec("<red>Computing<reset> 2**(2**(2**4))....."); tmp = 2**(2**(2**4)); writec("<el><green>Done !<reset>")
Done !
This example show how to use this module to write colored data in a file.
Be aware that this file now contains ANSI escape sequences.
For example, :code:`$ cat /tmp/colored-text.txt` will well print the colors, but editing the file will show *hard values* of escape code: ::
>>> my_file = open('/tmp/colored-text.txt', mode = 'w') # Open an random file.
>>> write("<reset><blue>this is blue.<white>And <this> is white.<red>Now this is red because I am <angry> !<green><reset>", file = my_file)
>>> # Now this file '/tmp/colored-text.txt' has some ANSI colored text in it.
Remark:
It can also be used to simply reinitialize the ANSI colors buffer, but the function :py:func:`Reset` is here for this: ::
>>> writec("<reset>")
.. warning::
The file ``out`` **will be flushed** by this function if ``flush`` is set to ``True`` (this is default behavior).
If you prefer no to, use ``flush=False`` option: ::
>>> writec(chainWithTags_1, out=my_file, flush=False)
>>> # many things...
>>> writec(chainWithTags_n, out=my_file, flush=False)
>>> my_file.flush() # only flush here!
"""
out.write(sprint(chainWithTags, left=left, right=right))
if flush:
out.flush()
[docs]def clearScreen():
""" clearScreen() -> unit
**Try to** clear the screen using ANSI code :py:data:`clear`.
"""
writec("<clear>")
[docs]def clearLine():
""" clearLine() -> unit
**Try to** clear the current line using ANSI code :py:data:`el`.
"""
writec("<el>")
[docs]def Reset():
""" Reset() -> unit
**Try to** reset the current ANSI codes buffer, using :py:data:`reset`.
"""
writec("<reset>")
# Other tools for the interface
[docs]def notify(msg="", obj="Notification sent by ansicolortags.notify", icon=None, verb=False):
""" notify(msg='', obj='Notification sent by ansicolortags.notify', icon=None, verb=False) -> bool
Notification using :py:mod:`subprocess` and ``notify-send`` (GNU/Linux command-line program).
Also print the informations directly to the screen (only if verb=True).
.. warning::
This does not use any *ANSI escape* codes, but the common *notify-send* GNU/Linux command line program.
It will probably fail (but cleanly) on Windows or Mac OS X.
- Return True if and only if the title have been correctly changed.
- Fails simply if ``notify-send`` is not found.
"""
try:
if icon:
Popen(['notify-send', obj, msg, "--icon = %s/%s" % (os.getcwd(), icon)])
if verb:
print("ansicolortags.notify(): A notification have been sent, with obj = %s, msg = %s, and icon = %s." % (obj, msg, icon))
else:
Popen(['notify-send', obj, msg])
if verb:
print("ansicolortags.notify(): A notification have been sent, with obj = %s, and msg = %s." % (obj, msg))
return 0
except Exception as e:
if verb:
print("ansicolortags.notify(): notify-send : not-found ! Returned exception is %s." % e)
return -1
[docs]def xtitle(new_title="", verb=False):
""" xtitle(new_title="", verb=False) -> 0 or 1
**Modify the current terminal title**.
Returns 0 if one of the two solutions worked, 1 otherwise.
An experimental try is with **ANSI escape code**,
if the simple way by calling the ``xtitle`` program does not work (or if it is not installed).
.. note::
The second solution simply uses the two *ANSI* Tags ``<title>`` (:py:data:`title`) and ``<bell>`` (:py:data:`bell`).
So, you can also do it with: ::
>>> ansicolortags.writec("<title>This is the new title of the terminal<bell>")
But this function *xtitle* is better: it tries two ways, and returns a signal to inform about his success.
"""
try:
Popen(['xtitle', new_title])
if verb:
print("ansicolortags.xtitle(): The title of the current terminal has been set to '%s'." % new_title)
except Exception as e:
if verb:
print("ansicolortags.xtitle(): xtitle : not-found ! Returned exception is %s." % e)
try:
writec("<title>%s<bell>" % erase(new_title))
except Exception as e:
if verb:
print("ansicolortags.notify(): With ANSI escape code <title> and <bell> : failed. ! Returned exception is %s." % e)
return 1
return 0
# %% Script part
[docs]def _generate_color_sh(file_name=None):
""" _generate_color_sh(file_name=None) -> string | unit.
Used to print or generate (if file_name is present and is a valid URI address)
a profile of all the colors defined in this file.
Print all ANSI Colors as :code:`export NAME="VALUE"`.
Useful to automatically generate a :download:`.color.sh` file, to be used with Bash:
and now you can easily colorized your Bash script with :code:`. color.sh` to import all colors.
The file is a list of :code:`export NAME="VALUE"`, to be used with GNU Bash.
.. note::
For example, to generate the :code:`color.sh` file with this script, use the ``-g`` or ``--generate`` option, with ``-f FILE`` or ``--file FILE``:
.. code:: bash
$ python -m ansicolortags -g -f color.sh
.. hint::
I suggest to save this :download:`.color.sh` file to your home, like :code:`~/.color.sh`, so it will be available for any GNU Bash script.
During the last 4 years, `all the Bash scripts I wrote <https://bitbucket.org/lbesson/bin/src/master/>`_ that uses this color profile (or assume it to be enabled, e.g. from `your .bashrc file <https://bitbucket.org/lbesson/bin/src/master/.bashrc#.bashrc-205>`_) assume it to be saved as :code:`~/.color.sh`.
For instance,
`PDFCompress <https://bitbucket.org/lbesson/bin/src/master/PDFCompress>`_,
`git-blame-last-commit.sh <https://bitbucket.org/lbesson/bin/src/master/git-blame-last-commit.sh>`_,
`mymake.sh <https://bitbucket.org/lbesson/bin/src/master/mymake.sh>`_,
`makequotes.sh <https://bitbucket.org/lbesson/bin/src/master/makequotes.sh>`_,
`photosmagic.sh <https://bitbucket.org/lbesson/bin/src/master/photosmagic.sh>`_,
`remove_trailing_spaces.sh <https://bitbucket.org/lbesson/bin/src/master/remove_trailing_spaces.sh>`_,
`series.sh <https://bitbucket.org/lbesson/bin/src/master/series.sh>`_,
`strapdown2pdf.sh <https://bitbucket.org/lbesson/bin/src/master/strapdown2pdf.sh>`_,
`Volume.sh <https://bitbucket.org/lbesson/bin/src/master/Volume.sh>`_ etc.
In a Bash script, I suggest to source this :download:`.color.sh` file like this (it checks if the file exists before sourcing it):
.. code:: bash
[ -f ~/.color.sh ] && . ~/.color.sh
"""
if file_name:
writec("<green> The file %s is creating.<reset> (C) Lilian Besson, 2012-2017.\t" % file_name)
writec("<blue><u>Listing of all ANSI colors...<reset>")
sleep(0.5)
writec("<el>...")
for s in colorList:
writec("<green><u>%s<reset>..." % s)
sleep(0.05)
writec("<el>...")
writec("<reset>Listing of all ANSI colors...><red><u> DONE !<reset>...")
sleep(0.5)
writec("<el>")
if file_name:
mfile = open(file_name, 'w')
else:
mfile = sys.stdout
mfile.write("""#!/bin/sh
#
# From ansicolortags.py module, auto generated with the --generate command
# More information on https://bitbucket.org/lbesson/ansicolortags.py/
#
# About the convention for the names of the colors :
# * for the eight colors black, red, green, yellow, blue, magenta, cyan, white:
# + the name in minuscule is for color **with bold** (example 'yellow'),
# + the name starting with 'B' is for color **without bold** (example 'Byellow'),
# + the name starting with a capital letter is for the background color (example 'Yellow').
# * for the special effects (blink, italic, bold, underline, negative), **not always supported** :
# + the name in minuscule is to **turn on** the effect,
# + the name starting in capital letter is to **turn off** the effect.
# * for the other special effects (nocolors, default, Default, clear, el), the effect is **immediate** (and seems to be well supported).
#
# About
# =====
# Use this file .color.sh in other GNU Bash scripts, simply by sourcing it with:
# $ [ -f ~/.color.sh ] && source ~/.color.sh
# And then:
# $ echo -e "${reset}French flag is ${blue}blue${reset}, ${white}white${reset}, ${red}red${reset}."
#
# Copyrigth
# =========
# (C) Lilian Besson, 2012-2017.
#
# List of colors
# ==============
""")
# FIXED it appeared to be failing on Python 3, now with this colorDict it works very well!
res = ""
for s in colorDict:
res = colorDict[s]
# print("""DEBUG: exec("res = ('%%s' %% %s)""" % s.replace('\x1b', '\\\\x1b'))
# exec("res = ('%%s' %% %s)" % s.replace('\x1b', '\\\\x1b')) # Bad to use exec !
# Unescaping special characters.
res = res.replace('\x1b', '\\033').replace('\r', '\\r').replace('\007', '\\007')
# print("DEBUG 1: res =", res)
mfile.write("export %s=\"%s\"\n" % (s, res))
# print("""DEBUG 2: mfile.write("export %s=\\"%s\\"\\n" """ % (s, res))
# mfile.write("export %s=\"%s\"\n" % (s, (r"%s" % res)))
# the r"%s" above is important ?
mfile.write("# DONE\n\n")
if file_name:
writec("<green> The file %s have been creating.<reset> (C) Lilian Besson 2012-2017.\n" % file_name)
sys.exit(0)
[docs]def _run_complete_tests():
""" _run_complete_tests() -> unit.
Launch a complete test of all ANSI Colors code in the list :py:data:`colorList`.
"""
printc("Launching full test for ANSI Colors.<default><Default><nocolors> now the text is printed with default value of the terminal...")
for s in colorList:
printc("The color '%s'\t is used to make the following effect : <%s>!! This is a sample text for '%s' !!<default><Default><nocolors>..." % (s, s, s))
# %% Main part, executed only if the script is executed
if __name__ == '__main__':
#: Generate the parser, with another module.
#: This variable is the preprocessor, given to description and epilogue by ParseCommandArgs,.
#: * erase: to print with no colors.
#: * sprint: to print with colors.
# preprocessor = __builtin__.str, if you wanna to *see* the tags.
mypreprocessor = sprint if ANSISupported else erase
#: Generate the parser, with another module.
myparser = _parser_default(
description='<green>ANSI Colors utility <red>module<reset> and <blue>script<reset> (ansicolortags.py).',
epilogue="""
Use this file <u>~/.color.sh<U> with other GNU Bash scripts, simply by sourcing him with:
<b><black>source ~/.color.sh<reset> # in a GNU Bash script
<b>About the <u>convention<U> for the names of the colors:<reset>
- for the eight colors black, red, green, yellow, blue, magenta, cyan, white:
+ the name in minuscule is for color <u>with bold<reset> (example <yellow>'yellow'<reset>),
+ the name starting with 'B' is for color <u>without bold<reset> (example <Byellow>'Byellow'<reset>),
+ the name starting with a capital letter is for the background color (example <Yellow>'Yellow'<reset>);
- for the special effects (blink, italic (i), bold (b), underline (u), negative), <u>not always supported<reset>:
+ the name in minuscule is to <u>turn on<reset> the effect (for example 'u' to <u>underline<U>),
+ the name starting in capital letter is to <u>turn down<reset> the effect (for example 'U' to stop underline);
- for the other special effects (nocolors, default, Default, clear, el), the effect is <u>immediate<reset> (and seems to be well supported).
<yellow>About
=====<reset>
This project can be found <green>on-line<reset>:
- here on <neg>BitBucket<Neg> : <u>https://bitbucket.org/lbesson/ansicolortags.py<U>,
- here on <neg>PyPi<Neg> : <u>https://pypi.python.org/pypi/ansicolortags<U>,
- and its documentation can be found here on <neg>Read the Docs<Neg> : <u>http://ansicolortags.readthedocs.io/<U>.
The reference page for ANSI code is : <u>https://en.wikipedia.org/wiki/ANSI_escape_code<U>.\n""",
version=__version__, preprocessor=mypreprocessor)
#: So, here become the interesting part.
group = myparser.add_mutually_exclusive_group()
group.add_argument("-t", "--test", help="Launch a complete test of all ANSI Colors code defined here.", action="store_true")
#: Description for the part with '--file' and '--generate' options.
group = myparser.add_argument_group('Generation of a GNU Bash color aliases file')
# Add lats two options
group.add_argument("-g", "--generate", help="Print all ANSI Colors as 'export name=\"value\"'.", action="store_true") # , required = True)
group.add_argument("-f", "--file", help="If present, and with --generate option, don't print the values, but export them in the file FILE (e.g. FILE = ~/.color.sh)", default=None)
#: The parser is done.
#: Use it to extract the args from the command line.
args = myparser.parse_args()
#: Use those args.
if args.generate:
if args.file:
_generate_color_sh(args.file)
else:
_generate_color_sh()
sys.exit(0)
if args.test:
_run_complete_tests()
sys.exit(0)
# Otherwise, print help and exit
myparser.print_help()
sys.exit(1)
# Remove the scripts values here
# FIXED: be sure we removed exactly the good ones
else:
# del _generate_color_sh
# del _run_complete_tests
del _parser_default
del _default_description
del _default_epilogue
del _add_default_options