[console] Add rudimentary debug console mode

This commit is contained in:
Daniel Edgecumbe 2017-10-01 18:20:54 +01:00
parent 7fda2b0d04
commit 95de5ec147
4 changed files with 210 additions and 16 deletions

187
console.py Normal file
View File

@ -0,0 +1,187 @@
# Copyright (c) 2014-2017 esotericnonsense (Daniel Edgecumbe)
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://opensource.org/licenses/mit-license.php
import curses
import curses.textpad
import asyncio
import decimal
try:
import ujson as json
except ImportError:
import json
import view
class ConsoleView(view.View):
_mode_name = "console"
def __init__(self, client):
self._client = client
self._textbox_active = False
# TODO: implement history properly
self._command_history = [""]
self._response_history = []
self._response_history_strings = []
self._response_history_offset = 0
super().__init__()
async def _draw(self):
self._clear_init_pad()
CGREEN = curses.color_pair(1)
CRED = curses.color_pair(3)
CYELLOW = curses.color_pair(5)
CBOLD = curses.A_BOLD
CREVERSE = curses.A_REVERSE
self._pad.addstr(0, 63, "[UP/DOWN: browse, TAB: enter command]", CYELLOW)
offset = self._response_history_offset
if offset > 0:
self._pad.addstr(0, 36, "... ^ ...", CBOLD)
if offset < len(self._response_history_strings) - 17:
self._pad.addstr(17, 36, "... v ...", CBOLD)
for i, (t, string) in enumerate(self._response_history_strings):
if i < offset:
continue
if i > offset+15: # TODO
break
color = CBOLD + CGREEN if t == 0 else CBOLD
self._pad.addstr(1+i-offset, 1, string, color)
cmd = self._command_history[-1]
cmd2 = None
if len(cmd) > 97:
cmd2, cmd = cmd[97:], cmd[:97]
self._pad.addstr(18, 1, "> {}".format(cmd),
CRED + CBOLD + CREVERSE if self._textbox_active else 0)
if cmd2 is not None:
self._pad.addstr(19, 3, cmd2,
CRED + CBOLD + CREVERSE if self._textbox_active else 0)
self._draw_pad_to_screen()
@staticmethod
def _convert_reqresp_to_strings(request, response):
srequest = [
(0, request[i:i+95])
for i in range(0, len(request), 95)
]
srequest[0] = (0, ">>> " + srequest[0][1])
jresponse = json.dumps(response, indent=4, sort_keys=True).split("\n")
# TODO: if error, set 2 not 1
sresponse = [
(1, l[i:i+99])
for l in jresponse
for i in range(0, len(l), 99)
]
return srequest + sresponse + [(-1, "")]
async def _submit_command(self):
# TODO: parse, allow nested, use brackets etc
request = self._command_history[-1]
if len(request) == 0:
return
parts = request.split(" ")
for i in range(len(parts)):
# TODO: parse better.
if parts[i].isdigit():
parts[i] = int(parts[i])
elif parts[i] == "false" or parts[i] == "False":
parts[i] = False
elif parts[i] == "true" or parts[i] == "True":
parts[i] = True
else:
try:
parts[i] = decimal.Decimal(parts[i])
except:
pass
cmd = parts[0]
if len(parts) > 1:
params = parts[1:]
else:
params = None
response = await self._client.request(cmd, params=params)
self._response_history.append(
(request, response),
)
self._response_history_strings.extend(
self._convert_reqresp_to_strings(request, response),
)
self._command_history.append("") # add a new, empty command
self._response_history_offset = len(self._response_history_strings) - 17
self._textbox_active = not self._textbox_active
await self._draw_if_visible()
async def _scroll_back_response_history(self):
if self._response_history_offset == 0:
return # At the beginning already.
self._response_history_offset -= 1
await self._draw_if_visible()
async def _scroll_forward_response_history(self):
if self._response_history_offset > len(self._response_history_strings) - 18:
return # At the end already.
self._response_history_offset += 1
await self._draw_if_visible()
async def handle_keypress(self, key):
if key == "\t" or key == "KEY_TAB":
self._textbox_active = not self._textbox_active
key = None
elif self._textbox_active:
if (len(key) == 1 and ord(key) == 127) or key == "KEY_BACKSPACE":
self._command_history[-1] = self._command_history[-1][:-1]
key = None
elif key == "KEY_RETURN" or key == "\n":
# We use ensure_future so as not to block the keypad loop on
# an RPC call
# asyncio.ensure_future(self._submit_command())
await self._submit_command()
return None
elif len(key) == 1:
# TODO: check if it's printable etc
if len(self._command_history[-1]) < 190:
self._command_history[-1] += key
key = None
else:
if key == "KEY_UP":
await self._scroll_back_response_history()
key = None
elif key == "KEY_DOWN":
await self._scroll_forward_response_history()
key = None
await self._draw_if_visible()
return key
async def on_mode_change(self, newmode):
""" Overrides view.View to set the textbox inactive. """
if newmode != self._mode_name:
self._textbox_active = False
self._visible = False
return
self._visible = True
await self._draw_if_visible()

