...
 
Commits (11)
__version__ = '0.3'
__version__ = '0.4'
import math
from docker_ascii_map.widget import *
from docker_ascii_map.docker_config import Configuration, Container
def build_container_widget(container: Container, encoding: str) -> Widget:
def build_container_widget(container: Container, encoding: str, color: bool) -> Widget:
lines = []
container_running = container.status == 'running'
if encoding == 'UTF-8':
statuschar = u"\u2713" if container.status == 'running' else u"\u274c"
statuschar = u"\u2713" if container_running else u"\u274c"
else:
statuschar = 'V' if container.status == 'running' else 'x'
statuschar = 'V' if container_running else 'x'
color = 'green' if container_running else 'red'
lines.append('[' + statuschar + '] ' + container.name)
lines.append(' ' + container.image)
container_widget = Paragraph(lines)
container_widget = Paragraph(lines, color=color)
return container_widget
def build_ordered_network_list(config):
def build_ordered_network_list(config: Configuration) -> List[str]:
networks = set()
for container in config.containers:
......@@ -39,11 +45,23 @@ def build_ordered_network_list(config):
return networks
def build_container_wrapper(container: Container, container_widget: Widget) -> Widget:
ports_count = len(container.ports)
total_padding = ports_count - 2
if total_padding <= 0:
return container_widget
else:
return Padding(container_widget,
Size(0, int(math.floor(total_padding / 2))),
Size(0, int(math.ceil(total_padding / 2))))
class Renderer:
def __init__(self):
pass
def render(self, config: Configuration, encoding: str = 'UTF-8'):
def render(self, config: Configuration, encoding: str = 'UTF-8', color: bool = False):
network_widgets = []
networks = build_ordered_network_list(config)
......@@ -58,9 +76,9 @@ class Renderer:
for container in config.containers:
if [net] == container.networks:
container_widget = build_container_widget(container, encoding)
container_widget = build_container_widget(container, encoding, color)
cnt_widgets_map[container] = container_widget
net_widgets.append(container_widget)
net_widgets.append(build_container_wrapper(container, container_widget))
net_box = Border(VBox(net_widgets), net)
net_widgets_map[net] = net_box
......@@ -73,7 +91,7 @@ class Renderer:
for container in config.containers:
if len(container.networks) > 1:
c = Padding(build_container_widget(container, encoding), Size(1, 0))
c = Padding(build_container_widget(container, encoding, color), Size(1, 0))
cnt_widgets_map[container] = c
padded = Padding(c, Size(12, 2))
bridge_widgets.append(padded)
......@@ -98,4 +116,4 @@ class Renderer:
ports_box = Annotations(links_box, portmaps)
root_box = ports_box
return str(root_box.render())
return root_box.render().text(color)
......@@ -8,15 +8,18 @@ from docker_ascii_map.docker_config import ConfigParser
from docker_ascii_map.ascii_render import Renderer
parser = argparse.ArgumentParser(description='Display the docker host contents on a visual map.')
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
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')
if __name__ == '__main__':
args = parser.parse_args()
color_mode = args.color and not args.mono
parser = ConfigParser()
config_parser = ConfigParser()
renderer = Renderer()
config = parser.get_configuration()
config = config_parser.get_configuration()
# print(config)
text = renderer.render(config, sys.stdout.encoding)
text = renderer.render(config, encoding=sys.stdout.encoding, color=color_mode)
print(text)
from typing import Tuple
import termcolor
class Boundary:
def __init__(self, x: int, y: int, w: int, h: int):
......@@ -15,37 +17,47 @@ class Boundary:
return '%d,%d %dx%d' % (self.x, self.y, self.w, self.h)
class RasterCell:
def __init__(self, character: str = ' ', origin: object = None, color: str = None):
self.character = character
self.origin = origin
self.color = color
def __eq__(self, other):
return self.__dict__ == other.__dict__
class Raster:
def __init__(self):
self._cells = []
self._default = ' ', None
self._default = RasterCell()
def write(self, x: int, y: int, text, origin: object = None):
def write(self, x: int, y: int, text, origin: object = None, color: str = None):
if type(text) is Raster:
rastersize_x, rastersize_y = text.size()
for ry in range(rastersize_y):
for rx in range(rastersize_x):
c, o = text.get(rx, ry)
self.write(x + rx, y + ry, c, o)
cell = text.get(rx, ry)
self.write(x + rx, y + ry, origin=cell.origin, color=cell.color, text=cell.character)
else:
self._expand(x + len(text), y + 1)
for i in range(len(text)):
self._cells[y][x + i] = text[i], origin
self._cells[y][x + i] = RasterCell(character=text[i], origin=origin, color=color)
def draw_line(self, src_x, src_y, dst_x, dst_y):
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)[0] in ['|', '+']:
if self.get(x, y).character in ['|', '+']:
self.write(x, y, '+')
else:
self.write(x, y, '-')
for y in range(min(src_y, dst_y), max(src_y, dst_y)):
if self.get(med_x, y)[0] != '+':
if self.get(med_x, y).character != '+':
self.write(med_x, y, '|')
if src_y != dst_y:
......@@ -59,7 +71,7 @@ class Raster:
while len(self._cells[y - 1]) < x:
self._cells[y - 1].append(self._default)
def get(self, x: int, y: int) -> Tuple[str, object]:
def get(self, x: int, y: int) -> RasterCell:
if y >= 0 and y < len(self._cells) and x >= 0 and x < len(self._cells[y]):
return self._cells[y][x]
else:
......@@ -79,7 +91,7 @@ class Raster:
for y in range(raster_h):
for x in range(raster_w):
cell = self.get(x, y)
if origin == cell[1]:
if origin == cell.origin:
if b_w == 0:
b_x, b_y, b_w, b_h = x, y, 1, 1
else:
......@@ -91,13 +103,19 @@ class Raster:
return Boundary(b_x, b_y, b_w, b_h) if b_w > 0 else None
def __str__(self):
def text(self, color: bool = False):
text = ''
for line in self._cells:
for c in line:
text += c[0]
if color and c.color:
text += termcolor.colored(c.character, c.color)
else:
text += c.character
text += '\n'
return text
def __str__(self):
return self.text()
......@@ -76,20 +76,20 @@ class Border(Widget):
r = Raster()
for y in range(cmp_h + 2):
r.write(0, y, '|', self)
r.write(width + 1, y, '|', self)
r.write(0, y, '|', origin=self, color='grey')
r.write(width + 1, y, '|', origin=self, color='grey')
for x in range(width + 2):
r.write(x, 0, '-', self)
r.write(x, cmp_h + 1, '-', self)
r.write(x, 0, '-', origin=self, color='grey')
r.write(x, cmp_h + 1, '-', origin=self, color='grey')
r.write(0, 0, '+', self)
r.write(width + 1, 0, '+', self)
r.write(0, cmp_h + 1, '+', self)
r.write(width + 1, cmp_h + 1, '+', self)
r.write(0, 0, '+', origin=self, color='grey')
r.write(width + 1, 0, '+', origin=self, color='grey')
r.write(0, cmp_h + 1, '+', origin=self, color='grey')
r.write(width + 1, cmp_h + 1, '+', origin=self, color='grey')
if len(self._title) > 0:
r.write(2, 0, ' ' + self._title + ' ', self)
r.write(2, 0, ' ' + self._title + ' ', origin=self, color='white')
r.write(1, 1, cmp_raster)
......@@ -148,8 +148,9 @@ class HBox(Widget):
class Paragraph(Widget):
def __init__(self, lines: List[str]):
def __init__(self, lines: List[str], color: str = None):
self._lines = lines
self._color = color
def preferred_size(self) -> Size:
return Size(max([len(l) for l in self._lines]), len(self._lines))
......@@ -158,7 +159,7 @@ class Paragraph(Widget):
r = Raster()
for l in self._lines:
r.write(0, r.size()[1], l, self)
r.write(0, r.size()[1], l, origin=self, color=self._color)
return r
......@@ -205,19 +206,28 @@ class Annotations(Widget):
raster = Raster()
raster.write(self._width, 0, self._content.render(hints))
widget_annotations_map = {}
used_y = []
for widget, annotation_text in self._annotations:
if widget not in widget_annotations_map.keys():
widget_annotations_map[widget] = []
widget_annotations_map[widget].append(annotation_text)
for widget, annotations in widget_annotations_map.items():
bounds = raster.origin_bounds(widget)
y = int(bounds.y + bounds.h / 2) - 1
y = int(bounds.y + (bounds.h - len(annotations)) / 2)
while y in used_y:
y += 1
for annotation in annotations:
while y in used_y:
y += 1
used_y.append(y)
used_y.append(y)
raster.write(0, y, annotation_text)
raster.write(self._width, y, ' ]-')
raster.draw_line(self._width + 3, y, bounds.x - 1, y)
raster.write(0, y, annotation)
raster.write(self._width, y, ' ]-')
raster.draw_line(self._width + 3, y, bounds.x - 1, y)
return raster
#!/usr/bin/env python3
from setuptools import setup
from docker_ascii_map import __version__
setup(
name='docker-ascii-map',
version='0.3',
version=__version__,
packages=['docker_ascii_map'],
package_dir={'docker_ascii_map': 'docker_ascii_map'},
scripts=['docker_ascii_map/docker-ascii-map.py'],
test_suite='tests',
setup_requires=['pytest-runner'],
install_requires=['docker-py'],
install_requires=['docker-py', 'termcolor'],
tests_require=['pytest'],
url='https://github.com/ChessCorp/docker-ascii-map',
license='MIT',
......
......@@ -48,6 +48,24 @@ class RenderingTests(unittest.TestCase):
'+----------------------------+\n',
renderer.render(config, 'ascii'))
def test_container_color(self):
config = Configuration([
Container('n1', 'running', ['net1'], 'group1/image-long-name', []),
Container('n2', 'stopped', ['net2'], 'group2/image-short', []),
])
renderer = Renderer()
self.assertNotEquals(
'+- net1 ---------------------+\n'
'| [✓] n1 |\n'
'| group1/image-long-name |\n'
'+----------------------------+\n'
'+- net2 ---------------------+\n'
'| [❌] n2 |\n'
'| group2/image-short |\n'
'+----------------------------+\n',
renderer.render(config, color=True))
def test_dual_net_config(self):
self.maxDiff = None
config = Configuration([
......@@ -131,6 +149,7 @@ class RenderingTests(unittest.TestCase):
PortMapping(private_port=8080, public_port=80),
PortMapping(private_port=22, public_port=22),
PortMapping(private_port=25, public_port=25),
PortMapping(private_port=5432, public_port=54),
]),
])
renderer = Renderer()
......@@ -144,9 +163,11 @@ class RenderingTests(unittest.TestCase):
' | im |\n'
' +--------+\n'
' +- net2 -+\n'
'80 ]-+ [✓] n2 |\n'
'22 ]-+ im |\n'
'25 ]-+--------+\n'
'80 ]-+ |\n'
'22 ]-+ [✓] n2 |\n'
'25 ]-+ im |\n'
'54 ]-+ |\n'
' +--------+\n'
, text)
......
import unittest
from docker_ascii_map.raster import Raster, Boundary
from docker_ascii_map.raster import Raster, Boundary, RasterCell
class RasterTests(unittest.TestCase):
......@@ -8,7 +8,7 @@ class RasterTests(unittest.TestCase):
raster = Raster()
self.assertEqual('', str(raster))
self.assertEqual((0, 0), raster.size())
self.assertEqual((' ', None), raster.get(5, 4))
self.assertEqual(RasterCell(), raster.get(5, 4))
def test_expansion(self):
raster = Raster()
......@@ -87,7 +87,6 @@ class RasterTests(unittest.TestCase):
, str(r)
)
def test_line_overlap(self):
r = Raster()
r.draw_line(0, 3, 6, 2)
......@@ -100,6 +99,25 @@ class RasterTests(unittest.TestCase):
, str(r)
)
def test_color_disabled(self):
r = Raster()
r.write(0, 0, 'Red', color='red')
r.write(0, 1, 'Green', color='green')
self.assertEqual(
'Red\n'
'Green\n'
, str(r)
)
def test_color_enabled(self):
r = Raster()
r.write(0, 0, 'Red', color='red')
r.write(0, 1, 'Green', color='green')
self.assertEqual(
'\x1b[31mR\x1b[0m\x1b[31me\x1b[0m\x1b[31md\x1b[0m\n\x1b[32mG\x1b[0m\x1b[32mr\x1b[0m\x1b[32me\x1b[0m\x1b[32me\x1b[0m\x1b[32mn\x1b[0m\n'
, r.text(True)
)
if __name__ == '__main__':
unittest.main()
......@@ -213,6 +213,21 @@ class ModelTests(unittest.TestCase):
, str(model.render())
)
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')])
text = str(model.render())
self.assertEqual(
'23 ]-Hello \n'
'22 ]-World !\n'
'24 ]-Line 3 \n'
'24 ]-Line 4 \n'
, text
)
if __name__ == '__main__':
unittest.main()