output/viewer.py (138 lines of code) (raw):
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from argparse import ArgumentParser
from curses import curs_set, newpad, is_term_resized, wrapper, \
KEY_UP, KEY_DOWN, KEY_PPAGE, KEY_NPAGE, KEY_HOME, KEY_END, \
A_REVERSE, A_BOLD, A_ITALIC
from json import load as json_load
from textwrap import wrap
class Window:
def __init__(self):
self.pad = newpad(1, 1)
self.y = 0
self.off_y = 0
self.off_x = 0
def resize(self, height, width):
self.pad.resize(height, width)
self.y = 0
def clear(self):
self.pad.clear()
self.y = 0
def print(self, text="", attr=0):
self.pad.addstr(self.y, 0, text, attr)
self.y += 1
def refresh(self, min_y, min_x, max_y, max_x):
self.pad.refresh(self.off_y, self.off_x, min_y, min_x, max_y, max_x)
class ListWindow(Window):
def __init__(self, items):
super().__init__()
self.items = items
self.n_items = len(self.items)
self.max_item_width = max(len(item) for item in self.items)
self.current_index = 0
def refresh(self, min_y, min_x, max_y, max_x):
self.resize(len(self.items), self.max_item_width + 1)
if self.current_index < self.off_y:
self.off_y = self.current_index
view_height = max_y - min_y + 1
while self.current_index >= self.off_y + view_height:
self.off_y += 1
for y, name in enumerate(self.items):
if y == self.current_index:
self.pad.addstr(y, 0, name.ljust(self.max_item_width), A_REVERSE)
else:
self.pad.addstr(y, 0, name.ljust(self.max_item_width))
super().refresh(min_y, min_x, max_y, max_x)
def move_by(self, delta):
return self.move_to(self.current_index + delta)
def move_to(self, new_index):
old_index = self.current_index
if 0 <= new_index < self.n_items:
self.current_index = new_index
return new_index - old_index
class EndpointWindow(Window):
def __init__(self):
super().__init__()
self.endpoint = {}
def update(self, endpoint):
self.endpoint = endpoint
def refresh(self, min_y, min_x, max_y, max_x):
view_width = max_x - min_x + 1
self.resize(200, view_width)
self.print(f"{self.endpoint['name']} [{self.endpoint['visibility']}] [{self.endpoint['stability']}]", A_BOLD)
since = self.endpoint.get('since', '0.0.0')
if since != '0.0.0':
self.print(f"(since {since})", A_ITALIC)
self.print()
description = wrap(self.endpoint["description"], width=view_width - 1)
for line in description:
self.print(line)
self.print()
for url in self.endpoint["urls"]:
for method in url["methods"]:
self.print(f"{method} {url['path']}")
self.print()
self.print(f"{self.endpoint['docUrl']}")
super().refresh(min_y, min_x, max_y, max_x)
class Viewer:
def __init__(self, filename):
with open(filename) as f:
self.spec = json_load(f)
self.title_window = Window()
self.endpoint_list_window = ListWindow([endpoint["name"] for endpoint in self.spec["endpoints"]])
self.endpoint_window = EndpointWindow()
def _refresh_title(self, min_y, min_x, max_y, max_x):
title = self.spec["_info"]["title"]
self.title_window.resize(2, len(title) + 1)
self.title_window.print(title)
self.title_window.print("=" * len(title))
self.title_window.refresh(min_y, min_x, max_y, max_x)
def _refresh_names(self, min_y, min_x, max_y, max_x):
self.endpoint_list_window.refresh(min_y, min_x, max_y, max_x)
def _refresh_details(self, min_y, min_x, max_y, max_x):
self.endpoint_window.update(self.spec["endpoints"][self.endpoint_list_window.current_index])
self.endpoint_window.refresh(min_y, min_x, max_y, max_x)
def refresh(self, screen):
screen.refresh()
height, width = screen.getmaxyx()
aside_width = int(min(self.endpoint_list_window.max_item_width - 1, width / 4))
self._refresh_title(0, 0, 1, width - 1)
self._refresh_names(3, 0, height - 1, aside_width)
self._refresh_details(3, aside_width + 4, height - 1, width - 1)
return height, width
def view(self, screen):
curs_set(0)
height, width = self.refresh(screen)
while True:
try:
key = screen.getch()
except KeyboardInterrupt:
break
else:
moved = 0
if key == ord("q"):
break
elif key == KEY_UP:
moved = self.endpoint_list_window.move_by(-1)
elif key == KEY_DOWN:
moved = self.endpoint_list_window.move_by(+1)
elif key == KEY_PPAGE:
moved = self.endpoint_list_window.move_by(-(height - 3))
elif key == KEY_NPAGE:
moved = self.endpoint_list_window.move_by(+(height - 3))
elif key == KEY_HOME:
moved = self.endpoint_list_window.move_to(0)
elif key == KEY_END:
moved = self.endpoint_list_window.move_to(self.endpoint_list_window.n_items - 1)
if moved:
self.endpoint_window.clear()
height, width = self.refresh(screen)
elif is_term_resized(height, width):
screen.clear()
height, width = self.refresh(screen)
def main(screen):
parser = ArgumentParser()
parser.add_argument("filename")
parsed = parser.parse_args()
viewer = Viewer(parsed.filename)
viewer.view(screen)
if __name__ == "__main__":
wrapper(main)