From eaef4203a342c3516383f1c5b135b9a77cf8b2f0 Mon Sep 17 00:00:00 2001 From: Peter Odding Date: Wed, 14 Oct 2015 21:52:01 +0200 Subject: [PATCH] Change `ansi2html' to `coloredlogs --convert' (fixes #8) See issue 8 on GitHub (request to change the name): https://github.com/xolox/python-coloredlogs/issues/8 --- .coveragerc | 5 +- README.rst | 22 +++++---- coloredlogs/__init__.py | 2 +- coloredlogs/cli.py | 102 +++++++++++++++++++++++++++++++++++++++ coloredlogs/converter.py | 21 -------- coloredlogs/demo.py | 18 +++---- coloredlogs/tests.py | 49 +++++++++++++++++-- setup.py | 5 +- tox.ini | 4 ++ 9 files changed, 180 insertions(+), 48 deletions(-) create mode 100644 coloredlogs/cli.py diff --git a/.coveragerc b/.coveragerc index c42654c..690833e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,6 @@ [run] source = coloredlogs -omit = coloredlogs/demo.py, coloredlogs/tests.py +omit = coloredlogs/tests.py + +[report] +exclude_lines = assert False diff --git a/README.rst b/README.rst index af36a3c..c95f73c 100644 --- a/README.rst +++ b/README.rst @@ -92,19 +92,20 @@ e-mailed to you by cron there's a simple way to set it up:: MAILTO="your-email-address@here" CONTENT_TYPE="text/html" - * * * * * root ansi2html your-command + * * * * * root coloredlogs --to-html your-command -The ``ansi2html`` program is installed when you install `coloredlogs`. It runs +The ``coloredlogs`` program is installed when you install the `coloredlogs` +package. When you execute ``coloredlogs --to-html your-command`` it runs ``your-command`` under the external program ``script`` (you need to have this -installed to get ``ansi2html`` working). This makes ``your-command`` think that -it's attached to an interactive terminal which means it will output ANSI escape -sequences and ``ansi2html`` converts these to HTML. Yes, this is a bit -convoluted, but it works great :-) +installed). This makes ``your-command`` think that it's attached to an +interactive terminal which means it will output ANSI escape sequences which +will then be converted to HTML by the ``coloredlogs`` program. Yes, this is a +bit convoluted, but it works great :-) -You can use ``ansi2html`` without `coloredlogs`, but please note that it only -supports normal text, bold text and text with one of the foreground colors -black, red, green, yellow, blue, magenta, cyan and white (these are the -portable ANSI color codes). +You can use this feature without using `coloredlogs` in your Python modules, +but please note that only normal text, bold text and text with one of the +foreground colors black, red, green, yellow, blue, magenta, cyan and white +(these are the portable ANSI color codes) are supported. Contact ------- @@ -121,6 +122,7 @@ This software is licensed under the `MIT license`_. © 2015 Peter Odding. + .. External references: .. _ANSI escape sequences: http://en.wikipedia.org/wiki/ANSI_escape_code#Colors .. _cron: https://en.wikipedia.org/wiki/Cron diff --git a/coloredlogs/__init__.py b/coloredlogs/__init__.py index 4d6e038..cde91fe 100644 --- a/coloredlogs/__init__.py +++ b/coloredlogs/__init__.py @@ -7,7 +7,7 @@ """Colored terminal output for Python's :mod:`logging` module.""" # Semi-standard module versioning. -__version__ = '1.0.1' +__version__ = '2.0' # Standard library modules. import copy diff --git a/coloredlogs/cli.py b/coloredlogs/cli.py new file mode 100644 index 0000000..ee361ab --- /dev/null +++ b/coloredlogs/cli.py @@ -0,0 +1,102 @@ +# Command line interface for the coloredlogs package. +# +# Author: Peter Odding +# Last Change: October 14, 2015 +# URL: https://coloredlogs.readthedocs.org + +""" +Usage: coloredlogs [OPTIONS] [ARGS] + +The coloredlogs program provides a simple command line interface for the Python +package by the same name. + +Supported options: + + -c, --convert, --to-html + + Capture the output of an external command (given by the positional + arguments) and convert ANSI escape sequences in the output to HTML. + + If the `coloredlogs' program is attached to an interactive terminal it will + write the generated HTML to a temporary file and open that file in a web + browser, otherwise the generated HTML will be written to standard output. + + This requires the `script' program to fake the external command into + thinking that it's attached to an interactive terminal (in order to enable + output of ANSI escape sequences). + + -d, --demo + + Perform a simple demonstration of the coloredlogs package to show the + colored logging on an interactive terminal. + + -h, --help + + Show this message and exit. +""" + +# Standard library modules. +import functools +import getopt +import logging +import sys +import tempfile +import webbrowser + +# External dependencies. +from humanfriendly.terminal import connected_to_terminal, usage + +# Modules included in our package. +from coloredlogs.converter import capture, convert +from coloredlogs.demo import demonstrate_colored_logging + +# Initialize a logger for this module. +logger = logging.getLogger(__name__) + + +def main(): + """Command line interface for the `coloredlogs` program.""" + actions = [] + try: + # Parse the command line arguments. + options, arguments = getopt.getopt(sys.argv[1:], 'cdh', [ + 'convert', 'to-html', 'demo', 'help', + ]) + # Map command line options to actions. + for option, value in options: + if option in ('-c', '--convert', '--to-html'): + actions.append(functools.partial(convert_command_output, *arguments)) + arguments = [] + elif option in ('-d', '--demo'): + actions.append(demonstrate_colored_logging) + elif option in ('-h', '--help'): + usage(__doc__) + return + else: + assert False, "Programming error: Unhandled option!" + if not actions: + usage(__doc__) + return + except Exception as e: + sys.stderr.write("Error: %s\n" % e) + sys.exit(1) + for function in actions: + function() + + +def convert_command_output(*command): + """ + Command line interface for ``coloredlogs --to-html``. + + Takes a command (and its arguments) and runs the program under ``script`` + (emulating an interactive terminal), intercepts the output of the command + and converts ANSI escape sequences in the output to HTML. + """ + html_output = convert(capture(command)) + if connected_to_terminal(): + fd, temporary_file = tempfile.mkstemp(suffix='.html') + with open(temporary_file, 'w') as handle: + handle.write(html_output) + webbrowser.open(temporary_file) + else: + print(html_output) diff --git a/coloredlogs/converter.py b/coloredlogs/converter.py index 86043db..73adfc0 100644 --- a/coloredlogs/converter.py +++ b/coloredlogs/converter.py @@ -10,9 +10,6 @@ import pipes import re import subprocess -import sys -import tempfile -import webbrowser # Portable color codes from http://en.wikipedia.org/wiki/ANSI_escape_code#Colors. EIGHT_COLOR_PALETTE = ( @@ -32,24 +29,6 @@ token_pattern = re.compile('(https?://\\S+|www\\.\\S+|\x1b\\[.*?m)', re.UNICODE) -def main(): - """ - Command line interface for the ``ansi2html`` program. - - Takes a command (and its arguments) and runs the program under ``script`` - (emulating an interactive terminal), intercepts the output of the command - and converts ANSI escape sequences in the output to HTML. - """ - html_output = convert(capture(sys.argv[1:])) - if sys.stdout.isatty(): - fd, filename = tempfile.mkstemp(suffix='.html') - with open(filename, 'w') as handle: - handle.write(html_output) - webbrowser.open(filename) - else: - print(html_output) - - def capture(command, encoding='UTF-8'): """ Capture the output of an external program as if it runs in an interactive terminal. diff --git a/coloredlogs/demo.py b/coloredlogs/demo.py index f2a1c47..a7bfd62 100644 --- a/coloredlogs/demo.py +++ b/coloredlogs/demo.py @@ -15,18 +15,19 @@ # If my verbose logger is installed, we'll use that for the demo. try: - from verboselogs import VerboseLogger as DemoLogger + from verboselogs import VerboseLogger as getLogger except ImportError: - from logging import getLogger as DemoLogger + from logging import getLogger -# Initialize the logger and handler. -logger = DemoLogger('coloredlogs') +# Initialize a logger for this module. +logger = getLogger(__name__) -def main(): - """Command line interface for the `coloredlogs` demonstration.""" +def demonstrate_colored_logging(): + """A simple demonstration of the `coloredlogs` package.""" # Initialize colored output to the terminal. - coloredlogs.install(level=logging.DEBUG) + coloredlogs.install() + coloredlogs.set_level(logging.DEBUG) # Print some examples with different timestamps. for level in ['debug', 'verbose', 'info', 'warn', 'error', 'critical']: if hasattr(logger, level): @@ -40,6 +41,3 @@ class RandomException(Exception): except Exception as e: logger.exception(e) logger.info("Done, exiting ..") - -if __name__ == '__main__': - main() diff --git a/coloredlogs/tests.py b/coloredlogs/tests.py index b23ef5b..2010b12 100644 --- a/coloredlogs/tests.py +++ b/coloredlogs/tests.py @@ -11,6 +11,7 @@ import random import re import string +import sys import unittest # External dependencies. @@ -18,10 +19,12 @@ # The module we're testing. import coloredlogs +import coloredlogs.cli import coloredlogs.converter -# External test dependency required to test support for custom log levels. -import verboselogs +# External test dependencies. +from capturer import CaptureOutput +from verboselogs import VerboseLogger # Compatibility with Python 2 and 3. try: @@ -57,7 +60,7 @@ def setUp(self): self.stream = StringIO() self.handler = coloredlogs.ColoredStreamHandler(stream=self.stream, isatty=False) self.logger_name = ''.join(random.choice(string.ascii_letters) for i in range(25)) - self.logger = verboselogs.VerboseLogger(self.logger_name) + self.logger = VerboseLogger(self.logger_name) self.logger.addHandler(self.handler) def test_is_verbose(self): @@ -147,3 +150,43 @@ def test_output_interception(self): """Test capturing of output from external commands.""" expected_output = 'testing, 1, 2, 3 ..' assert coloredlogs.converter.capture(['sh', '-c', 'echo -n %s' % expected_output]) == expected_output + + def test_cli_demo(self): + """Test the command line colored logging demonstration.""" + with CaptureOutput() as capturer: + main('coloredlogs', '--demo') + output = capturer.get_text() + # Make sure the output contains all of the expected logging level names. + for name in 'debug', 'info', 'warning', 'error', 'critical': + assert name.upper() in output + + def test_cli_conversion(self): + """Test the command line HTML conversion.""" + output = main('coloredlogs', '--convert', 'coloredlogs', '--demo', capture=True) + # Make sure the output is encoded as HTML. + assert '= 1.25.1', ], test_suite='coloredlogs.tests', tests_require=[ + 'capturer', 'verboselogs', ], classifiers=[ diff --git a/tox.ini b/tox.ini index 260fe82..b5d4188 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ envlist = py26, py27, py34, pypy [testenv] deps = + capturer pytest verboselogs commands = py.test @@ -15,3 +16,6 @@ commands = py.test [flake8] exclude = .tox max-line-length = 120 + +[pep257] +ignore = D400