...
 
Commits (3)
......@@ -18,10 +18,10 @@ You can install this tool from PyPI:
```
alcibiade@mobydick:~$ sudo pip3 install docker-ascii-map
Collecting docker-ascii-map
Downloading docker-ascii-map-0.4.1.tar.gz
Downloading docker-ascii-map-1.0.0.tar.gz
Installing collected packages: docker-ascii-map
Running setup.py install for docker-ascii-map ... done
Successfully installed docker-ascii-map-0.4.1
Successfully installed docker-ascii-map-1.0.0
```
......
__version__ = '0.4.2'
__version__ = '1.0.0'
import argparse
import os
import sys
from typing import Tuple
from docker_ascii_map import __version__
# This code is taken from Django
def supports_color():
"""
Returns True if the running system's terminal supports color, and False
otherwise.
"""
plat = sys.platform
supported_platform = plat != 'Pocket PC' and (plat != 'win32' or 'ANSICON' in os.environ)
# isatty is not always implemented, #6223.
is_a_tty = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
if not supported_platform or not is_a_tty:
return False
return True
def get_input_parameters() -> Tuple[bool]:
parser = argparse.ArgumentParser(description='Display the docker host contents on a visual map.')
parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__)
parser.add_argument('-c', '--color', action='store_const', const='color', help='Use color in map display')
parser.add_argument('-m', '--mono', action='store_const', const='mono', help='Render the map in monochrome')
terminal = os.getenv('TERM')
args = parser.parse_args()
color_mode = False
if terminal:
color_mode = 'color' in terminal
color_mode = supports_color()
if args.color:
color_mode = True
......@@ -24,4 +35,4 @@ def get_input_parameters() -> Tuple[bool]:
if args.mono:
color_mode = False
return color_mode, None
return color_mode,
......@@ -5,16 +5,15 @@ from docker_ascii_map.widget import *
from docker_ascii_map.docker_config import Configuration, Container
def build_container_widget(container: Container, encoding: str, color: bool) -> Widget:
def build_container_widget(container: Container, encoding: str) -> Widget:
lines = []
container_running = container.status == 'running'
if encoding == 'UTF-8':
statuschar = u"\u2713" if container_running else u"\u274c"
statuschar = u"\u2713" if container.is_running() else u"\u274c"
else:
statuschar = 'V' if container_running else 'x'
statuschar = 'V' if container.is_running() else 'x'
color = 'green' if container_running else 'red'
color = 'green' if container.is_running() else 'red'
lines.append('[' + statuschar + '] ' + container.name)
lines.append(' ' + container.image)
......@@ -61,7 +60,7 @@ class Renderer:
def __init__(self):
pass
def render(self, config: Configuration, encoding: str = 'UTF-8', color: bool = False):
def render_to_raster(self, config: Configuration, encoding: str = 'UTF-8'):
network_widgets = []
networks = build_ordered_network_list(config)
......@@ -76,7 +75,7 @@ class Renderer:
for container in config.containers:
if [net] == container.networks:
container_widget = build_container_widget(container, encoding, color)
container_widget = build_container_widget(container, encoding)
cnt_widgets_map[container] = container_widget
net_widgets.append(build_container_wrapper(container, container_widget))
......@@ -91,7 +90,7 @@ class Renderer:
for container in config.containers:
if len(container.networks) > 1:
c = Padding(build_container_widget(container, encoding, color), Size(1, 0))
c = Padding(build_container_widget(container, encoding), Size(1, 0))
cnt_widgets_map[container] = c
padded = Padding(c, Size(12, 2))
bridge_widgets.append(padded)
......@@ -111,9 +110,20 @@ class Renderer:
for container in config.containers:
for port in container.ports:
w = cnt_widgets_map[container]
portmaps.append((w, str(port.public_port)))
portmaps.append((w,
str(port.public_port),
'green' if container.is_running() else 'red',
[]
))
ports_box = Annotations(links_box, portmaps)
root_box = ports_box
return root_box.render().text(color)
return root_box.render()
def render_to_string(self, config: Configuration, encoding: str = 'UTF-8', color: bool = False):
raster = self.render_to_raster(config, encoding)
return raster.text(color)
def render(self, config: Configuration, encoding: str = 'UTF-8', color: bool = False):
return self.render_to_string(config, encoding, color)
......@@ -20,6 +20,9 @@ class Container:
self.image = image
self.ports = ports
def is_running(self):
return self.status == 'running'
def __repr__(self) -> str:
return '%s - %s - %s %s' % (self.name, self.status, self.networks, self.ports)
......
......@@ -48,23 +48,23 @@ class Raster:
for i in range(len(text)):
self._cells[y][x + i] = RasterCell(character=text[i], origin=origin, color=color, attrs=attrs)
def draw_line(self, src_x, src_y, dst_x, dst_y):
def draw_line(self, src_x, src_y, dst_x, dst_y, color: str = None, attrs: List[str] = None):
med_x = int((src_x + dst_x) / 2)
for x in range(src_x, dst_x):
y = src_y if x < med_x else dst_y
if self.get(x, y).character in ['|', '+']:
self.write(x, y, '+')
self.write(x, y, '+', color=color, attrs=attrs)
else:
self.write(x, y, '-')
self.write(x, y, '-', color=color, attrs=attrs)
for y in range(min(src_y, dst_y), max(src_y, dst_y)):
if self.get(med_x, y).character != '+':
self.write(med_x, y, '|')
self.write(med_x, y, '|', color=color, attrs=attrs)
if src_y != dst_y:
self.write(med_x, src_y, '+')
self.write(med_x, dst_y, '+')
self.write(med_x, src_y, '+', color=color, attrs=attrs)
self.write(med_x, dst_y, '+', color=color, attrs=attrs)
def _expand(self, x, y):
while len(self._cells) < y:
......
......@@ -194,7 +194,7 @@ class Links(Widget):
class Annotations(Widget):
def __init__(self, content: Widget, annotations: List[Tuple[Widget, str]]):
def __init__(self, content: Widget, annotations: List[Tuple[Widget, str, str, List[str]]]):
self._content = Padding(content, Size(3, 0), Size(0, 0)) if len(annotations) > 0 else content
self._annotations = annotations
self._width = max([0] + [len(a[1]) for a in annotations])
......@@ -211,24 +211,24 @@ class Annotations(Widget):
used_y = []
for widget, annotation_text in self._annotations:
for widget, annotation_text, color, attrs in self._annotations:
if widget not in widget_annotations_map.keys():
widget_annotations_map[widget] = []
widget_annotations_map[widget].append(annotation_text)
widget_annotations_map[widget].append((annotation_text, color, attrs))
for widget, annotations in widget_annotations_map.items():
bounds = raster.origin_bounds(widget)
y = int(bounds.y + (bounds.h - len(annotations)) / 2)
for annotation in annotations:
for annotation_text, color, attrs in annotations:
while y in used_y:
y += 1
used_y.append(y)
raster.write(0, y, annotation)
raster.write(self._width, y, ' ]-')
raster.draw_line(self._width + 3, y, bounds.x - 1, y)
raster.write(0, y, annotation_text, color=color, attrs=attrs)
raster.write(self._width, y, ' ]-', color='white')
raster.draw_line(self._width + 3, y, bounds.x - 1, y, color='white')
return raster
......@@ -3,6 +3,7 @@ import unittest
from docker_ascii_map.docker_config import *
from docker_ascii_map.ascii_render import Renderer
from docker_ascii_map.raster import RasterCell
class RenderingTests(unittest.TestCase):
......@@ -170,6 +171,32 @@ class RenderingTests(unittest.TestCase):
' +--------+\n'
, text)
def test_port_color(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name',
[PortMapping(private_port=8080, public_port=80)]),
Container('n2', 'stopped', ['net2'], 'group2/image-short', [PortMapping(private_port=22, public_port=22)]),
])
renderer = Renderer()
raster = renderer.render_to_raster(config)
self.assertEquals(
' +- net1 ---------------------+\n'
'80 ]-+ [✓] n1 |\n'
' | group1/image-long-name |\n'
' +----------------------------+\n'
' +- net2 ---------------------+\n'
'22 ]-+ [❌] n2 |\n'
' | group2/image-short |\n'
' +----------------------------+\n',
raster.text(color=False))
self.assertEquals('green', raster.get(0, 1).color)
self.assertEquals('white', raster.get(3, 1).color)
self.assertEquals('red', raster.get(0, 5).color)
self.assertEquals('white', raster.get(3, 5).color)
if __name__ == '__main__':
unittest.main()
......@@ -2,6 +2,8 @@ import unittest
from argparse import ArgumentParser
from unittest.mock import patch
import sys
import docker_ascii_map.argument_parser
......@@ -13,16 +15,29 @@ class Args:
class ParametersTests(unittest.TestCase):
def test_monoterm(self):
with patch('os.getenv', return_value='vt100') as mock_getenv:
with patch.object(ArgumentParser, 'parse_args', return_value=Args()) as mock_parse:
r = docker_ascii_map.argument_parser.get_input_parameters()
self.assertEqual(False, r[0])
with patch('sys.platform', return_value='linux') as mock_getenv, \
patch.object(sys.stdout, 'isatty', return_value=False) as mock_stdout, \
patch.object(ArgumentParser, 'parse_args', return_value=Args()) as mock_parse:
r = docker_ascii_map.argument_parser.get_input_parameters()
self.assertEqual(False, r[0])
def test_colorterm(self):
with patch('os.getenv', return_value='xterm-256color') as mock_getenv:
with patch.object(ArgumentParser, 'parse_args', return_value=Args()) as mock_parse:
r = docker_ascii_map.argument_parser.get_input_parameters()
self.assertEqual(True, r[0])
with patch('sys.platform', return_value='linux') as mock_getenv, \
patch.object(sys.stdout, 'isatty', return_value=True) as mock_stdout, \
patch.object(ArgumentParser, 'parse_args', return_value=Args()) as mock_parse:
r = docker_ascii_map.argument_parser.get_input_parameters()
self.assertEqual(True, r[0])
def test_colorterm_win(self):
with patch('sys.platform', return_value='win32') as mock_getenv, \
patch.object(sys.stdout, 'isatty', return_value=True) as mock_stdout, \
patch('os.environ', return_value=['ANSICON']) as mock_environ, \
patch.object(ArgumentParser, 'parse_args', return_value=Args()) as mock_parse:
r = docker_ascii_map.argument_parser.get_input_parameters()
self.assertEqual(True, r[0])
if __name__ == '__main__':
......
......@@ -191,7 +191,7 @@ class ModelTests(unittest.TestCase):
model = Annotations(VBox([
Padding(w1, Size(4, 4)),
Padding(w2, Size(12, 1))
]), [(w2, '8080'), (w2, ' 22')])
]), [(w2, '8080', 'white', []), (w2, ' 22', 'white', [])])
self.assertEqual(Size(38, 14), model.preferred_size())
......@@ -216,7 +216,8 @@ class ModelTests(unittest.TestCase):
def test_Annotation_Alignment(self):
self.maxDiff = None
w1 = Paragraph(['Hello', 'World !', 'Line 3', 'Line 4'])
model = Annotations(w1, [(w1, '23'), (w1, '22'), (w1, '24'), (w1, '24')])
model = Annotations(w1, [(w1, '23', 'white', []), (w1, '22', 'white', []),
(w1, '24', 'white', []), (w1, '24', 'white', [])])
text = str(model.render())
......