-
Notifications
You must be signed in to change notification settings - Fork 94
Expand file tree
/
Copy pathpythonpythonjs.py
More file actions
287 lines (236 loc) · 8.83 KB
/
pythonpythonjs.py
File metadata and controls
287 lines (236 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# PythonJS Low Level Runtime
# by Amirouche Boubekki and Brett Hartshorn - copyright 2013
# License: "New BSD"
__NULL_OBJECT__ = Object.create( null )
if 'window' in this and 'document' in this:
__NODEJS__ = False
pythonjs = {}
else:
## note, we can not test for: '"process" in this' or '"process" in global'
## make sure we are really inside NodeJS by letting this fail, and halting the program.
__NODEJS__ = True
print process.title
print process.version
def jsrange(num):
"""Emulates Python's range function"""
var(i, r)
i = 0
r = []
while i < num:
r.push(i)
i = i + 1
return r
def create_array():
"""Used to fix a bug/feature of Javascript where new Array(number)
created a array with number of undefined elements which is not
what we want"""
var(array)
array = []
for i in jsrange(arguments.length):
array.push(arguments[i])
return array
def adapt_arguments(handler):
"""Useful to transform Javascript arguments to Python arguments"""
def func():
handler(Array.prototype.slice.call(arguments))
return func
def get_attribute(object, attribute):
"""Retrieve an attribute, method, property, or wrapper function.
method are actually functions which are converted to methods by
prepending their arguments with the current object. Properties are
not functions!
DOM support:
http://stackoverflow.com/questions/14202699/document-createelement-not-working
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
Direct JavaScript Calls:
if an external javascript function is found, and it was not a wrapper that was generated here,
check the function for a 'cached_wrapper' attribute, if none is found then generate a new
wrapper, cache it on the function, and return the wrapper.
"""
if attribute == '__call__':
if JS("{}.toString.call(object) === '[object Function]'"):
if JS("object.pythonscript_function === true"):
return object
elif JS("object.is_wrapper !== undefined"):
return object
else:
JS("var cached = object.cached_wrapper")
if cached:
return cached
else:
def wrapper(args,kwargs): return object.apply(None, args)
wrapper.is_wrapper = True
object.cached_wrapper = wrapper
return wrapper
var(attr)
attr = object[attribute] ## this could be a javascript object with cached method
if __NODEJS__ is False:
if JS("object instanceof HTMLDocument"):
#print 'DYNAMIC wrapping HTMLDocument'
if JS("typeof(attr) === 'function'"):
def wrapper(args,kwargs): return attr.apply(object, args)
wrapper.is_wrapper = True
return wrapper
else:
return attr
elif JS("object instanceof HTMLElement"):
#print 'DYNAMIC wrapping HTMLElement'
if JS("typeof(attr) === 'function'"):
def wrapper(args,kwargs): return attr.apply(object, args)
wrapper.is_wrapper = True
return wrapper
else:
return attr
#if attribute in object: ## in test not allowed with javascript-string
if attr is not None: ## what about cases where attr is None?
if JS("typeof(attr) === 'function' && attr.pythonscript_function === undefined && attr.is_wrapper === undefined"):
## to avoid problems with other generated wrapper funcs not marked with:
## F.pythonscript_function or F.is_wrapper, we could check if object has these props:
## bases, __name__, __dict__, __call__
#print 'wrapping something external', object, attribute
def wrapper(args,kwargs): return attr.apply(object, args)
wrapper.is_wrapper = True
return wrapper
else:
return attr
var(__class__, __dict__, bases)
# next check object.__dict__ for attr, note that object could be a class, and classes have a __dict__
__dict__ = object.__dict__
if __dict__:
attr = __dict__[attribute]
if attr != None:
return attr
# next check for object.__class__
__class__ = object.__class__
if __class__: ## at this point we can assume we are dealing with a pythonjs class instance
if attribute in __class__.__properties__: ## @property decorators
return __class__.__properties__[ attribute ]['get']( [object], JSObject() )
__dict__ = __class__.__dict__
attr = __dict__[attribute]
if attribute in __dict__:
if JS("{}.toString.call(attr) === '[object Function]'"):
def method():
var(args)
args = Array.prototype.slice.call(arguments)
if (JS('args[0] instanceof Array') and JS("{}.toString.call(args[1]) === '[object Object]'") and args.length == 2):
pass
else:
# in the case where the method was submitted to javascript code
# put the arguments in order to be processed by PythonJS
args = [args, JSObject()]
args[0].splice(0, 0, object)
return attr.apply(None, args) ## should we bind `this` here so callback can use this?
method.is_wrapper = True
object[attribute] = method ## cache method - we assume that methods do not change
return method
else:
return attr
bases = __class__.__bases__
for base in bases:
attr = _get_upstream_attribute(base, attribute)
if attr:
if JS("{}.toString.call(attr) === '[object Function]'"):
def method():
var(args)
args = Array.prototype.slice.call(arguments)
if (JS('args[0] instanceof Array') and JS("{}.toString.call(args[1]) === '[object Object]'") and args.length == 2):
pass
else:
# in the case where the method was submitted to javascript code
# put the arguments in order to be processed by PythonJS
args = [args, JSObject()]
args[0].splice(0, 0, object)
return attr.apply(None, args)
method.is_wrapper = True
object[attribute] = method ## cache method - we assume that methods do not change
return method
else:
return attr
for base in bases: ## upstream property getters come before __getattr__
var( prop )
prop = _get_upstream_property(base, attribute)
if prop:
return prop['get']( [object], JSObject() )
if '__getattr__' in __dict__:
return __dict__['__getattr__']( [object, attribute], JSObject() )
for base in bases:
var( f )
f = _get_upstream_attribute(base, '__getattr__')
if f:
return f( [object, attribute], JSObject() )
if JS('object instanceof Array'):
if attribute == '__getitem__':
def wrapper(args,kwargs): return object[ args[0] ]
wrapper.is_wrapper = True
return wrapper
elif attribute == '__setitem__':
def wrapper(args,kwargs): object[ args[0] ] = args[1]
wrapper.is_wrapper = True
return wrapper
elif attribute == '__getitem__': ## this should be a JSObject - or anything else - is this always safe?
def wrapper(args,kwargs): return object[ args[0] ]
wrapper.is_wrapper = True
return wrapper
elif attribute == '__setitem__':
def wrapper(args,kwargs): object[ args[0] ] = args[1]
wrapper.is_wrapper = True
return wrapper
# raise AttributeError instead? or should we allow this? maybe we should be javascript style here and return undefined
return None
def _get_upstream_attribute(base, attr):
if attr in base.__dict__:
return base.__dict__[ attr ]
for parent in base.__bases__:
return _get_upstream_attribute(parent, attr)
def _get_upstream_property(base, attr):
if attr in base.__properties__:
return base.__properties__[ attr ]
for parent in base.__bases__:
return _get_upstream_property(parent, attr)
def set_attribute(object, attribute, value):
"""Set an attribute on an object by updating its __dict__ property"""
var(__dict__, __class__)
__class__ = object.__class__
#if __class__: ## TODO property setter
__dict__ = object.__dict__
if __dict__:
__dict__[attribute] = value
else:
object[attribute] = value
def get_arguments(signature, args, kwargs):
"""Based on ``signature`` and ``args``, ``kwargs`` parameters retrieve
the actual parameters.
This will set default keyword arguments and retrieve positional arguments
in kwargs if their called as such"""
if args is None:
args = []
if kwargs is None:
kwargs = JSObject()
out = JSObject()
# if the caller did not specify supplemental positional arguments e.g. *args in the signature
# raise an error
if args.length > signature.args.length:
if signature.vararg:
pass
else:
print 'ERROR args:', args, 'kwargs:', kwargs, 'sig:', signature
raise TypeError("Supplemental positional arguments provided but signature doesn't accept them")
j = 0
while j < signature.args.length:
name = signature.args[j]
if name in kwargs:
# value is provided as a keyword argument
out[name] = kwargs[name]
elif j < args.length:
# value is positional and within the signature length
out[name] = args[j]
elif name in signature.kwargs:
# value is not found before and is in signature.length
out[name] = signature.kwargs[name]
j += 1
args = args.slice(j) ## note that if this fails because args is not an array, then a pythonjs function was called from javascript in a bad way.
if signature.vararg:
out[signature.vararg] = args
if signature.varkwarg:
out[signature.varkwarg] = kwargs
return out