mirror of
https://github.com/hjdhnx/dr_py.git
synced 2024-11-24 01:38:25 -06:00
261 lines
7.4 KiB
Python
261 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# File : quickjs_py交互.py
|
|
# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
|
|
# Date : 2022/10/12
|
|
|
|
|
|
from quickjs import Context, Object as QuickJSObject
|
|
import json
|
|
from pprint import pp
|
|
from uuid import UUID
|
|
from datetime import date, datetime
|
|
|
|
# https://github.com/PetterS/quickjs/pull/82 py交互扩展
|
|
# print(QuickJSObject)
|
|
# QuickJSObject.set('a',1)
|
|
# print(Context.get_global())
|
|
# exit()
|
|
|
|
class JS:
|
|
interp = None
|
|
# Store global variables here. Reference from javascript by path
|
|
_globals = None
|
|
|
|
# Used for generating unique ids in Context namespace
|
|
_incr = 0
|
|
|
|
# I cache the values passed from python to js. Otherwise, we create new representation
|
|
# objects each time a value is referenced.
|
|
_cache = None
|
|
|
|
def __init__(self):
|
|
self.interp = Context()
|
|
self._globals = {}
|
|
self._cache = {}
|
|
|
|
# Install js proxy logic
|
|
self.interp.add_callable("proxy_get", self.proxy_get)
|
|
self.interp.add_callable("proxy_set", self.proxy_set)
|
|
self.interp.eval("""
|
|
var handler = {
|
|
get(target, property) {
|
|
rv = proxy_get(target.path, property)
|
|
if (typeof rv == 'string' && rv.substr(0, 5) == 'eval:') {
|
|
eval(rv.substr(5));
|
|
return eval(rv.substr(5));
|
|
}
|
|
return rv
|
|
},
|
|
set(target, property, value) {
|
|
return proxy_set(target.path, property, value)
|
|
}
|
|
}
|
|
var mk_proxy = function(path) {
|
|
return new Proxy({path: path}, handler);
|
|
}
|
|
""")
|
|
|
|
def set(self, **kwargs):
|
|
for (k, v) in kwargs.items():
|
|
self.interp.set(k, v)
|
|
|
|
def __call__(self, s):
|
|
return self.interp.eval(s)
|
|
|
|
# -----------------------------------------------------------------
|
|
def to_non_proxied(self, v):
|
|
# returns True/False and a value if the value can be represented
|
|
# by a Javascript type (not proxied)
|
|
if v in [None, True, False]:
|
|
return True, v
|
|
|
|
if type(v) in [QuickJSObject, str, int, float]:
|
|
return True, v
|
|
|
|
if type(v) in [UUID]:
|
|
return True, str(v)
|
|
|
|
return False, None
|
|
|
|
def to_eval_str(self, v, path=None):
|
|
# The value will be produced via eval if it is a string starting with eval:
|
|
|
|
# Cache results
|
|
if id(v) and id(v) in self._cache:
|
|
return self._cache[id(v)]
|
|
|
|
# If the value is a list, create a list of return values. Problem is
|
|
# that these have no path in the self._globals dict. They will have to
|
|
# be duplicated if they are objects.
|
|
|
|
# BUG here - every reference to the list, create another copy - need to cache
|
|
if type(v) == list:
|
|
rv = []
|
|
|
|
for v1 in v:
|
|
can_non_proxy, non_proxied = self.to_non_proxied(v1)
|
|
if can_non_proxy:
|
|
self._incr += 1
|
|
self.interp.set("_lv%s" % self._incr, v1)
|
|
rv.append("_lv%s" % self._incr)
|
|
else:
|
|
rv.append(self.to_eval_str(v1))
|
|
rv = "[" + ",".join(rv) + "]"
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
if type(v) == date:
|
|
rv = "new Date(%s, %s, %s)" % (v.year, v.month - 1, v.day)
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
if type(v) == datetime:
|
|
rv = "new Date('%s')" % v.isoformat()
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
# this creates a function, which can never be garbage collected
|
|
if callable(v):
|
|
self._incr += 1
|
|
gname = "_fn%s" % self._incr
|
|
self.interp.add_callable(gname, v)
|
|
rv = "%s" % gname
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
# Anonymous variables are created by values inside lists
|
|
if path is None:
|
|
self._incr += 1
|
|
path = "_anon%s" % self._incr
|
|
self._globals[path] = v
|
|
|
|
# I need to do this for objects and try getattr
|
|
if type(v) == dict:
|
|
rv = "mk_proxy('%s')" % path
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
# Should be a user defined object to get here. Proxy it.
|
|
rv = "mk_proxy('%s')" % path
|
|
self._cache[id(v)] = rv
|
|
return rv
|
|
|
|
# -----------------------------------------------------------------
|
|
# Proxy Callback Points
|
|
def proxy_variable(self, **kwargs):
|
|
for (k, v) in kwargs.items():
|
|
self._globals[k] = v
|
|
self.interp.set(k, None)
|
|
js("""%s = mk_proxy("%s");""" % (k, k))
|
|
|
|
def eval_path(self, path):
|
|
parts = path.split(".")
|
|
root = self._globals
|
|
for part in parts:
|
|
root = root[part]
|
|
return root
|
|
|
|
def proxy_get(self, path, property):
|
|
# print(path, property)
|
|
root = self.eval_path(path)
|
|
try:
|
|
rv = root.get(property, None)
|
|
except:
|
|
# Object
|
|
rv = getattr(root, property)
|
|
|
|
# print(path, property, rv)
|
|
|
|
can_non_proxy, non_proxied = self.to_non_proxied(rv)
|
|
if can_non_proxy:
|
|
return rv
|
|
|
|
new_path = path + "." + property
|
|
estr = self.to_eval_str(rv, path=new_path)
|
|
# print("eval:" + estr)
|
|
return "eval:" + estr
|
|
|
|
def proxy_set(self, path, property, value):
|
|
# print(path, property, value)
|
|
root = self.eval_path(path)
|
|
root[property] = value
|
|
|
|
if __name__ == '__main__':
|
|
# Example access class attributes
|
|
class example:
|
|
a = "I am a"
|
|
a1 = 111
|
|
|
|
def fn(self, a='not set'):
|
|
print("fn() called, a = ", a)
|
|
|
|
|
|
# Example access dict
|
|
l = {
|
|
"a": 1,
|
|
"fn": lambda: "XXXX",
|
|
"p1": None,
|
|
"p2": {
|
|
"p3": "PPP333"
|
|
},
|
|
"p4": ["A", 4, None, example()],
|
|
"p5": example()
|
|
}
|
|
|
|
js = JS()
|
|
|
|
# Standard Variables
|
|
js.set(v1="Set via python")
|
|
print("v1 = ", js("v1"))
|
|
assert (js("v1") == "Set via python")
|
|
js.set(v2=None)
|
|
print("v2 = ", js("v2"))
|
|
assert (js("v2") is None)
|
|
|
|
js.proxy_variable(l=l)
|
|
|
|
# null
|
|
print("p1 = ", js("l.p1"))
|
|
assert (l['p1'] == js("l.p1"))
|
|
|
|
# Access dict values
|
|
print("l.a = ", js("l.a"))
|
|
assert (l['a'] == js("l.a"))
|
|
js("l.b = 4")
|
|
print("l.b = ", js("l.b"))
|
|
assert (l['b'] == 4)
|
|
print("fn() = ", js("l.fn()"))
|
|
|
|
# Undefined attribute
|
|
print("l.undef = ", js("l.undef"))
|
|
|
|
# Nested dict
|
|
print("l.p2.p3 = ", js("l.p2.p3"))
|
|
assert (l['p2']['p3'] == js("l.p2.p3"))
|
|
|
|
# Dict assigned from JS - Need to use .json() to unwrap in Python
|
|
js("l.c = {d: 4}")
|
|
print("l.c = ", js("l.c"))
|
|
print("l.c.d = ", js("l.c.d"))
|
|
print("l.c = ", l['c'].json())
|
|
|
|
# List
|
|
print("l.p4[1] =", js("l.p4[1]"))
|
|
assert (js("l.p4[1]") == l['p4'][1])
|
|
print("calling l.p4[3].fn('called')")
|
|
js("l.p4[3].fn('called')")
|
|
|
|
# THIS FAILS - p4 was copied and the original variable is never referenced.
|
|
js("l.p4.push('added')")
|
|
print("l.p4 = ", l['p4'])
|
|
|
|
# Python Object accesss
|
|
print("l.p5 =", js("l.p5"))
|
|
print("l.p5.a1 =", js("l.p5.a1"))
|
|
assert (l['p5'].a1 == js("l.p5.a1"))
|
|
print("calling l.p5.fn(444)")
|
|
js("l.p5.fn(444)")
|
|
|
|
# Print the global variables - will see anonymous variables
|
|
pp(js._globals) |