interactive client: avoid messing up the input line
- trigger a redraw of the input line when asynchronous log messages arrive + do not print traceback on 'remote' errors + persistent readline history Change-Id: If85fd064c1c09c44e0cb0ebccbfc1b6411ad5aac Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/30793 Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de> Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
parent
2dad4b4ee1
commit
4f69899fbe
@ -25,14 +25,14 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import code
|
|
||||||
import argparse
|
import argparse
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
# Add import path for inplace usage
|
# Add import path for inplace usage
|
||||||
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
sys.path.insert(0, path.abspath(path.join(path.dirname(__file__), '..')))
|
||||||
|
|
||||||
from frappy.client.interactive import Client, watch
|
from frappy.client.interactive import Client, watch, Console
|
||||||
|
|
||||||
|
|
||||||
def parseArgv(argv):
|
def parseArgv(argv):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
@ -47,6 +47,7 @@ def parseArgv(argv):
|
|||||||
nargs='*', type=str, default=[])
|
nargs='*', type=str, default=[])
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
_USAGE = """
|
_USAGE = """
|
||||||
Usage:
|
Usage:
|
||||||
%s
|
%s
|
||||||
@ -101,4 +102,4 @@ if success:
|
|||||||
print('skipping interactive mode')
|
print('skipping interactive mode')
|
||||||
exit()
|
exit()
|
||||||
print(_USAGE % _usage_args)
|
print(_USAGE % _usage_args)
|
||||||
code.interact(banner='', local=sys.modules['__main__'].__dict__)
|
Console(sys.modules['__main__'].__dict__)
|
||||||
|
@ -42,18 +42,28 @@ watch(io, T=True) # watch io and all parameters of T
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
import code
|
||||||
|
import signal
|
||||||
|
import os
|
||||||
|
from os.path import expanduser
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from frappy.client import SecopClient
|
from frappy.client import SecopClient
|
||||||
from frappy.errors import SECoPError
|
from frappy.errors import SECoPError
|
||||||
from frappy.datatypes import get_datatype, StatusType
|
from frappy.datatypes import get_datatype, StatusType
|
||||||
|
try:
|
||||||
|
import readline
|
||||||
|
except ImportError:
|
||||||
|
readline = None
|
||||||
|
|
||||||
main = sys.modules['__main__']
|
main = sys.modules['__main__']
|
||||||
|
|
||||||
LOG_LEVELS = {'debug', 'comlog', 'info', 'warning', 'error', 'off'}
|
LOG_LEVELS = {'debug', 'comlog', 'info', 'warning', 'error', 'off'}
|
||||||
|
CLR = '\r\x1b[K' # code to move to the left and clear current line
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
show_time = False
|
show_time = False
|
||||||
|
sigwinch = False
|
||||||
|
|
||||||
def __init__(self, loglevel='info'):
|
def __init__(self, loglevel='info'):
|
||||||
func = self.noop
|
func = self.noop
|
||||||
@ -66,22 +76,23 @@ class Logger:
|
|||||||
def emit(self, fmt, *args, **kwds):
|
def emit(self, fmt, *args, **kwds):
|
||||||
if self.show_time:
|
if self.show_time:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
minute = now // 60
|
tm = time.localtime(now)
|
||||||
if minute != self._minute:
|
if tm.tm_min != self._minute:
|
||||||
self._minute = minute
|
self._minute = tm.tm_min
|
||||||
print(time.strftime('--- %H:%M:%S ---', time.localtime(now)))
|
print(CLR + time.strftime('--- %H:%M:%S ---', tm))
|
||||||
print('%6.3f' % (now % 60.0), str(fmt) % args)
|
sec = ('%6.3f' % (now % 60.0)).replace(' ', '0')
|
||||||
|
print(CLR + sec, str(fmt) % args)
|
||||||
else:
|
else:
|
||||||
print(str(fmt) % args)
|
print(CLR + (str(fmt) % args))
|
||||||
|
if self.sigwinch:
|
||||||
|
# SIGWINCH: 'window size has changed' -> triggers a refresh of the input line
|
||||||
|
os.kill(os.getpid(), signal.SIGWINCH)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def noop(fmt, *args, **kwds):
|
def noop(fmt, *args, **kwds):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
infologger = Logger('info')
|
|
||||||
|
|
||||||
|
|
||||||
class PrettyFloat(float):
|
class PrettyFloat(float):
|
||||||
"""float with a nicer repr:
|
"""float with a nicer repr:
|
||||||
|
|
||||||
@ -118,16 +129,12 @@ class Module:
|
|||||||
def _one_line(self, pname, minwid=0):
|
def _one_line(self, pname, minwid=0):
|
||||||
"""return <module>.<param> = <value> truncated to one line"""
|
"""return <module>.<param> = <value> truncated to one line"""
|
||||||
param = getattr(type(self), pname)
|
param = getattr(type(self), pname)
|
||||||
try:
|
result = param.formatted(self)
|
||||||
value = getattr(self, pname)
|
|
||||||
r = param.format(value)
|
|
||||||
except Exception as e:
|
|
||||||
r = repr(e)
|
|
||||||
pname = pname.ljust(minwid)
|
pname = pname.ljust(minwid)
|
||||||
vallen = 113 - len(self._name) - len(pname)
|
vallen = 113 - len(self._name) - len(pname)
|
||||||
if len(r) > vallen:
|
if len(result) > vallen:
|
||||||
r = r[:vallen - 4] + ' ...'
|
result = result[:vallen - 4] + ' ...'
|
||||||
return '%s.%s = %s' % (self._name, pname, r)
|
return '%s.%s = %s' % (self._name, pname, result)
|
||||||
|
|
||||||
def _isBusy(self):
|
def _isBusy(self):
|
||||||
return self.status[0] // 100 == StatusType.BUSY // 100
|
return self.status[0] // 100 == StatusType.BUSY // 100
|
||||||
@ -185,6 +192,7 @@ class Module:
|
|||||||
|
|
||||||
def _start_watching(self):
|
def _start_watching(self):
|
||||||
for pname in self._watched_params:
|
for pname in self._watched_params:
|
||||||
|
self._watch_parameter(self, pname, forced=True)
|
||||||
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
self._secnode.register_callback((self._name, pname), updateEvent=self._watch_parameter)
|
||||||
self._secnode.request('logging', self._name, self._log_level)
|
self._secnode.request('logging', self._name, self._log_level)
|
||||||
self._secnode.register_callback(None, nodeStateChange=self._set_log_level)
|
self._secnode.register_callback(None, nodeStateChange=self._set_log_level)
|
||||||
@ -202,7 +210,7 @@ class Module:
|
|||||||
def read(self, pname='value'):
|
def read(self, pname='value'):
|
||||||
value, _, error = self._secnode.readParameter(self._name, pname)
|
value, _, error = self._secnode.readParameter(self._name, pname)
|
||||||
if error:
|
if error:
|
||||||
raise error
|
Console.raise_without_traceback(error)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __call__(self, target=None):
|
def __call__(self, target=None):
|
||||||
@ -252,16 +260,24 @@ class Param:
|
|||||||
return self
|
return self
|
||||||
value, _, error = obj._secnode.cache[obj._name, self.name]
|
value, _, error = obj._secnode.cache[obj._name, self.name]
|
||||||
if error:
|
if error:
|
||||||
|
Console.raise_without_traceback(error)
|
||||||
raise error
|
raise error
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def formatted(self, obj):
|
||||||
|
value, _, error = obj._secnode.cache[obj._name, self.name]
|
||||||
|
if error:
|
||||||
|
return repr(error)
|
||||||
|
return self.format(value)
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
def __set__(self, obj, value):
|
||||||
if self.name == 'target':
|
if self.name == 'target':
|
||||||
obj._running = Queue()
|
obj._running = Queue()
|
||||||
try:
|
try:
|
||||||
obj._secnode.setParameter(obj._name, self.name, value)
|
obj._secnode.setParameter(obj._name, self.name, value)
|
||||||
except SECoPError as e:
|
except SECoPError as e:
|
||||||
obj._secnode.log.error(repr(e))
|
Console.raise_without_traceback(e)
|
||||||
|
# obj._secnode.log.error(repr(e))
|
||||||
|
|
||||||
def format(self, value):
|
def format(self, value):
|
||||||
return self.datatype.format_value(value)
|
return self.datatype.format_value(value)
|
||||||
@ -390,3 +406,34 @@ class Client(SecopClient):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Client(%r)' % self.uri
|
return 'Client(%r)' % self.uri
|
||||||
|
|
||||||
|
|
||||||
|
class Console(code.InteractiveConsole):
|
||||||
|
def __init__(self, local):
|
||||||
|
super().__init__(local)
|
||||||
|
history = None
|
||||||
|
if readline:
|
||||||
|
try:
|
||||||
|
history = expanduser('~/.frappy-cli-history')
|
||||||
|
readline.read_history_file(history)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.interact('', '')
|
||||||
|
finally:
|
||||||
|
if history:
|
||||||
|
readline.write_history_file(history)
|
||||||
|
|
||||||
|
def raw_input(self, prompt=""):
|
||||||
|
Logger.sigwinch = bool(readline) # activate refresh signal
|
||||||
|
line = input(prompt)
|
||||||
|
Logger.sigwinch = False
|
||||||
|
return line
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def raise_without_traceback(cls, exc):
|
||||||
|
def omit_traceback_once(cls):
|
||||||
|
del Console.showtraceback
|
||||||
|
cls.showtraceback = omit_traceback_once
|
||||||
|
print('ERROR:', repr(exc))
|
||||||
|
raise exc
|
||||||
|
Loading…
x
Reference in New Issue
Block a user