ansimarkup-1.4.0/0000755000175000017500000000000013542427016014054 5ustar alastairalastairansimarkup-1.4.0/PKG-INFO0000644000175000017500000002700413542427012015150 0ustar alastairalastairMetadata-Version: 1.1 Name: ansimarkup Version: 1.4.0 Summary: Produce colored terminal text with an xml-like markup Home-page: https://github.com/gvalkov/python-ansimarkup Author: Georgi Valkov Author-email: georgi.t.valkov@gmail.com License: Revised BSD License Description-Content-Type: UNKNOWN Description: Ansimarkup ========== .. class:: no-web no-pdf |pypi| |build| |license| Ansimarkup is an XML-like markup for producing colored terminal text. .. code-block:: 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: .. code-block:: bash $ pip install ansimarkup Usage ----- Basic ~~~~~ .. code-block:: 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 ~~~~~~~~~~~~~~~~~ .. code-block:: 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: .. code-block:: 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> s = ansistring('abc') >>> a = '| {:{width}} |'.format('abc', width=30) >>> b = '| {:{width}} |'.format(s, width=(30 + s.delta)) >>> print(a, b, sep='\n') | abc | | abc | Other features ~~~~~~~~~~~~~~ The default tag separators can be changed by passing the ``tag_sep`` argument to ``AnsiMarkup``: .. code-block:: 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: .. code-block:: 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: .. code-block:: 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 "bold" "red" Logging formatter ~~~~~~~~~~~~~~~~~ Ansimarkup also comes with a formatter for the standard library `logging` module. It can be used as: .. code-block:: 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: .. code-block:: 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.2998 μs termcolor 3.2339 μs colr 3.6483 μs ansimarkup 6.8679 μs pastel 28.8538 μs plumbum 53.5004 μs Benchmark 2: red boldredbold colorama 0.8269 μs termcolor 8.9296 μs ansimarkup 9.3099 μs colr 9.6244 μs pastel 62.2018 μs plumbum 120.8048 μ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 License ------- Ansimarkup is released under the terms of the `Revised BSD License`_. .. |pypi| image:: https://img.shields.io/pypi/v/ansimarkup.svg?style=flat-square&label=latest%20stable%20version :target: https://pypi.python.org/pypi/ansimarkup :alt: Latest version released on PyPi .. |license| image:: https://img.shields.io/pypi/l/ansimarkup.svg?style=flat-square&label=license :target: https://pypi.python.org/pypi/ansimarkup :alt: BSD 3-Clause .. |build| image:: https://img.shields.io/travis/gvalkov/python-ansimarkup/master.svg?style=flat-square&label=build :target: http://travis-ci.org/gvalkov/python-ansimarkup :alt: Build status .. _tags.py: https://github.com/gvalkov/python-ansimarkup/blob/master/ansimarkup/tags.py .. _benchmark.py: https://github.com/gvalkov/python-ansimarkup/blob/master/tests/benchmark.py .. _colorama: https://pypi.python.org/pypi/colorama .. _pastel: https://github.com/sdispater/pastel .. _plumbum.colors: https://plumbum.readthedocs.io/en/latest/cli.html#colors .. _colr: https://pypi.python.org/pypi/Colr/ .. _`Revised BSD License`: https://raw.github.com/gvalkov/python-ansimarkup/master/LICENSE Keywords: ansi terminal markup Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries Classifier: License :: OSI Approved :: BSD License ansimarkup-1.4.0/MANIFEST.in0000644000175000017500000000001713542427012015604 0ustar alastairalastairinclude LICENSEansimarkup-1.4.0/ansimarkup.egg-info/0000755000175000017500000000000013542427012017714 5ustar alastairalastairansimarkup-1.4.0/ansimarkup.egg-info/PKG-INFO0000644000175000017500000002700413542427012021014 0ustar alastairalastairMetadata-Version: 1.1 Name: ansimarkup Version: 1.4.0 Summary: Produce colored terminal text with an xml-like markup Home-page: https://github.com/gvalkov/python-ansimarkup Author: Georgi Valkov Author-email: georgi.t.valkov@gmail.com License: Revised BSD License Description-Content-Type: UNKNOWN Description: Ansimarkup ========== .. class:: no-web no-pdf |pypi| |build| |license| Ansimarkup is an XML-like markup for producing colored terminal text. .. code-block:: 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: .. code-block:: bash $ pip install ansimarkup Usage ----- Basic ~~~~~ .. code-block:: 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 ~~~~~~~~~~~~~~~~~ .. code-block:: 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: .. code-block:: 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> s = ansistring('abc') >>> a = '| {:{width}} |'.format('abc', width=30) >>> b = '| {:{width}} |'.format(s, width=(30 + s.delta)) >>> print(a, b, sep='\n') | abc | | abc | Other features ~~~~~~~~~~~~~~ The default tag separators can be changed by passing the ``tag_sep`` argument to ``AnsiMarkup``: .. code-block:: 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: .. code-block:: 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: .. code-block:: 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 "bold" "red" Logging formatter ~~~~~~~~~~~~~~~~~ Ansimarkup also comes with a formatter for the standard library `logging` module. It can be used as: .. code-block:: 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: .. code-block:: 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.2998 μs termcolor 3.2339 μs colr 3.6483 μs ansimarkup 6.8679 μs pastel 28.8538 μs plumbum 53.5004 μs Benchmark 2: red boldredbold colorama 0.8269 μs termcolor 8.9296 μs ansimarkup 9.3099 μs colr 9.6244 μs pastel 62.2018 μs plumbum 120.8048 μ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 License ------- Ansimarkup is released under the terms of the `Revised BSD License`_. .. |pypi| image:: https://img.shields.io/pypi/v/ansimarkup.svg?style=flat-square&label=latest%20stable%20version :target: https://pypi.python.org/pypi/ansimarkup :alt: Latest version released on PyPi .. |license| image:: https://img.shields.io/pypi/l/ansimarkup.svg?style=flat-square&label=license :target: https://pypi.python.org/pypi/ansimarkup :alt: BSD 3-Clause .. |build| image:: https://img.shields.io/travis/gvalkov/python-ansimarkup/master.svg?style=flat-square&label=build :target: http://travis-ci.org/gvalkov/python-ansimarkup :alt: Build status .. _tags.py: https://github.com/gvalkov/python-ansimarkup/blob/master/ansimarkup/tags.py .. _benchmark.py: https://github.com/gvalkov/python-ansimarkup/blob/master/tests/benchmark.py .. _colorama: https://pypi.python.org/pypi/colorama .. _pastel: https://github.com/sdispater/pastel .. _plumbum.colors: https://plumbum.readthedocs.io/en/latest/cli.html#colors .. _colr: https://pypi.python.org/pypi/Colr/ .. _`Revised BSD License`: https://raw.github.com/gvalkov/python-ansimarkup/master/LICENSE Keywords: ansi terminal markup Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Intended Audience :: Developers Classifier: Topic :: Software Development :: Libraries Classifier: License :: OSI Approved :: BSD License ansimarkup-1.4.0/ansimarkup.egg-info/zip-safe0000644000175000017500000000000113542427012021344 0ustar alastairalastair ansimarkup-1.4.0/ansimarkup.egg-info/top_level.txt0000644000175000017500000000001313542427012022440 0ustar alastairalastairansimarkup ansimarkup-1.4.0/ansimarkup.egg-info/dependency_links.txt0000644000175000017500000000000113542427012023762 0ustar alastairalastair ansimarkup-1.4.0/ansimarkup.egg-info/SOURCES.txt0000644000175000017500000000062013542427012021576 0ustar alastairalastairLICENSE MANIFEST.in README.rst setup.cfg setup.py ansimarkup/__init__.py ansimarkup/__main__.py ansimarkup/ansi.py ansimarkup/compat.py ansimarkup/logformatter.py ansimarkup/markup.py ansimarkup/tags.py ansimarkup.egg-info/PKG-INFO ansimarkup.egg-info/SOURCES.txt ansimarkup.egg-info/dependency_links.txt ansimarkup.egg-info/requires.txt ansimarkup.egg-info/top_level.txt ansimarkup.egg-info/zip-safeansimarkup-1.4.0/ansimarkup.egg-info/requires.txt0000644000175000017500000000022713542427012022315 0ustar alastairalastaircolorama [devel] bumpversion>=0.5.2 check-manifest>=0.35 readme-renderer>=16.0 flake8 pep8-naming [tests] tox>=2.6.0 pytest>=3.0.3 pytest-cov>=2.3.1 ansimarkup-1.4.0/setup.cfg0000644000175000017500000000044113542427012015670 0ustar alastairalastair[bumpversion] current_version = 1.4.0 message = Bump version: {current_version} -> {new_version} commit = True tag = True [wheel] universal = 1 [flake8] ignore = W191,E302,E265,E241,F403,E401,E221 max-line-length = 120 [bumpversion:file:setup.py] [egg_info] tag_build = tag_date = 0 ansimarkup-1.4.0/setup.py0000644000175000017500000000311613542427012015563 0ustar alastairalastairfrom setuptools import setup, find_packages classifiers = [ 'Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', 'License :: OSI Approved :: BSD License', ] install_requires = [ 'colorama', ] extras_require = { 'tests': [ 'tox >= 2.6.0', 'pytest >= 3.0.3', 'pytest-cov >= 2.3.1', ], 'devel': [ 'bumpversion >= 0.5.2', 'check-manifest >= 0.35', 'readme-renderer >= 16.0', 'flake8', 'pep8-naming', ] } kw = { 'name': 'ansimarkup', 'version': '1.4.0', 'description': 'Produce colored terminal text with an xml-like markup', 'long_description': open('README.rst').read(), 'author': 'Georgi Valkov', 'author_email': 'georgi.t.valkov@gmail.com', 'license': 'Revised BSD License', 'keywords': 'ansi terminal markup', 'url': 'https://github.com/gvalkov/python-ansimarkup', 'classifiers': classifiers, 'install_requires': install_requires, 'extras_require': extras_require, 'packages': find_packages(), 'zip_safe': True, } if __name__ == '__main__': setup(**kw) ansimarkup-1.4.0/LICENSE0000644000175000017500000000275213542427012015063 0ustar alastairalastairBSD 3-Clause License Copyright (c) 2017, Georgi Valkov. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ansimarkup-1.4.0/ansimarkup/0000755000175000017500000000000013542427012016222 5ustar alastairalastairansimarkup-1.4.0/ansimarkup/logformatter.py0000644000175000017500000000075313542427012021306 0ustar alastairalastairimport sys import logging from . import markup class AnsiMarkupFormatter(logging.Formatter): def __init__(self, *args, **kwargs): self.ansimarkup = markup.AnsiMarkup() super(AnsiMarkupFormatter, self).__init__(*args) def format(self, record): message = super(AnsiMarkupFormatter, self).format(record) message = self.ansimarkup.parse(message) return message # TODO: Add stream handler that strips tags when the output stream is not a tty. ansimarkup-1.4.0/ansimarkup/compat.py0000644000175000017500000000015713542427012020062 0ustar alastairalastairtry: import builtins except ImportError: import __builtin__ as builtins # noqa __all__ = 'builtins' ansimarkup-1.4.0/ansimarkup/ansi.py0000644000175000017500000000040313542427012017523 0ustar alastairalastairfrom colorama.ansi import AnsiCodes from colorama import Fore, Back, Style class AnsiExtendedStyle(AnsiCodes): ITALIC = 3 UNDERLINE = 4 BLINK = 5 REVERSE = 7 STRIKE = 8 HIDE = 9 ExtendedStyle = AnsiExtendedStyle() ansimarkup-1.4.0/ansimarkup/markup.py0000644000175000017500000001670613542427012020105 0ustar alastairalastairfrom __future__ import print_function import re from colorama import Style from .tags import style, background, foreground, all_tags from .compat import builtins class AnsiMarkupError(Exception): pass class MismatchedTag(AnsiMarkupError): pass class UnbalancedTag(AnsiMarkupError): pass class AnsiMarkup: ''' Produce colored terminal text with a tag-based markup. ''' def __init__(self, tags=None, always_reset=False, strict=False, tag_sep='<>', ansistring_cls=None): ''' Arguments --------- tags: dict User-supplied tags, which are a mapping of tag names to the strings they will be substituted with. always_reset: bool Whether or not ``parse()`` should always end strings with a reset code. strict: bool Whether or not ``parse()`` should raise an error for missing closing tags. tag_sep: str The opening and closing characters of each tag (e.g. ``<>``, ``{}``). ansistring_cls: type Class to use in ``ansistring()`` method. ''' self.user_tags = tags if tags else {} self.always_reset = always_reset self.strict = strict self.tag_sep = tag_sep self.ansistring_cls = ansistring_cls if ansistring_cls else AnsiMarkupString self.re_tag = self.compile_tag_regex(tag_sep) def parse(self, text): '''Return a string with markup tags converted to ansi-escape sequences.''' tags, results = [], [] text = self.re_tag.sub(lambda m: self.sub_tag(m, tags, results), text) if self.strict and tags: markup = "%s%s%s" % (self.tag_sep[0], tags.pop(0), self.tag_sep[1]) raise MismatchedTag('opening tag "%s" has no corresponding closing tag' % markup) if self.always_reset: if not text.endswith(Style.RESET_ALL): text += Style.RESET_ALL return text def ansiprint(self, *args, **kwargs): '''Wrapper around builtins.print() that runs parse() on all arguments first.''' args = (self.parse(str(i)) for i in args) builtins.print(*args, **kwargs) def strip(self, text): '''Return string with markup tags removed.''' tags, results = [], [] return self.re_tag.sub(lambda m: self.clear_tag(m, tags, results), text) def ansistring(self, markup): return self.ansistring_cls(self, markup) def __call__(self, text): return self.parse(text) def sub_tag(self, match, tag_list, res_list): markup, tag = match.group(0), match.group(1) closing = markup[1] == '/' res = None # Early exit if the closing tag matches the last known opening tag. if closing and tag_list and tag_list[-1] == tag: tag_list.pop() res_list.pop() return Style.RESET_ALL + ''.join(res_list) # User-defined tags take preference over all other. if tag in self.user_tags: utag = self.user_tags[tag] res = utag() if callable(utag) else utag # Substitute on a direct match. elif tag in all_tags: res = all_tags[tag] # An alternative syntax for setting the color (e.g. , ). elif tag.startswith('fg ') or tag.startswith('bg '): st, color = tag[:2], tag[3:] code = '38' if st == 'fg' else '48' if st == 'fg' and color in foreground: res = foreground[color] elif st == 'bg' and color.islower() and color.upper() in background: res = background[color.upper()] elif color.isdigit() and int(color) <= 255: res = '\033[%s;5;%sm' % (code, color) elif re.match(r'#(?:[a-fA-F0-9]{3}){1,2}$', color): hex_color = color[1:] if len(hex_color) == 3: hex_color *= 2 res = '\033[%s;2;%s;%s;%sm' % ((code,) + hex_to_rgb(hex_color)) elif color.count(',') == 2: colors = tuple(color.split(',')) if all(x.isdigit() and int(x) <= 255 for x in colors): res = '\033[%s;2;%s;%s;%sm' % ((code,) + colors) # Shorthand formats (e.g. , ). elif ',' in tag: el_count = tag.count(',') if el_count == 1: fg, bg = tag.split(',') if fg in foreground and bg.islower() and bg.upper() in background: res = foreground[fg] + background[bg.upper()] elif el_count == 2: st, fg, bg = tag.split(',') if st in style and (fg != '' or bg != ''): if fg == '' or fg in foreground: if bg == '' or (bg.islower() and bg.upper() in background): res = style[st] + foreground.get(fg, '') + background.get(bg.upper(), '') # If nothing matches, return the full tag (i.e. text). if res is None: return markup # If closing tag is known, but did not early exit, something is wrong. if closing: if tag in tag_list: raise UnbalancedTag('closing tag "%s" violates nesting rules.' % markup) else: raise MismatchedTag('closing tag "%s" has no corresponding opening tag' % markup) tag_list.append(tag) res_list.append(res) return res def clear_tag(self, match, tag_list, res_list): pre_length = len(tag_list) try: self.sub_tag(match, tag_list, res_list) # If list did not change, the tag is unknown if len(tag_list) == pre_length: return match.group(0) # Otherwise, tag matched so remove it else: return '' # Tag matched but is invalid, remove it anyway except (UnbalancedTag, MismatchedTag): return '' def compile_tag_regex(self, tag_sep): # Optimize the default: if tag_sep == '<>': tag_regex = re.compile(r']+)>') return tag_regex if len(tag_sep) != 2: raise ValueError('tag_sep needs to have exactly two elements (e.g. "<>")') if tag_sep[0] == tag_sep[1]: raise ValueError('opening and closing characters cannot be the same') tag_regex = r'{0}/?([^{0}{1}]+){1}'.format(tag_sep[0], tag_sep[1]) return re.compile(tag_regex) class AnsiMarkupString(str): ''' A string containing the original markup, the formatted string and the string with tags stripped off. Example usage:: >>> t = AnsiMarkup().string('abc') >>> repr(t) abc >>> print(t) abc # in bold >>> len(t) 3 >>> len(AnsiMarkup().parse('abc>> t.delta 8 ''' def __new__(cls, am, markup): parsed = am.parse(markup) new_str = str.__new__(cls, parsed) new_str.markup = markup new_str.stripped = am.strip(markup) # The difference in length between the formatted string and the string # with markup tags stripped off. new_str.delta = len(parsed) - len(new_str.stripped) return new_str def __len__(self): return len(self.stripped) def __repr__(self): return self.markup def hex_to_rgb(value): return tuple(int(value[i:i + 2], 16) for i in (0, 2, 4)) #return tuple(bytes.fromhex(value)) ansimarkup-1.4.0/ansimarkup/tags.py0000644000175000017500000000536413542427012017542 0ustar alastairalastairfrom .ansi import Fore, Back, Style, ExtendedStyle style = { 'b': Style.BRIGHT, 'd': Style.DIM, 'n': Style.NORMAL, '0': Style.RESET_ALL, 'h': ExtendedStyle.HIDE, 'i': ExtendedStyle.ITALIC, 'l': ExtendedStyle.BLINK, 's': ExtendedStyle.STRIKE, 'u': ExtendedStyle.UNDERLINE, 'v': ExtendedStyle.REVERSE, 'bold': Style.BRIGHT, 'dim': Style.DIM, 'normal': Style.NORMAL, 'reset': Style.RESET_ALL, 'hide': ExtendedStyle.HIDE, 'italic': ExtendedStyle.ITALIC, 'blink': ExtendedStyle.BLINK, 'strike': ExtendedStyle.STRIKE, 'underline': ExtendedStyle.UNDERLINE, 'reverse': ExtendedStyle.REVERSE, } foreground = { 'k': Fore.BLACK, 'r': Fore.RED, 'g': Fore.GREEN, 'y': Fore.YELLOW, 'e': Fore.BLUE, 'm': Fore.MAGENTA, 'c': Fore.CYAN, 'w': Fore.WHITE, 'lk': Fore.LIGHTBLACK_EX, 'lr': Fore.LIGHTRED_EX, 'lg': Fore.LIGHTGREEN_EX, 'ly': Fore.LIGHTYELLOW_EX, 'le': Fore.LIGHTBLUE_EX, 'lm': Fore.LIGHTMAGENTA_EX, 'lc': Fore.LIGHTCYAN_EX, 'lw': Fore.LIGHTWHITE_EX, 'black': Fore.BLACK, 'red': Fore.RED, 'green': Fore.GREEN, 'yellow': Fore.YELLOW, 'blue': Fore.BLUE, 'magenta': Fore.MAGENTA, 'cyan': Fore.CYAN, 'white': Fore.WHITE, 'light-black': Fore.LIGHTBLACK_EX, 'light-red': Fore.LIGHTRED_EX, 'light-green': Fore.LIGHTGREEN_EX, 'light-yellow': Fore.LIGHTYELLOW_EX, 'light-blue': Fore.LIGHTBLUE_EX, 'light-magenta': Fore.LIGHTMAGENTA_EX, 'light-cyan': Fore.LIGHTCYAN_EX, 'light-white': Fore.LIGHTWHITE_EX, } background = { 'K': Back.BLACK, 'R': Back.RED, 'G': Back.GREEN, 'Y': Back.YELLOW, 'E': Back.BLUE, 'M': Back.MAGENTA, 'C': Back.CYAN, 'W': Back.WHITE, 'LK': Back.LIGHTBLACK_EX, 'LR': Back.LIGHTRED_EX, 'LG': Back.LIGHTGREEN_EX, 'LY': Back.LIGHTYELLOW_EX, 'LE': Back.LIGHTBLUE_EX, 'LM': Back.LIGHTMAGENTA_EX, 'LC': Back.LIGHTCYAN_EX, 'LW': Back.LIGHTWHITE_EX, 'BLACK': Back.BLACK, 'RED': Back.RED, 'GREEN': Back.GREEN, 'YELLOW': Back.YELLOW, 'BLUE': Back.BLUE, 'MAGENTA': Back.MAGENTA, 'CYAN': Back.CYAN, 'WHITE': Back.WHITE, 'LIGHT-BLACK': Back.LIGHTBLACK_EX, 'LIGHT-RED': Back.LIGHTRED_EX, 'LIGHT-GREEN': Back.LIGHTGREEN_EX, 'LIGHT-YELLOW': Back.LIGHTYELLOW_EX, 'LIGHT-BLUE': Back.LIGHTBLUE_EX, 'LIGHT-MAGENTA': Back.LIGHTMAGENTA_EX, 'LIGHT-CYAN': Back.LIGHTCYAN_EX, 'LIGHT-WHITE': Back.LIGHTWHITE_EX, } all_tags = {} for i in style, foreground, background: all_tags.update(i) ansimarkup-1.4.0/ansimarkup/__init__.py0000644000175000017500000000054113542427012020333 0ustar alastairalastairfrom . markup import AnsiMarkup, AnsiMarkupError, MismatchedTag, UnbalancedTag _ansimarkup = AnsiMarkup() parse = _ansimarkup.parse strip = _ansimarkup.strip ansiprint = _ansimarkup.ansiprint ansistring = _ansimarkup.ansistring __all__ = 'AnsiMarkup', 'AnsiMarkupError', 'MismatchedTag', 'UnbalancedTag', 'parse', 'strip', 'ansiprint', 'ansistring' ansimarkup-1.4.0/ansimarkup/__main__.py0000644000175000017500000000060713542427012020317 0ustar alastairalastairfrom sys import argv, exit from . import print_function if len(argv) == 1: from textwrap import dedent usage = ''' Usage: python -m ansimarkup [ ...] Example usage: python -m ansimarkup 'Bold' 'Red' python -m ansimarkup 'Bold Red' ''' print(dedent(usage).strip()) exit(0) else: print_function(*argv[1:]) ansimarkup-1.4.0/README.rst0000644000175000017500000002027313542427012015543 0ustar alastairalastairAnsimarkup ========== .. class:: no-web no-pdf |pypi| |build| |license| Ansimarkup is an XML-like markup for producing colored terminal text. .. code-block:: 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: .. code-block:: bash $ pip install ansimarkup Usage ----- Basic ~~~~~ .. code-block:: 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 ~~~~~~~~~~~~~~~~~ .. code-block:: 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: .. code-block:: 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> s = ansistring('abc') >>> a = '| {:{width}} |'.format('abc', width=30) >>> b = '| {:{width}} |'.format(s, width=(30 + s.delta)) >>> print(a, b, sep='\n') | abc | | abc | Other features ~~~~~~~~~~~~~~ The default tag separators can be changed by passing the ``tag_sep`` argument to ``AnsiMarkup``: .. code-block:: 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: .. code-block:: 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: .. code-block:: 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 "bold" "red" Logging formatter ~~~~~~~~~~~~~~~~~ Ansimarkup also comes with a formatter for the standard library `logging` module. It can be used as: .. code-block:: 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: .. code-block:: 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.2998 μs termcolor 3.2339 μs colr 3.6483 μs ansimarkup 6.8679 μs pastel 28.8538 μs plumbum 53.5004 μs Benchmark 2: red boldredbold colorama 0.8269 μs termcolor 8.9296 μs ansimarkup 9.3099 μs colr 9.6244 μs pastel 62.2018 μs plumbum 120.8048 μ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 License ------- Ansimarkup is released under the terms of the `Revised BSD License`_. .. |pypi| image:: https://img.shields.io/pypi/v/ansimarkup.svg?style=flat-square&label=latest%20stable%20version :target: https://pypi.python.org/pypi/ansimarkup :alt: Latest version released on PyPi .. |license| image:: https://img.shields.io/pypi/l/ansimarkup.svg?style=flat-square&label=license :target: https://pypi.python.org/pypi/ansimarkup :alt: BSD 3-Clause .. |build| image:: https://img.shields.io/travis/gvalkov/python-ansimarkup/master.svg?style=flat-square&label=build :target: http://travis-ci.org/gvalkov/python-ansimarkup :alt: Build status .. _tags.py: https://github.com/gvalkov/python-ansimarkup/blob/master/ansimarkup/tags.py .. _benchmark.py: https://github.com/gvalkov/python-ansimarkup/blob/master/tests/benchmark.py .. _colorama: https://pypi.python.org/pypi/colorama .. _pastel: https://github.com/sdispater/pastel .. _plumbum.colors: https://plumbum.readthedocs.io/en/latest/cli.html#colors .. _colr: https://pypi.python.org/pypi/Colr/ .. _`Revised BSD License`: https://raw.github.com/gvalkov/python-ansimarkup/master/LICENSE