馃悕馃悕馃悕
at main 7.1 kB view raw
1 2import inspect 3import dis 4import traceback 5import hashlib 6import sys 7import os 8import time 9import shutil 10from pathlib import Path 11from argparse import ArgumentParser as ArgParser 12from importlib import import_module, reload 13from time import perf_counter 14 15from lib.log import Logger, inner_log 16from lib.internal.parse import lsnap 17from lib.internal.witchery import try_dump_locals 18 19parser = ArgParser("snakepyt") 20#parser.add_argument("sketch", help="the sketch to run", type=str) 21args = parser.parse_args() 22 23snakepyt_version = (0, 1) 24 25def establish_scheduler(): 26 schedule = [] 27 def _schedule(fn, args): 28 schedule.append((fn, args)) 29 return (schedule, _schedule) 30 31def run(fn, arg, partial_id, outer_scope, log, sources, finalizer=None): 32 scope = dict(outer_scope) 33 schedule, schedule_fn = establish_scheduler() 34 scope["schedule"] = schedule_fn 35 scope["print"] = inner_log(source=fn, indent=4) 36 run_id = partial_id + (arg,) 37 38 args = [] if arg is None else [arg] 39 (success, locals_or_err) = try_dump_locals(fn, sources[fn.__name__], args, {}, scope, log) 40 if success: 41 scope.update(locals_or_err) 42 else: 43 err = locals_or_err 44 errs = try_dump_locals.errs 45 if err == errs.NON_DICT_RETURN: 46 log.indented().log("could not extract locals. check for an early return", mode="warning") 47 else: 48 log.indented().log(locals_or_err, mode="error") 49 return False 50 51 52 for (fn, args) in schedule: 53 if args is not None: 54 for arg in args: 55 run(fn, arg, run_id, scope, log.indented(), sources) 56 else: 57 t0 = perf_counter() 58 log(f"begin {fn.__name__} {run_id}") 59 success = run(fn, None, run_id, scope, log.indented(), sources) 60 log(f"done in {perf_counter() - t0:.3f}s", mode="success" if success else "error") 61 log.blank() 62 if finalizer is not None: 63 try: 64 exec(finalizer, globals=scope) 65 except KeyboardInterrupt: 66 raise 67 except: 68 log.indented().trace(source=fn) 69 return False 70 return True 71 72persistent_state = {} 73persistent_hashes = {} 74def handle_persistent(persistent_fn, module_globals, log, sources): 75 bytecode_hash = hashlib.sha256(persistent_fn.__code__.co_code).hexdigest() 76 if sketch_name in persistent_hashes: 77 if bytecode_hash == persistent_hashes[sketch_name]: 78 return True 79 (success, locals_or_err) = try_dump_locals(persistent_fn, sources[persistent_fn.__name__], [], {}, module_globals, log) 80 if success: 81 persistent_hashes[sketch_name] = bytecode_hash 82 persistent_state.update(locals_or_err) 83 else: 84 log(f"failed to run persistent function: {locals_or_err}", mode="error") 85 86 return success 87 88 89try: 90 username = os.getlogin() 91except: 92 username = None 93 94pyt_print = Logger().mode("success").tag("snakepyt") 95pyt_print(f"hello {username}! <3" if username else "hello! <3") 96pyt_print.blank() 97 98repl_continue = True 99while repl_continue: 100 try: 101 message = pyt_print.input(username) 102 except (KeyboardInterrupt, EOFError): 103 pyt_print.blank().log("goodbye <3").blank() 104 repl_continue = False 105 continue 106 107 if "prefix" in persistent_state: 108 message = " ".join([persistent_state["prefix"], message]) 109 110 message = message.lstrip() 111 112 if message.startswith("."): 113 if message.rstrip() == ".": 114 pyt_print(persistent_state) 115 else: 116 segments = [segment.strip() for segment in message.split(".")][1:] 117 selection = ("base scope", persistent_state) 118 for segment in segments: 119 if segment == "": 120 pyt_print("repeated dots (..) are redundant", mode="warning") 121 continue 122 try: 123 selection = (segment, selection[1][segment]) 124 pyt_print(f"{selection[0]}: {selection[1]}") 125 except KeyError: 126 pyt_print(f"no \"{segment}\" in {selection[0]}", mode="error") 127 continue 128 except TypeError: 129 pyt_print(f"{selection[0]} is not a scope", mode="error") 130 pyt_print.indented()(f"{selection[0]}: {selection[1]}", mode="info") 131 132 continue 133 134 135 (command, remainder) = lsnap(message) 136 137 if command == "flush": 138 persistent_state = {} 139 persistent_hashes = {} 140 pyt_print("state flushed") 141 continue 142 if command in ["exit", "quit", ":q", ",q"]: 143 pyt_print.blank().log("goodbye <3").blank() 144 repl_continue = False 145 continue 146 if command in ["hello", "hi"]: 147 pyt_print("hiii :3") 148 continue 149 if command == "crash": 150 raise Exception("crashing on purpose :3") 151 continue 152 if command == "run": 153 sketch_name, remainder = lsnap(remainder) 154 155 try: 156 pyt_print(f"loading sketch \"{sketch_name}\"", mode="info") 157 module_name = f"sketch.{sketch_name}" 158 if module_name in sys.modules: 159 del sys.modules[module_name] 160 sketch = import_module(module_name) 161 except ModuleNotFoundError: 162 pyt_print.indented().trace() 163 pyt_print("no such sketch", mode="error", indent=4).blank() 164 continue 165 except KeyboardInterrupt: 166 pyt_print("aborted", mode="info").blank() 167 continue 168 except: 169 pyt_print.indented().trace() 170 continue 171 172 sources = { name : inspect.getsource(member) 173 for name, member in inspect.getmembers(sketch) 174 if inspect.isfunction(member) and member.__module__ == module_name 175 } 176 177 log = pyt_print.tag(sketch_name).mode("info") 178 179 t0 = perf_counter() 180 if hasattr(sketch, "persistent"): 181 if not handle_persistent(sketch.persistent, sketch.__dict__, log, sources): 182 log.blank() 183 continue 184 185 sketch.__dict__.update(persistent_state) 186 187 run_dir = time.strftime(f"%d.%m.%Y/{sketch_name}_t%H.%M.%S") 188 sketch.__dict__["run_dir"] = run_dir 189 Path("out/" + run_dir).mkdir(parents=True, exist_ok=True) 190 191 shutil.copy(sketch.__file__, f"out/{run_dir}/{sketch_name}.py") 192 193 with open(f"out/{run_dir}/.snakepyt", "w") as metadata: 194 metadata.write(f"snakepyt version {snakepyt_version[0]}.{snakepyt_version[1]}\n") 195 196 if hasattr(sketch, "main"): 197 if hasattr(sketch, "final"): 198 finalizer = sketch.final.__code__ 199 else: 200 finalizer = None 201 try: 202 run(sketch.main, None, (), sketch.__dict__, log, sources, finalizer) 203 except KeyboardInterrupt: 204 pyt_print.blank().log("aborted", mode="info").blank() 205 continue 206 else: 207 log("sketch has no main function", mode="error", indent=4) 208 209 log(f"finished all runs in {perf_counter() - t0:.3f}s") 210 continue 211 212 pyt_print(f"command: {command}", mode="info") 213