import subprocess import sys import ast import copy import PySimpleGUI as sg """ Python repl with a "watch list" This Demo Program was created as an answer to a Reddit question. The idea is to build a Python interpreter >>> prompt, accept command and execute them... and to also inslude a watcher feature. The "Watched" variables or expressions are constantly updated as the proram runs. At the moment, the event loop runs once a second. It could easily be shortened if that's too slow """ WIDTH_VARIABLES = 12 WIDTH_RESULTS = 16 def convertExpr2Expression(Expr): Expr.lineno = 0 Expr.col_offset = 0 result = ast.Expression(Expr.value, lineno=0, col_offset = 0) return result def exec_with_return(code): code_ast = ast.parse(code) init_ast = copy.deepcopy(code_ast) init_ast.body = code_ast.body[:-1] last_ast = copy.deepcopy(code_ast) last_ast.body = code_ast.body[-1:] exec(compile(init_ast, "", "exec"), globals()) if type(last_ast.body[0]) == ast.Expr: return eval(compile(convertExpr2Expression(last_ast.body[0]), "", "eval"),globals()) else: exec(compile(last_ast, "", "exec"),globals()) def func(x=''): return f'return value from func()={x}' def init(): def InVar(key1, key2): row1 = [sg.T(' '), sg.I(key=key1, size=(WIDTH_VARIABLES,1)), sg.I(key=key1+'CHANGED_', size=(WIDTH_RESULTS,1)),sg.B('Detail', key=key1+'DETAIL_'), sg.T(' '), sg.T(' '), sg.I(key=key2, size=(WIDTH_VARIABLES, 1)), sg.I(key=key2 + 'CHANGED_', size=(WIDTH_RESULTS, 1)), sg.B('Detail', key=key2+'DETAIL_'),] return row1 variables_frame = [ InVar('_VAR1_', '_VAR2_'), InVar('_VAR3_', '_VAR4_'), InVar('_VAR5_', '_VAR6_'),] interactive_frame = [[sg.T('>>> '), sg.In(size=(70,1), key='_INTERACTIVE_'), sg.B('Go', bind_return_key=True, visible=False)], [sg.Output(size=(70,8))],] layout = [ [sg.Frame('Variables or Expressions to Watch', variables_frame)], [sg.Frame('Interactive REPL', interactive_frame)], [sg.Button('Exit')]] window = sg.Window('Realtime REPL Command Output + Watches', layout).Finalize() window.Element('_INTERACTIVE_').SetFocus() return window # event_loop(window) event_once = lambda window, var=0 : exec(""" # while True: # Event Loop event, values = window.Read(timeout=100) print(event, values) if event != sg.TIMEOUT_KEY else None if event in (None, 'Exit'): False cmd = values['_INTERACTIVE_'] if event == 'Run': runCommand(cmd=cmd, window=window) elif event == 'Go': window.Element('_INTERACTIVE_').Update('') out='' print(">>> ", cmd) try: print(exec_with_return(cmd)) except Exception as e: print(f'Exception on output {e}') elif event.endswith('_DETAIL_'): try: sg.PopupScrolled(eval(values[f'_VAR{event[4]}_'])) except: pass # -------------------- Process the "watch list" ------------------ for i in range(1, 6): key = f'_VAR{i}_' out_key = f'_VAR{i}_CHANGED_' if window.Element(key): if values[key]: try: window.Element(out_key).Update(eval(values[key])) except: window.Element(out_key).Update('') var += 1 True """) def runCommand(cmd, timeout=None, window=None): """ run shell command @param cmd: command to execute @param timeout: timeout for command execution @param window: the PySimpleGUI window that the output is going to (needed to do refresh on) @return: (return code from command, command output) """ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) output = '' for line in p.stdout: line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip() output += line print(line) window.Refresh() if window else None # yes, a 1-line if, so shoot me retval = p.wait(timeout) return (retval, output) if __name__ == '__main__': window = init() my_variable=1000 while True: event_once(window) # if not event_once(window): # break window.Close()