mirror of
https://github.com/ansible/awx.git
synced 2026-01-17 04:31:21 -03:30
Replace ansi2html (GPL) with ansiconv (MIT).
This commit is contained in:
parent
a63eb24cc0
commit
7f2a029532
50
awx/api/templates/api/stdout.html
Normal file
50
awx/api/templates/api/stdout.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% if content_only %}<div class="nocode ansi_fore ansi_back{% if dark %} ansi_dark{% endif %}">{% else %}
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>{{ title }}</title>
|
||||
{% endif %}<style type="text/css">
|
||||
.ansi_fore { color: #000000; }
|
||||
.ansi_back { background-color: #F5F5F5; }
|
||||
.ansi_fore.ansi_dark { color: #AAAAAA; }
|
||||
.ansi_back.ansi_dark { background-color: #000000; }
|
||||
.ansi1 { font-weight: bold; }
|
||||
.ansi3 { font-weight: italic; }
|
||||
.ansi4 { text-decoration: underline; }
|
||||
.ansi9 { text-decoration: line-through; }
|
||||
.ansi30 { color: #000316; }
|
||||
.ansi31 { color: #AA0000; }
|
||||
.ansi32 { color: #00AA00; }
|
||||
.ansi33 { color: #AA5500; }
|
||||
.ansi34 { color: #0000AA; }
|
||||
.ansi35 { color: #E850A8; }
|
||||
.ansi36 { color: #00AAAA; }
|
||||
.ansi37 { color: #F5F1DE; }
|
||||
.ansi40 { background-color: #000000; }
|
||||
.ansi41 { background-color: #AA0000; }
|
||||
.ansi42 { background-color: #00AA00; }
|
||||
.ansi43 { background-color: #AA5500; }
|
||||
.ansi44 { background-color: #0000AA; }
|
||||
.ansi45 { background-color: #E850A8; }
|
||||
.ansi46 { background-color: #00AAAA; }
|
||||
.ansi47 { background-color: #F5F1DE; }
|
||||
body.ansi_back pre {
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
div.ansi_back.ansi_dark {
|
||||
padding: 0 8px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>{% if content_only %}{{ body }}
|
||||
</div>
|
||||
{% else %}
|
||||
</head>
|
||||
<body class="ansi_fore ansi_back{% if dark %} ansi_dark{% endif %}">
|
||||
<pre>{{ body }}</pre>
|
||||
</body>
|
||||
</html>
|
||||
{% endif %}
|
||||
@ -3,6 +3,7 @@
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import cgi
|
||||
import datetime
|
||||
import dateutil
|
||||
import time
|
||||
@ -33,13 +34,12 @@ from rest_framework.settings import api_settings
|
||||
from rest_framework.views import exception_handler
|
||||
from rest_framework import status
|
||||
|
||||
# Ansi2HTML
|
||||
from ansi2html import Ansi2HTMLConverter
|
||||
from ansi2html.style import SCHEME
|
||||
|
||||
# QSStats
|
||||
import qsstats
|
||||
|
||||
# ANSIConv
|
||||
import ansiconv
|
||||
|
||||
# AWX
|
||||
from awx.main.task_engine import TaskSerializer, TASK_FILE
|
||||
from awx.main.access import get_user_queryset
|
||||
@ -2201,28 +2201,23 @@ class UnifiedJobStdout(RetrieveAPIView):
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
unified_job = self.get_object()
|
||||
if request.accepted_renderer.format in ('html', 'api', 'json'):
|
||||
scheme = request.QUERY_PARAMS.get('scheme', None)
|
||||
start_line = request.QUERY_PARAMS.get('start_line', 0)
|
||||
end_line = request.QUERY_PARAMS.get('end_line', None)
|
||||
if scheme not in SCHEME:
|
||||
scheme = 'ansi2html'
|
||||
dark_val = request.QUERY_PARAMS.get('dark', '')
|
||||
dark = bool(dark_val and dark_val[0].lower() in ('1', 't', 'y'))
|
||||
content_only = bool(request.accepted_renderer.format in ('api', 'json'))
|
||||
dark_bg = (content_only and dark) or (not content_only and (dark or not dark_val))
|
||||
conv = Ansi2HTMLConverter(scheme=scheme, dark_bg=dark_bg,
|
||||
title=get_view_name(self.__class__))
|
||||
content, start, end, absolute_end = unified_job.result_stdout_raw_limited(start_line, end_line)
|
||||
if content_only:
|
||||
headers = conv.produce_headers()
|
||||
body = conv.convert(content, full=False) # Escapes any HTML that may be in content.
|
||||
data = '\n'.join([headers, body])
|
||||
data = '<div class="nocode body_foreground body_background">%s</div>' % data
|
||||
else:
|
||||
data = conv.convert(content)
|
||||
# Fix ugly grey background used by default.
|
||||
data = data.replace('.body_background { background-color: #AAAAAA; }',
|
||||
'.body_background { background-color: #f5f5f5; }')
|
||||
|
||||
body = ansiconv.to_html(cgi.escape(content))
|
||||
context = {
|
||||
'title': get_view_name(self.__class__),
|
||||
'body': mark_safe(body),
|
||||
'dark': dark_bg,
|
||||
'content_only': content_only,
|
||||
}
|
||||
data = render_to_string('api/stdout.html', context).strip()
|
||||
|
||||
if request.accepted_renderer.format == 'api':
|
||||
return Response(mark_safe(data))
|
||||
if request.accepted_renderer.format == 'json':
|
||||
|
||||
@ -2,7 +2,7 @@ Local versions of third-party packages required by Tower. Package names and
|
||||
versions are listed below, along with notes on which files are included.
|
||||
|
||||
amqp==1.4.5 (amqp/*)
|
||||
ansi2html==1.0.6 (ansi2html/*)
|
||||
ansiconv==1.0.0 (ansiconv.py)
|
||||
anyjson==0.3.3 (anyjson/*)
|
||||
argparse==1.2.1 (argparse.py, needed for Python 2.6 support)
|
||||
azure==0.9.0 (azure/*)
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
from ansi2html.converter import Ansi2HTMLConverter
|
||||
__all__ = ['Ansi2HTMLConverter']
|
||||
@ -1,492 +0,0 @@
|
||||
# This file is part of ansi2html
|
||||
# Convert ANSI (terminal) colours and attributes to HTML
|
||||
# Copyright (C) 2012 Ralph Bean <rbean@redhat.com>
|
||||
# Copyright (C) 2013 Sebastian Pipping <sebastian@pipping.org>
|
||||
#
|
||||
# Inspired by and developed off of the work by pixelbeat and blackjack.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import sys
|
||||
import optparse
|
||||
import pkg_resources
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
from ordereddict import OrderedDict
|
||||
|
||||
from ansi2html.style import get_styles, SCHEME
|
||||
import six
|
||||
from six.moves import map
|
||||
from six.moves import zip
|
||||
|
||||
|
||||
ANSI_FULL_RESET = 0
|
||||
ANSI_INTENSITY_INCREASED = 1
|
||||
ANSI_INTENSITY_REDUCED = 2
|
||||
ANSI_INTENSITY_NORMAL = 22
|
||||
ANSI_STYLE_ITALIC = 3
|
||||
ANSI_STYLE_NORMAL = 23
|
||||
ANSI_BLINK_SLOW = 5
|
||||
ANSI_BLINK_FAST = 6
|
||||
ANSI_BLINK_OFF = 25
|
||||
ANSI_UNDERLINE_ON = 4
|
||||
ANSI_UNDERLINE_OFF = 24
|
||||
ANSI_CROSSED_OUT_ON = 9
|
||||
ANSI_CROSSED_OUT_OFF = 29
|
||||
ANSI_VISIBILITY_ON = 28
|
||||
ANSI_VISIBILITY_OFF = 8
|
||||
ANSI_FOREGROUND_CUSTOM_MIN = 30
|
||||
ANSI_FOREGROUND_CUSTOM_MAX = 37
|
||||
ANSI_FOREGROUND_256 = 38
|
||||
ANSI_FOREGROUND_DEFAULT = 39
|
||||
ANSI_BACKGROUND_CUSTOM_MIN = 40
|
||||
ANSI_BACKGROUND_CUSTOM_MAX = 47
|
||||
ANSI_BACKGROUND_256 = 48
|
||||
ANSI_BACKGROUND_DEFAULT = 49
|
||||
ANSI_NEGATIVE_ON = 7
|
||||
ANSI_NEGATIVE_OFF = 27
|
||||
|
||||
|
||||
_template = six.u("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=%(output_encoding)s">
|
||||
<title>%(title)s</title>
|
||||
<style type="text/css">\n%(style)s\n</style>
|
||||
</head>
|
||||
<body class="body_foreground body_background" style="font-size: %(font_size)s;" >
|
||||
<pre>
|
||||
%(content)s
|
||||
</pre>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
""")
|
||||
|
||||
|
||||
class _State(object):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.intensity = ANSI_INTENSITY_NORMAL
|
||||
self.style = ANSI_STYLE_NORMAL
|
||||
self.blink = ANSI_BLINK_OFF
|
||||
self.underline = ANSI_UNDERLINE_OFF
|
||||
self.crossedout = ANSI_CROSSED_OUT_OFF
|
||||
self.visibility = ANSI_VISIBILITY_ON
|
||||
self.foreground = (ANSI_FOREGROUND_DEFAULT, None)
|
||||
self.background = (ANSI_BACKGROUND_DEFAULT, None)
|
||||
self.negative = ANSI_NEGATIVE_OFF
|
||||
|
||||
def adjust(self, ansi_code, parameter=None):
|
||||
if ansi_code in (ANSI_INTENSITY_INCREASED, ANSI_INTENSITY_REDUCED, ANSI_INTENSITY_NORMAL):
|
||||
self.intensity = ansi_code
|
||||
elif ansi_code in (ANSI_STYLE_ITALIC, ANSI_STYLE_NORMAL):
|
||||
self.style = ansi_code
|
||||
elif ansi_code in (ANSI_BLINK_SLOW, ANSI_BLINK_FAST, ANSI_BLINK_OFF):
|
||||
self.blink = ansi_code
|
||||
elif ansi_code in (ANSI_UNDERLINE_ON, ANSI_UNDERLINE_OFF):
|
||||
self.underline = ansi_code
|
||||
elif ansi_code in (ANSI_CROSSED_OUT_ON, ANSI_CROSSED_OUT_OFF):
|
||||
self.crossedout = ansi_code
|
||||
elif ansi_code in (ANSI_VISIBILITY_ON, ANSI_VISIBILITY_OFF):
|
||||
self.visibility = ansi_code
|
||||
elif ANSI_FOREGROUND_CUSTOM_MIN <= ansi_code <= ANSI_FOREGROUND_CUSTOM_MAX:
|
||||
self.foreground = (ansi_code, None)
|
||||
elif ansi_code == ANSI_FOREGROUND_256:
|
||||
self.foreground = (ansi_code, parameter)
|
||||
elif ansi_code == ANSI_FOREGROUND_DEFAULT:
|
||||
self.foreground = (ansi_code, None)
|
||||
elif ANSI_BACKGROUND_CUSTOM_MIN <= ansi_code <= ANSI_BACKGROUND_CUSTOM_MAX:
|
||||
self.background = (ansi_code, None)
|
||||
elif ansi_code == ANSI_BACKGROUND_256:
|
||||
self.background = (ansi_code, parameter)
|
||||
elif ansi_code == ANSI_BACKGROUND_DEFAULT:
|
||||
self.background = (ansi_code, None)
|
||||
elif ansi_code in (ANSI_NEGATIVE_ON, ANSI_NEGATIVE_OFF):
|
||||
self.negative = ansi_code
|
||||
|
||||
def to_css_classes(self):
|
||||
css_classes = []
|
||||
|
||||
def append_unless_default(output, value, default):
|
||||
if value != default:
|
||||
css_class = 'ansi%d' % value
|
||||
output.append(css_class)
|
||||
|
||||
def append_color_unless_default(output, color, default, negative, neg_css_class):
|
||||
value, parameter = color
|
||||
if value != default:
|
||||
prefix = 'inv' if negative else 'ansi'
|
||||
css_class_index = str(value) \
|
||||
if (parameter is None) \
|
||||
else '%d-%d' % (value, parameter)
|
||||
output.append(prefix + css_class_index)
|
||||
elif negative:
|
||||
output.append(neg_css_class)
|
||||
|
||||
append_unless_default(css_classes, self.intensity, ANSI_INTENSITY_NORMAL)
|
||||
append_unless_default(css_classes, self.style, ANSI_STYLE_NORMAL)
|
||||
append_unless_default(css_classes, self.blink, ANSI_BLINK_OFF)
|
||||
append_unless_default(css_classes, self.underline, ANSI_UNDERLINE_OFF)
|
||||
append_unless_default(css_classes, self.crossedout, ANSI_CROSSED_OUT_OFF)
|
||||
append_unless_default(css_classes, self.visibility, ANSI_VISIBILITY_ON)
|
||||
|
||||
flip_fore_and_background = (self.negative == ANSI_NEGATIVE_ON)
|
||||
append_color_unless_default(css_classes, self.foreground, ANSI_FOREGROUND_DEFAULT, flip_fore_and_background, 'inv_background')
|
||||
append_color_unless_default(css_classes, self.background, ANSI_BACKGROUND_DEFAULT, flip_fore_and_background, 'inv_foreground')
|
||||
|
||||
return css_classes
|
||||
|
||||
|
||||
def linkify(line):
|
||||
for match in re.findall(r'https?:\/\/\S+', line):
|
||||
line = line.replace(match, '<a href="%s">%s</a>' % (match, match))
|
||||
|
||||
return line
|
||||
|
||||
|
||||
def _needs_extra_newline(text):
|
||||
if not text or text.endswith('\n'):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class CursorMoveUp(object):
|
||||
pass
|
||||
|
||||
|
||||
class Ansi2HTMLConverter(object):
|
||||
""" Convert Ansi color codes to CSS+HTML
|
||||
|
||||
Example:
|
||||
>>> conv = Ansi2HTMLConverter()
|
||||
>>> ansi = " ".join(sys.stdin.readlines())
|
||||
>>> html = conv.convert(ansi)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
inline=False,
|
||||
dark_bg=True,
|
||||
font_size='normal',
|
||||
linkify=False,
|
||||
escaped=True,
|
||||
markup_lines=False,
|
||||
output_encoding='utf-8',
|
||||
scheme='ansi2html',
|
||||
title=''
|
||||
):
|
||||
|
||||
self.inline = inline
|
||||
self.dark_bg = dark_bg
|
||||
self.font_size = font_size
|
||||
self.linkify = linkify
|
||||
self.escaped = escaped
|
||||
self.markup_lines = markup_lines
|
||||
self.output_encoding = output_encoding
|
||||
self.scheme = scheme
|
||||
self.title = title
|
||||
self._attrs = None
|
||||
|
||||
if inline:
|
||||
self.styles = dict([(item.klass.strip('.'), item) for item in get_styles(self.dark_bg, self.scheme)])
|
||||
|
||||
self.ansi_codes_prog = re.compile('\033\\[' '([\\d;]*)' '([a-zA-z])')
|
||||
|
||||
def apply_regex(self, ansi):
|
||||
parts = self._apply_regex(ansi)
|
||||
parts = self._collapse_cursor(parts)
|
||||
parts = list(parts)
|
||||
|
||||
if self.linkify:
|
||||
parts = [linkify(part) for part in parts]
|
||||
|
||||
combined = "".join(parts)
|
||||
|
||||
if self.markup_lines:
|
||||
combined = "\n".join([
|
||||
"""<span id="line-%i">%s</span>""" % (i, line)
|
||||
for i, line in enumerate(combined.split('\n'))
|
||||
])
|
||||
|
||||
return combined
|
||||
|
||||
def _apply_regex(self, ansi):
|
||||
if self.escaped:
|
||||
specials = OrderedDict([
|
||||
('&', '&'),
|
||||
('<', '<'),
|
||||
('>', '>'),
|
||||
])
|
||||
for pattern, special in specials.items():
|
||||
ansi = ansi.replace(pattern, special)
|
||||
|
||||
state = _State()
|
||||
inside_span = False
|
||||
last_end = 0 # the index of the last end of a code we've seen
|
||||
for match in self.ansi_codes_prog.finditer(ansi):
|
||||
yield ansi[last_end:match.start()]
|
||||
last_end = match.end()
|
||||
|
||||
params, command = match.groups()
|
||||
|
||||
if command not in 'mMA':
|
||||
continue
|
||||
|
||||
# Special cursor-moving code. The only supported one.
|
||||
if command == 'A':
|
||||
yield CursorMoveUp
|
||||
continue
|
||||
|
||||
try:
|
||||
params = list(map(int, params.split(';')))
|
||||
except ValueError:
|
||||
params = [ANSI_FULL_RESET]
|
||||
|
||||
# Find latest reset marker
|
||||
last_null_index = None
|
||||
skip_after_index = -1
|
||||
for i, v in enumerate(params):
|
||||
if i <= skip_after_index:
|
||||
continue
|
||||
|
||||
if v == ANSI_FULL_RESET:
|
||||
last_null_index = i
|
||||
elif v in (ANSI_FOREGROUND_256, ANSI_BACKGROUND_256):
|
||||
skip_after_index = i + 2
|
||||
|
||||
# Process reset marker, drop everything before
|
||||
if last_null_index is not None:
|
||||
params = params[last_null_index + 1:]
|
||||
if inside_span:
|
||||
inside_span = False
|
||||
yield '</span>'
|
||||
state.reset()
|
||||
|
||||
if not params:
|
||||
continue
|
||||
|
||||
# Turn codes into CSS classes
|
||||
skip_after_index = -1
|
||||
for i, v in enumerate(params):
|
||||
if i <= skip_after_index:
|
||||
continue
|
||||
|
||||
if v in (ANSI_FOREGROUND_256, ANSI_BACKGROUND_256):
|
||||
try:
|
||||
parameter = params[i + 2]
|
||||
except IndexError:
|
||||
continue
|
||||
skip_after_index = i + 2
|
||||
else:
|
||||
parameter = None
|
||||
state.adjust(v, parameter=parameter)
|
||||
|
||||
if inside_span:
|
||||
yield '</span>'
|
||||
inside_span = False
|
||||
|
||||
css_classes = state.to_css_classes()
|
||||
if not css_classes:
|
||||
continue
|
||||
|
||||
if self.inline:
|
||||
style = [self.styles[klass].kw for klass in css_classes if
|
||||
klass in self.styles]
|
||||
yield '<span style="%s">' % "; ".join(style)
|
||||
else:
|
||||
yield '<span class="%s">' % " ".join(css_classes)
|
||||
inside_span = True
|
||||
|
||||
yield ansi[last_end:]
|
||||
if inside_span:
|
||||
yield '</span>'
|
||||
inside_span = False
|
||||
|
||||
def _collapse_cursor(self, parts):
|
||||
""" Act on any CursorMoveUp commands by deleting preceding tokens """
|
||||
|
||||
final_parts = []
|
||||
for part in parts:
|
||||
|
||||
# Throw out empty string tokens ("")
|
||||
if not part:
|
||||
continue
|
||||
|
||||
# Go back, deleting every token in the last 'line'
|
||||
if part == CursorMoveUp:
|
||||
final_parts.pop()
|
||||
while '\n' not in final_parts[-1]:
|
||||
final_parts.pop()
|
||||
|
||||
continue
|
||||
|
||||
# Otherwise, just pass this token forward
|
||||
final_parts.append(part)
|
||||
|
||||
return final_parts
|
||||
|
||||
def prepare(self, ansi='', ensure_trailing_newline=False):
|
||||
""" Load the contents of 'ansi' into this object """
|
||||
|
||||
body = self.apply_regex(ansi)
|
||||
|
||||
if ensure_trailing_newline and _needs_extra_newline(body):
|
||||
body += '\n'
|
||||
|
||||
self._attrs = {
|
||||
'dark_bg': self.dark_bg,
|
||||
'font_size': self.font_size,
|
||||
'body': body,
|
||||
}
|
||||
|
||||
return self._attrs
|
||||
|
||||
def attrs(self):
|
||||
""" Prepare attributes for the template """
|
||||
if not self._attrs:
|
||||
raise Exception("Method .prepare not yet called.")
|
||||
return self._attrs
|
||||
|
||||
def convert(self, ansi, full=True, ensure_trailing_newline=False):
|
||||
attrs = self.prepare(ansi, ensure_trailing_newline=ensure_trailing_newline)
|
||||
if not full:
|
||||
return attrs["body"]
|
||||
else:
|
||||
return _template % {
|
||||
'style' : "\n".join(map(str, get_styles(self.dark_bg, self.scheme))),
|
||||
'title' : self.title,
|
||||
'font_size' : self.font_size,
|
||||
'content' : attrs["body"],
|
||||
'output_encoding' : self.output_encoding,
|
||||
}
|
||||
|
||||
def produce_headers(self):
|
||||
return '<style type="text/css">\n%(style)s\n</style>\n' % {
|
||||
'style' : "\n".join(map(str, get_styles(self.dark_bg, self.scheme)))
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
$ ls --color=always | ansi2html > directories.html
|
||||
$ sudo tail /var/log/messages | ccze -A | ansi2html > logs.html
|
||||
$ task burndown | ansi2html > burndown.html
|
||||
"""
|
||||
|
||||
scheme_names = sorted(six.iterkeys(SCHEME))
|
||||
version_str = pkg_resources.get_distribution('ansi2html').version
|
||||
parser = optparse.OptionParser(
|
||||
usage=main.__doc__,
|
||||
version="%%prog %s" % version_str)
|
||||
parser.add_option(
|
||||
"-p", "--partial", dest="partial",
|
||||
default=False, action="store_true",
|
||||
help="Process lines as them come in. No headers are produced.")
|
||||
parser.add_option(
|
||||
"-i", "--inline", dest="inline",
|
||||
default=False, action="store_true",
|
||||
help="Inline style without headers or template.")
|
||||
parser.add_option(
|
||||
"-H", "--headers", dest="headers",
|
||||
default=False, action="store_true",
|
||||
help="Just produce the <style> tag.")
|
||||
parser.add_option(
|
||||
"-f", '--font-size', dest='font_size', metavar='SIZE',
|
||||
default="normal",
|
||||
help="Set the global font size in the output.")
|
||||
parser.add_option(
|
||||
"-l", '--light-background', dest='light_background',
|
||||
default=False, action="store_true",
|
||||
help="Set output to 'light background' mode.")
|
||||
parser.add_option(
|
||||
"-a", '--linkify', dest='linkify',
|
||||
default=False, action="store_true",
|
||||
help="Transform URLs into <a> links.")
|
||||
parser.add_option(
|
||||
"-u", '--unescape', dest='escaped',
|
||||
default=True, action="store_false",
|
||||
help="Do not escape XML tags found in the input.")
|
||||
parser.add_option(
|
||||
"-m", '--markup-lines', dest="markup_lines",
|
||||
default=False, action="store_true",
|
||||
help="Surround lines with <span id='line-n'>..</span>.")
|
||||
parser.add_option(
|
||||
'--input-encoding', dest='input_encoding', metavar='ENCODING',
|
||||
default='utf-8',
|
||||
help="Specify input encoding")
|
||||
parser.add_option(
|
||||
'--output-encoding', dest='output_encoding', metavar='ENCODING',
|
||||
default='utf-8',
|
||||
help="Specify output encoding")
|
||||
parser.add_option(
|
||||
'-s', '--scheme', dest='scheme', metavar='SCHEME',
|
||||
default='ansi2html', choices=scheme_names,
|
||||
help=("Specify color palette scheme. Default: %%default. Choices: %s"
|
||||
% scheme_names))
|
||||
parser.add_option(
|
||||
'-t', '--title', dest='output_title',
|
||||
default='',
|
||||
help="Specify output title")
|
||||
|
||||
opts, args = parser.parse_args()
|
||||
|
||||
conv = Ansi2HTMLConverter(
|
||||
inline=opts.inline,
|
||||
dark_bg=not opts.light_background,
|
||||
font_size=opts.font_size,
|
||||
linkify=opts.linkify,
|
||||
escaped=opts.escaped,
|
||||
markup_lines=opts.markup_lines,
|
||||
output_encoding=opts.output_encoding,
|
||||
scheme=opts.scheme,
|
||||
title=opts.output_title,
|
||||
)
|
||||
|
||||
def _read(input_bytes):
|
||||
if six.PY3:
|
||||
# This is actually already unicode. How to we explicitly decode in
|
||||
# python3? I don't know the answer yet.
|
||||
return input_bytes
|
||||
else:
|
||||
return input_bytes.decode(opts.input_encoding)
|
||||
|
||||
def _print(output_unicode, end='\n'):
|
||||
if hasattr(sys.stdout, 'buffer'):
|
||||
output_bytes = (output_unicode + end).encode(opts.output_encoding)
|
||||
sys.stdout.buffer.write(output_bytes)
|
||||
elif not six.PY3:
|
||||
sys.stdout.write((output_unicode + end).encode(opts.output_encoding))
|
||||
else:
|
||||
sys.stdout.write(output_unicode + end)
|
||||
|
||||
# Produce only the headers and quit
|
||||
if opts.headers:
|
||||
_print(conv.produce_headers(), end='')
|
||||
return
|
||||
|
||||
full = not bool(opts.partial or opts.inline)
|
||||
if six.PY3:
|
||||
output = conv.convert("".join(sys.stdin.readlines()), full=full, ensure_trailing_newline=True)
|
||||
_print(output, end='')
|
||||
else:
|
||||
output = conv.convert(six.u("").join(
|
||||
map(_read, sys.stdin.readlines())
|
||||
), full=full, ensure_trailing_newline=True)
|
||||
_print(output, end='')
|
||||
@ -1,113 +0,0 @@
|
||||
# This file is part of ansi2html.
|
||||
# Copyright (C) 2012 Kuno Woudt <kuno@frob.nl>
|
||||
# Copyright (C) 2013 Sebastian Pipping <sebastian@pipping.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
class Rule(object):
|
||||
|
||||
def __init__(self, klass, **kw):
|
||||
|
||||
self.klass = klass
|
||||
self.kw = '; '.join([(k.replace('_', '-')+': '+kw[k])
|
||||
for k in sorted(kw.keys())]).strip()
|
||||
|
||||
def __str__(self):
|
||||
return '%s { %s; }' % (self.klass, self.kw)
|
||||
|
||||
|
||||
def index(r, g, b):
|
||||
return str(16 + (r * 36) + (g * 6) + b)
|
||||
|
||||
|
||||
def color(r, g, b):
|
||||
return "#%.2x%.2x%.2x" % (r * 42, g * 42, b * 42)
|
||||
|
||||
|
||||
def level(grey):
|
||||
return "#%.2x%.2x%.2x" % (((grey * 10) + 8,) * 3)
|
||||
|
||||
|
||||
def index2(grey):
|
||||
return str(232 + grey)
|
||||
|
||||
# http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
SCHEME = { # black red green brown/yellow blue magenta cyan grey/white
|
||||
'ansi2html': ("#000316", "#aa0000", "#00aa00", "#aa5500", "#0000aa",
|
||||
"#E850A8", "#00aaaa", "#F5F1DE"),
|
||||
'xterm': ("#000000", "#cd0000", "#00cd00", "#cdcd00", "#0000ee",
|
||||
"#cd00cd", "#00cdcd", "#e5e5e5"),
|
||||
'xterm-bright': ("#7f7f7f", "#ff0000", "#00ff00", "#ffff00", "#5c5cff",
|
||||
"#ff00ff", "#00ffff", "#ffffff"),
|
||||
'osx': ("#000000", "#c23621", "#25bc24", "#adad27", "#492ee1",
|
||||
"#d338d3", "#33bbc8", "#cbcccd"),
|
||||
|
||||
# http://ethanschoonover.com/solarized
|
||||
'solarized': ("#262626", "#d70000", "#5f8700", "#af8700", "#0087ff",
|
||||
"#af005f", "#00afaf", "#e4e4e4"),
|
||||
}
|
||||
|
||||
def get_styles(dark_bg=True, scheme='ansi2html'):
|
||||
|
||||
css = [
|
||||
Rule('.body_foreground', color=('#000000', '#AAAAAA')[dark_bg]),
|
||||
Rule('.body_background', background_color=('#AAAAAA', '#000000')[dark_bg]),
|
||||
Rule('.body_foreground > .bold,.bold > .body_foreground, body.body_foreground > pre > .bold',
|
||||
color=('#000000', '#FFFFFF')[dark_bg], font_weight=('bold', 'normal')[dark_bg]),
|
||||
Rule('.inv_foreground', color=('#000000', '#FFFFFF')[not dark_bg]),
|
||||
Rule('.inv_background', background_color=('#AAAAAA', '#000000')[not dark_bg]),
|
||||
Rule('.ansi1', font_weight='bold'),
|
||||
Rule('.ansi2', font_weight='lighter'),
|
||||
Rule('.ansi3', font_style='italic'),
|
||||
Rule('.ansi4', text_decoration='underline'),
|
||||
Rule('.ansi5', text_decoration='blink'),
|
||||
Rule('.ansi6', text_decoration='blink'),
|
||||
Rule('.ansi8', visibility='hidden'),
|
||||
Rule('.ansi9', text_decoration='line-through'),
|
||||
]
|
||||
|
||||
# set palette
|
||||
pal = SCHEME[scheme]
|
||||
for _index in range(8):
|
||||
css.append(Rule('.ansi3%s' % _index, color=pal[_index]))
|
||||
css.append(Rule('.inv3%s' % _index, background_color=pal[_index]))
|
||||
for _index in range(8):
|
||||
css.append(Rule('.ansi4%s' % _index, background_color=pal[_index]))
|
||||
css.append(Rule('.inv4%s' % _index, color=pal[_index]))
|
||||
|
||||
# css.append("/* Define the explicit color codes (obnoxious) */\n\n")
|
||||
|
||||
for green in range(0, 6):
|
||||
for red in range(0, 6):
|
||||
for blue in range(0, 6):
|
||||
css.append(Rule(".ansi38-%s" % index(red, green, blue),
|
||||
color=color(red, green, blue)))
|
||||
css.append(Rule(".inv38-%s" % index(red, green, blue),
|
||||
background=color(red, green, blue)))
|
||||
css.append(Rule(".ansi48-%s" % index(red, green, blue),
|
||||
background=color(red, green, blue)))
|
||||
css.append(Rule(".inv48-%s" % index(red, green, blue),
|
||||
color=color(red, green, blue)))
|
||||
|
||||
for grey in range(0, 24):
|
||||
css.append(Rule('.ansi38-%s' % index2(grey), color=level(grey)))
|
||||
css.append(Rule('.inv38-%s' % index2(grey), background=level(grey)))
|
||||
css.append(Rule('.ansi48-%s' % index2(grey), background=level(grey)))
|
||||
css.append(Rule('.inv48-%s' % index2(grey), color=level(grey)))
|
||||
|
||||
return css
|
||||
@ -1,2 +0,0 @@
|
||||
def read_to_unicode(obj):
|
||||
return [line.decode('utf-8') for line in obj.readlines()]
|
||||
127
awx/lib/site-packages/ansiconv.py
Normal file
127
awx/lib/site-packages/ansiconv.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""
|
||||
Converts ANSI coded text and converts it to either plain text
|
||||
or to HTML.
|
||||
"""
|
||||
import re
|
||||
|
||||
supported_sgr_codes = [1, 3, 4, 9, 30, 31, 32, 33, 34, 35, 36, 37, 40, 41, 42,
|
||||
43, 44, 45, 46, 47]
|
||||
|
||||
|
||||
def to_plain(ansi):
|
||||
"""Takes the given string and strips all ANSI codes out.
|
||||
|
||||
:param ansi: The string to strip
|
||||
:return: The stripped string
|
||||
"""
|
||||
return re.sub(r'\x1B\[[0-9;]*[ABCDEFGHJKSTfmnsulh]', '', ansi)
|
||||
|
||||
|
||||
def to_html(ansi, replace_newline=False):
|
||||
"""Converts the given ANSI string to HTML
|
||||
|
||||
If `replace_newline` is set to True, then all newlines will be
|
||||
replaced with <br />.
|
||||
|
||||
:param ansi: The ANSI text.
|
||||
:param replace_newline: Whether to replace newlines with HTML.
|
||||
:return: The resulting HTML string.
|
||||
"""
|
||||
blocks = ansi.split('\x1B')
|
||||
parsed_blocks = []
|
||||
for block in blocks:
|
||||
command, text = _block_to_html(block)
|
||||
|
||||
# The command "A" means move the cursor up, so we emulate that here.
|
||||
if command == 'A' and len(parsed_blocks) > 0:
|
||||
parsed_blocks.pop()
|
||||
while len(parsed_blocks) > 0 and '\n' not in parsed_blocks[-1]:
|
||||
parsed_blocks.pop()
|
||||
|
||||
parsed_blocks.append(text)
|
||||
|
||||
text = ''.join(parsed_blocks)
|
||||
|
||||
if replace_newline:
|
||||
text = text.replace('\n', '<br />\n')
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def base_css(dark=True):
|
||||
"""Some base CSS with all of the default ANSI styles/colors.
|
||||
|
||||
:param dark: Whether background should be dark or light.
|
||||
:return: A string of CSS
|
||||
"""
|
||||
return "\n".join([
|
||||
css_rule('.ansi_fore', color=('#000000', '#FFFFFF')[dark]),
|
||||
css_rule('.ansi_back', background_color=('#FFFFFF', '#000000')[dark]),
|
||||
css_rule('.ansi1', font_weight='bold'),
|
||||
css_rule('.ansi3', font_weight='italic'),
|
||||
css_rule('.ansi4', text_decoration='underline'),
|
||||
css_rule('.ansi9', text_decoration='line-through'),
|
||||
css_rule('.ansi30', color="#000000"),
|
||||
css_rule('.ansi31', color="#FF0000"),
|
||||
css_rule('.ansi32', color="#00FF00"),
|
||||
css_rule('.ansi33', color="#FFFF00"),
|
||||
css_rule('.ansi34', color="#0000FF"),
|
||||
css_rule('.ansi35', color="#FF00FF"),
|
||||
css_rule('.ansi36', color="#00FFFF"),
|
||||
css_rule('.ansi37', color="#FFFFFF"),
|
||||
css_rule('.ansi40', background_color="#000000"),
|
||||
css_rule('.ansi41', background_color="#FF0000"),
|
||||
css_rule('.ansi42', background_color="#00FF00"),
|
||||
css_rule('.ansi43', background_color="#FFFF00"),
|
||||
css_rule('.ansi44', background_color="#0000FF"),
|
||||
css_rule('.ansi45', background_color="#FF00FF"),
|
||||
css_rule('.ansi46', background_color="#00FFFF"),
|
||||
css_rule('.ansi47', background_color="#FFFFFF")
|
||||
])
|
||||
|
||||
|
||||
def css_rule(class_name, **properties):
|
||||
"""Creates a CSS rule string.
|
||||
|
||||
The named parameters are used as the css properties. Underscores
|
||||
are converted to hyphens.
|
||||
|
||||
:param class_name: The CSS class name
|
||||
:param properties: The properties sent as named params.
|
||||
:return: The CSS string
|
||||
"""
|
||||
prop_str = lambda name, val: name.replace('_', '-') + ': ' + val
|
||||
return '{0} {{ {1}; }}'.format(
|
||||
class_name,
|
||||
'; '.join([prop_str(prop, properties[prop]) for prop in properties])
|
||||
)
|
||||
|
||||
|
||||
def _block_to_html(text):
|
||||
"""Converts the given block of ANSI coded text to HTML.
|
||||
|
||||
The text is only given back as HTML if the ANSI code is at the
|
||||
beginning of the string (e.g. "[0;33mFoobar")
|
||||
|
||||
:param text: The text block to convert.
|
||||
:return: The text as HTML
|
||||
"""
|
||||
match = re.match(r'^\[(?P<code>\d+(?:;\d+)*)?(?P<command>[Am])', text)
|
||||
if match is None:
|
||||
return None, text
|
||||
|
||||
command = match.group('command')
|
||||
text = text[match.end():]
|
||||
|
||||
if match.group('code') is None:
|
||||
return command, text
|
||||
|
||||
classes = []
|
||||
for code in match.group('code').split(';'):
|
||||
if int(code) in supported_sgr_codes:
|
||||
classes.append('ansi{0}'.format(code))
|
||||
|
||||
if classes:
|
||||
text = '<span class="{0}">{1}</span>'.format(' '.join(classes), text)
|
||||
|
||||
return command, text
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user