View File

@ -4,11 +4,7 @@
VERSION_STRING = "bitcoind-ncurses v0.2.0-dev"
# MODES = [
# "monitor", "wallet", "peers", "block",
# "tx", "console", "net", "forks",
# ]
MODES = ["monitor", "peers", "wallet", "block", "transaction", "net"]
MODES = ["monitor", "peers", "wallet", "block", "transaction", "console", "net"]
DEFAULT_MODE = "monitor"
# TX_VERBOSE_MODE controls whether the prevouts for an input are fetched.

View File

@ -19,6 +19,7 @@ import block
import transaction
import net
import wallet
import console
async def keypress_loop(window, callback, resize_callback):
@ -133,16 +134,20 @@ def create_tasks(client, window, nosplash):
modehandler.set_mode,
)
consoleview = console.ConsoleView(client)
modehandler.add_callback("monitor", monitorview.on_mode_change)
modehandler.add_callback("peers", peerview.on_mode_change)
modehandler.add_callback("block", blockview.on_mode_change)
modehandler.add_callback("transaction", transactionview.on_mode_change)
modehandler.add_callback("net", netview.on_mode_change)
modehandler.add_callback("wallet", walletview.on_mode_change)
modehandler.add_callback("console", consoleview.on_mode_change)
modehandler.add_keypress_handler("block", blockview.handle_keypress)
modehandler.add_keypress_handler("transaction", transactionview.handle_keypress)
modehandler.add_keypress_handler("wallet", walletview.handle_keypress)
modehandler.add_keypress_handler("console", consoleview.handle_keypress)
async def on_nettotals(key, obj):
await headerview.on_nettotals(key, obj)
@ -172,6 +177,7 @@ def create_tasks(client, window, nosplash):
await transactionview.on_window_resize(y, x)
await netview.on_window_resize(y, x)
await walletview.on_window_resize(y, x)
await consoleview.on_window_resize(y, x)
ty, tx = window.getmaxyx()
tasks = [

View File

@ -62,6 +62,22 @@ class ModeHandler(object):
await self.set_mode(newmode)
async def handle_keypress(self, key):
# See if the current mode can handle it.
if self._mode is None:
return key
handler = None
try:
handler = self._keypress_handlers[self._mode]
except KeyError:
pass
if handler:
key = await handler(key)
if key is None:
return key
# See if it's related to switching modes.
if key == "KEY_LEFT":
await self._seek_mode(-1)
@ -77,15 +93,4 @@ class ModeHandler(object):
await self.set_mode(mode)
return None
# See if the current mode can handle it.
if self._mode is None:
return key
try:
handler = self._keypress_handlers[self._mode]
except KeyError:
return key
key = await handler(key)
return key # Either none by this point, or still there.