Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/gvalkov/python-ansimarkup

Produce colored terminal text with an xml-like markup
https://github.com/gvalkov/python-ansimarkup

ansi-colors ansi-escape-codes

Last synced: 3 days ago
JSON representation

Produce colored terminal text with an xml-like markup

Awesome Lists containing this project

README

        

# Ansimarkup


pypi version
Build status
License

Ansimarkup is an XML-like markup for producing colored terminal text.

``` python
from ansimarkup import ansiprint as print

print("bold text"))
print("red text", "red text on a green background")
print("orange text")
```

## Installation

The latest stable version of ansimarkup can be installed from PyPi:

``` bash
python3 -m pip install ansimarkup
```

## Usage

### Basic

``` python
from ansimarkup import parse, ansiprint

# parse() converts the tags to the corresponding ansi escape sequence.
parse("bold dim")

# ansiprint() works exactly like print(), but first runs parse() on all arguments.
ansiprint("bold", "dim")
ansiprint("bold", "dim", sep=":", file=sys.stderr)
```

### Colors and styles

``` python
# Colors may be specified in one of several ways.
parse("red foreground")
parse("red background")
parse("red foreground")
parse("red background")

# Xterm, hex and rgb colors are accepted by the and tags.
parse("aquamarine foreground")
parse("dark blue background")
parse("dark green foreground")

# Tags may be nested.
parse("red text on a yellow foreground")

# The above may be more concisely written as:
parse("red text on a yellow background")

# This shorthand also supports style tags.
parse("bold red text on a yellow background")
parse("bold red text")
parse("bold regular text on a yellow background")

# Unrecognized tags are left as-is.
parse("")
```

For a list of markup tags, please refer to [tags.py].

### User-defined tags

Custom tags or overrides for existing tags may be defined by creating a
new `AnsiMarkup` instance:

``` python
from ansimarkup import AnsiMarkup, parse

user_tags = {
# Add a new tag (e.g. we want to expand to "").
"info": parse("")

# The ansi escape sequence can be used directly.
"info": "e\x1b[32m\x1b[1m",

# Tag names may also be callables.
"err": lambda: parse("")

# Colors may also be given convenient tag names.
"orange": parse(""),

# User-defined tags always take precedence over existing tags.
"bold": parse("")
}

am = AnsiMarkup(tags=user_tags)

am.parse("bold green")
am.ansiprint("red")

# Calling the instance is equivalent to calling its parse method.
am("bold") == am.parse("bold")
```

### Alignment and length

Aligning formatted strings can be challenging because the length of the
rendered string is different that the number of printable characters.
Consider this example:

``` pycon
>>> a = '| {:30} |'.format('abc')
>>> b = '| {:30} |'.format(parse('abc'))
>>> 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:

``` pycon
>>> s = ansistring('abc')
>>> print(repr(s), '->', s)
abc -> abc # abc is printed in bold
>>> len(s), len(am.parse('abc'), s.delta
3, 11, 8
```

With the help of the `delta` property, it is easy to align the strings
in the above example:

``` pycon
>>> s = ansistring('abc')
>>> a = '| {:{width}} |'.format('abc', width=30)
>>> b = '| {:{width}} |'.format(s, width=(30 + s.delta))
>>> print(a, b, sep='\n')
| abc |
| abc |
```

### Escaping raw strings

Both `ansiprint()` and `parse()` pass arguments of type `raw` untouched.

``` pycon
>>> from ansimarkup import ansiprint, parse, raw
>>> ansiprint("", raw("2.0"), "")
2.0 # printed in bold red (note the leading space caused)

>>> s = parse("", raw("2.0"), "")
>>> print(s)
2.0 # printed in bold red
```

Building a template string may also be sufficient:

``` pycon
>>> from ansimarkup import parse
>>> s = parse("%s")
>>> print(s % "2.0")
2.0 # printed in bold red
```

### Other features

The default tag separators can be changed by passing the `tag_sep`
argument to `AnsiMarkup`:

``` python
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:

``` python
from ansimarkup import AnsiMarkup

am = AnsiMarkup()
am.strip("bold red")
```

The `strict` option instructs the parser to raise `MismatchedTag` if
opening tags don\'t have corresponding closing tags:

``` python
from ansimarkup import AnsiMarkup

am = AnsiMarkup(strict=True)
am.parse("bold red")
# ansimarkup.MismatchedTag: opening tag "" has no corresponding closing tag
```

### Command-line

Ansimarkup 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 [ [ ...]]

Example usage:
python -m ansimarkup 'Bold' 'Red'
python -m ansimarkup 'Bold Red'
python -m ansimarkup < input-with-markup.txt
echo 'Bold' | python -m ansimarkup

### Logging formatter

Ansimarkup also comes with a formatter for the standard library `logging` module. It can be used as:

``` python
import logging
from ansimarkup.logformatter import AnsiMarkupFormatter

log = logging.getLogger()
hdl = logging.StreamHandler()
fmt = AnsiMarkupFormatter()
hdl.setFormatter(fmt)
log.addHandler(hdl)

log.info("bold text")
```

### Windows

Ansimarkup uses the [colorama] library internally, which means that
Windows support for ansi escape sequences is available by first running:

``` python
import colorama
colorama.init()
```

For more information on Windows support, consult the \"Usage\" section
of the [colorama] documentation.

## Performance

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: red bold
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: red boldredbold
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

## Limitations

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:

12

## Todo

- Many corner cases remain to be fixed.
- More elaborate testing. The current test suite mostly covers the \"happy paths\".
- Replace `tag_list.index` in `sub_end` with something more efficient (i.e. something like an ordered MultiDict).

## Similar libraries

- [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()` and `rich.markup.render()`)

## License

Ansimarkup is released under the terms of the [Revised BSD License].

[tags.py]: https://github.com/gvalkov/python-ansimarkup/blob/main/ansimarkup/tags.py
[colorama]: https://pypi.python.org/pypi/colorama
[benchmark.py]: https://github.com/gvalkov/python-ansimarkup/blob/main/tests/benchmark.py
[pastel]: https://github.com/sdispater/pastel
[plumbum.colors]: https://plumbum.readthedocs.io/en/latest/cli.html#colors
[colr]: https://pypi.python.org/pypi/Colr/
[rich]: https://github.com/Textualize/rich
[Revised BSD License]: https://github.com/gvalkov/python-ansimarkup/blob/main/LICENSE.txt