Ansimarkup is an XML-like markup for producing colored terminal text.
from ansimarkup import ansiprint as print
print("<b>bold text</b>"))
print("<red>red text</red>", "<red,green>red text on a green background</red,green>")
print("<fg #ffaf00>orange text</fg #ffaf00>")The latest stable version of ansimarkup can be installed from PyPi:
python3 -m pip install ansimarkupfrom ansimarkup import parse, ansiprint
# parse() converts the tags to the corresponding ansi escape sequence.
parse("<b>bold</b> <d>dim</d>")
# ansiprint() works exactly like print(), but first runs parse() on all arguments.
ansiprint("<b>bold</b>", "<d>dim</d>")
ansiprint("<b>bold</b>", "<d>dim</d>", sep=":", file=sys.stderr)# Colors may be specified in one of several ways.
parse("<red>red foreground</red>")
parse("<RED>red background</RED>")
parse("<fg red>red foreground</fg red>")
parse("<bg red>red background</bg red>")
# Xterm, hex and rgb colors are accepted by the <fg> and <bg> tags.
parse("<fg 86>aquamarine foreground</fg 86>")
parse("<bg #00005f>dark blue background</bg #00005f>")
parse("<fg 0,95,0>dark green foreground</fg 0,95,0>")
# Tags may be nested.
parse("<r><Y>red text on a yellow foreground</Y></r>")
# The above may be more concisely written as:
parse("<r,y>red text on a yellow background</r,y>")
# This shorthand also supports style tags.
parse("<b,r,y>bold red text on a yellow background</b,r,y>")
parse("<b,r,>bold red text</b,r,>")
parse("<b,,y>bold regular text on a yellow background</b,,y>")
# Unrecognized tags are left as-is.
parse("<b><element1></element1></b>")For a list of markup tags, please refer to tags.py.
Custom tags or overrides for existing tags may be defined by creating a
new AnsiMarkup instance:
from ansimarkup import AnsiMarkup, parse
user_tags = {
    # Add a new tag (e.g. we want <info> to expand to "<bold><green>").
    "info": parse("<b><g>")
    # The ansi escape sequence can be used directly.
    "info": "e\x1b[32m\x1b[1m",
    # Tag names may also be callables.
    "err":  lambda: parse("<r>")
    # Colors may also be given convenient tag names.
    "orange": parse("<fg #d78700>"),
    # User-defined tags always take precedence over existing tags.
    "bold": parse("<dim>")
}
am = AnsiMarkup(tags=user_tags)
am.parse("<info>bold green</info>")
am.ansiprint("<err>red</err>")
# Calling the instance is equivalent to calling its parse method.
am("<b>bold</b>") == am.parse("<b>bold</b>")Aligning formatted strings can be challenging because the length of the rendered string is different that the number of printable characters. Consider this example:
>>> a = '| {:30} |'.format('abc')
>>> b = '| {:30} |'.format(parse('<b>abc</b>'))
>>> print(a, b, sep='\n')
| abc                    |
| abc                            |This can be addressed by using the ansistring function or the
AnsiMarkup.string(markup) method, which has the following useful
properties:
>>> s = ansistring('<b>abc</b>')
>>> print(repr(s), '->', s)
<b>abc</b> -> abc  # abc is printed in bold
>>> len(s), len(am.parse('<b>abc</b>'), s.delta
3, 11, 8With the help of the delta property, it is easy to align the strings
in the above example:
>>> s = ansistring('<b>abc</b>')
>>> a = '| {:{width}} |'.format('abc', width=30)
>>> b = '| {:{width}} |'.format(s, width=(30 + s.delta))
>>> print(a, b, sep='\n')
| abc                            |
| abc                            |Both ansiprint() and parse() pass arguments of type raw untouched.
>>> from ansimarkup import ansiprint, parse, raw
>>> ansiprint("<b><r>", raw("<l type='V'>2.0</l>"), "</r></b>")
 <l type='V'>2.0</l>  # printed in bold red (note the leading space caused)
>>> s = parse("<b><r>", raw("<l type='V'>2.0</l>"), "</r></b>")
>>> print(s)
<l type='V'>2.0</l>  # printed in bold redBuilding a template string may also be sufficient:
>>> from ansimarkup import parse
>>> s = parse("<b><r>%s</r></b>")
>>> print(s % "<l type='V'>2.0</l>")
<l type='V'>2.0</l>  # printed in bold redThe default tag separators can be changed by passing the tag_sep
argument to AnsiMarkup:
from ansimarkup import AnsiMarkup
am = AnsiMarkup(tag_sep="{}")
am.parse("{b}{r}bold red{/b}{/r}")Markup tags can be removed using the strip() method:
from ansimarkup import AnsiMarkup
am = AnsiMarkup()
am.strip("<b><r>bold red</b></r>")The strict option instructs the parser to raise MismatchedTag if
opening tags don't have corresponding closing tags:
from ansimarkup import AnsiMarkup
am = AnsiMarkup(strict=True)
am.parse("<r><b>bold red")
# ansimarkup.MismatchedTag: opening tag "<r>" has no corresponding closing tagAnsimarkup may also be used on the command-line. This works as if all
arguments were passed to ansiprint():
$ python -m ansimarkup
Usage: python -m ansimarkup [<arg> [<arg> ...]]
Example usage:
  python -m ansimarkup '<b>Bold</b>' '<r>Red</r>'
  python -m ansimarkup '<b><r>Bold Red</r></b>'
  python -m ansimarkup < input-with-markup.txt
  echo '<b>Bold</b>' | python -m ansimarkup
Ansimarkup also comes with a formatter for the standard library logging module. It can be used as:
import logging
from ansimarkup.logformatter import AnsiMarkupFormatter
log = logging.getLogger()
hdl = logging.StreamHandler()
fmt = AnsiMarkupFormatter()
hdl.setFormatter(fmt)
log.addHandler(hdl)
log.info("<b>bold text</b>")Ansimarkup uses the colorama library internally, which means that Windows support for ansi escape sequences is available by first running:
import colorama
colorama.init()For more information on Windows support, consult the "Usage" section of the colorama documentation.
While the focus of ansimarkup is convenience, it does try to keep processing to a minimum. The benchmark.py script attempts to benchmark different ansi escape code libraries:
Benchmark 1: <r><b>red bold</b></r>
  colorama     0.1959 μs
  colr         1.8022 μs
  ansimarkup   3.1681 μs
  termcolor    5.3734 μs
  rich         9.0673 μs
  pastel       10.7440 μs
  plumbum      14.0620 μs
Benchmark 2: <r><b>red bold</b>red</r><b>bold</b>
  colorama     0.5360 μs
  colr         4.5575 μs
  ansimarkup   4.5727 μs
  termcolor    15.8462 μs
  rich         21.2631 μs
  pastel       22.9391 μs
  plumbum      33.1179 μs
Ansimarkup is a simple wrapper around colorama. It does very little in the way of validating that markup strings are well-formed. This is a conscious decision with the goal of keeping things simple and fast.
Unbalanced nesting, such as in the following example, will produce incorrect output:
<r><Y>1</r>2</Y>
- Many corner cases remain to be fixed.
 - More elaborate testing. The current test suite mostly covers the "happy paths".
 - Replace 
tag_list.indexinsub_endwith something more efficient (i.e. something like an ordered MultiDict). 
- pastel: bring colors to your terminal
 - plumbum.colors: small yet feature-rich library for shell script-like programs in Python
 - colr: easy terminal colors, with chainable methods
 - rich: rich text and beautiful formatting in the terminal (see 
rich.print()andrich.markup.render()) 
Ansimarkup is released under the terms of the Revised BSD License.