Mercurial > scratch
comparison pydershell/pydershell.py @ 12:f024e41d0fb9
More refactoring, a bit more documentation.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sun, 06 Sep 2009 14:25:10 -0700 |
parents | 74f27983a350 |
children | 2a3313bfe574 |
comparison
equal
deleted
inserted
replaced
11:74f27983a350 | 12:f024e41d0fb9 |
---|---|
2 | 2 |
3 import sys | 3 import sys |
4 import time | 4 import time |
5 import threading | 5 import threading |
6 import traceback | 6 import traceback |
7 import weakref | |
8 | |
7 import pydermonkey | 9 import pydermonkey |
8 | 10 |
11 class ContextWatchdogThread(threading.Thread): | |
12 """ | |
13 Watches active JS contexts and triggers their operation callbacks | |
14 at a regular interval. | |
15 """ | |
16 | |
17 def __init__(self, interval=0.25): | |
18 threading.Thread.__init__(self) | |
19 self._lock = threading.Lock() | |
20 self._stop = threading.Event() | |
21 self._contexts = [] | |
22 self.interval = interval | |
23 self.setDaemon(True) | |
24 | |
25 def add_context(self, cx): | |
26 self._lock.acquire() | |
27 try: | |
28 self._contexts.append(weakref.ref(cx)) | |
29 finally: | |
30 self._lock.release() | |
31 | |
32 def join(self): | |
33 self._stop.set() | |
34 threading.Thread.join(self) | |
35 | |
36 def run(self): | |
37 while not self._stop.isSet(): | |
38 time.sleep(self.interval) | |
39 new_list = [] | |
40 self._lock.acquire() | |
41 try: | |
42 for weakcx in self._contexts: | |
43 cx = weakcx() | |
44 if cx: | |
45 new_list.append(weakcx) | |
46 cx.trigger_operation_callback() | |
47 self._contexts = new_list | |
48 finally: | |
49 self._lock.release() | |
50 | |
51 # Create a global watchdog. | |
52 watchdog = ContextWatchdogThread() | |
53 watchdog.start() | |
54 | |
9 class InternalError(BaseException): | 55 class InternalError(BaseException): |
56 """ | |
57 Represents an error in a JS-wrapped Python function that wasn't | |
58 expected to happen; because it's derived from BaseException, it | |
59 unrolls the whole JS/Python stack so that the error can be | |
60 reported to the outermost calling code. | |
61 """ | |
62 | |
10 def __init__(self): | 63 def __init__(self): |
11 BaseException.__init__(self) | 64 BaseException.__init__(self) |
12 self.exc_info = sys.exc_info() | 65 self.exc_info = sys.exc_info() |
13 | 66 |
14 rt = pydermonkey.Runtime() | 67 def safejsfunc(cx, on_obj, name=None): |
15 cx = rt.new_context() | 68 """ |
16 globalobj = cx.new_object() | 69 Exposes the decorated Python function on the given JS object. |
17 cx.init_standard_classes(globalobj) | |
18 | 70 |
19 def safejsfunc(cx, on_obj, name=None): | 71 Any unexpected exceptions raised by the function will be |
72 re-raised as InternalError exceptions. | |
73 """ | |
74 | |
20 def make_wrapper(func): | 75 def make_wrapper(func): |
21 if name is None: | 76 if name is None: |
22 func_name = func.__name__ | 77 func_name = func.__name__ |
23 else: | 78 else: |
24 func_name = name | 79 func_name = name |
35 cx.new_function(wrapper, func_name) | 90 cx.new_function(wrapper, func_name) |
36 ) | 91 ) |
37 return func | 92 return func |
38 return make_wrapper | 93 return make_wrapper |
39 | 94 |
40 @safejsfunc(cx, globalobj) | 95 def format_stack(js_stack): |
41 def foo(cx, this, args): | 96 """ |
42 return cx.call_function(this, args[0], ()) | 97 Returns a formatted Python-esque stack traceback of the given |
98 JS stack. | |
99 """ | |
43 | 100 |
44 @safejsfunc(cx, globalobj, 'print') | 101 STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s" |
45 def jsprint(cx, this, args): | |
46 if len(args) > 0: | |
47 print args[0] | |
48 | 102 |
49 def opcb(cx): | |
50 # Don't do anything; if a keyboard interrupt was triggered, | |
51 # it'll get raised here automatically. | |
52 pass | |
53 | |
54 cx.set_operation_callback(opcb) | |
55 | |
56 class State(object): | |
57 def __init__(self): | |
58 self.curr_exc = None | |
59 self.curr_tb = None | |
60 self.curr_js_stack = None | |
61 | |
62 state = State() | |
63 | |
64 def throwhook(cx): | |
65 curr_exc = cx.get_pending_exception() | |
66 if state.curr_exc != curr_exc: | |
67 state.curr_exc = curr_exc | |
68 state.py_stack = traceback.extract_stack() | |
69 state.js_stack = cx.get_stack() | |
70 | |
71 cx.set_throw_hook(throwhook) | |
72 | |
73 def watchdog(): | |
74 while 1: | |
75 time.sleep(0.25) | |
76 cx.trigger_operation_callback() | |
77 | |
78 thread = threading.Thread(target=watchdog) | |
79 thread.setDaemon(True) | |
80 thread.start() | |
81 | |
82 filename = 'test.js' | |
83 | |
84 def make_stack(js_stack): | |
85 lines = [] | 103 lines = [] |
86 while js_stack: | 104 while js_stack: |
87 if js_stack['script']: | 105 script = js_stack['script'] |
88 script = js_stack['script'] | 106 function = js_stack['function'] |
89 thing = dict(filename = script.filename, | 107 if script: |
108 frameinfo = dict(filename = script.filename, | |
90 lineno = js_stack['lineno'], | 109 lineno = js_stack['lineno'], |
91 name = '<module>') | 110 name = '<module>') |
92 elif js_stack['function'] and not js_stack['function'].is_python: | 111 elif function and not function.is_python: |
93 func = js_stack['function'] | 112 frameinfo = dict(filename = function.filename, |
94 thing = dict(filename = func.filename, | |
95 lineno = js_stack['lineno'], | 113 lineno = js_stack['lineno'], |
96 name = func.name) | 114 name = function.name) |
97 else: | 115 else: |
98 thing = None | 116 frameinfo = None |
99 if thing: | 117 if frameinfo: |
100 lines.insert(0, " File \"%(filename)s\", line %(lineno)d, in %(name)s" % thing) | 118 lines.insert(0, STACK_LINE % frameinfo) |
101 js_stack = js_stack['caller'] | 119 js_stack = js_stack['caller'] |
102 lines.insert(0, "Traceback (most recent call last):") | 120 lines.insert(0, "Traceback (most recent call last):") |
103 return '\n'.join(lines) | 121 return '\n'.join(lines) |
104 | 122 |
105 try: | 123 class JSRuntime(object): |
106 cx.evaluate_script(globalobj, open(filename).read(), filename, 1) | 124 """ |
107 except pydermonkey.error, e: | 125 A JS runtime capable of loading and executing scripts. |
108 print make_stack(state.js_stack) | 126 """ |
109 print e.args[1] | 127 |
110 except InternalError, e: | 128 def __init__(self, watchdog=watchdog): |
111 print "An internal error occurred." | 129 rt = pydermonkey.Runtime() |
112 traceback.print_tb(e.exc_info[2]) | 130 cx = rt.new_context() |
113 print e.exc_info[1] | 131 globalobj = cx.new_object() |
132 cx.init_standard_classes(globalobj) | |
133 | |
134 @safejsfunc(cx, globalobj) | |
135 def foo(cx, this, args): | |
136 return cx.call_function(this, args[0], ()) | |
137 | |
138 @safejsfunc(cx, globalobj, 'print') | |
139 def jsprint(cx, this, args): | |
140 if len(args) > 0: | |
141 print args[0] | |
142 | |
143 cx.set_operation_callback(self._opcb) | |
144 cx.set_throw_hook(self._throwhook) | |
145 watchdog.add_context(cx) | |
146 | |
147 self.rt = rt | |
148 self.cx = cx | |
149 self.globalobj = globalobj | |
150 self.curr_exc = None | |
151 self.py_stack = None | |
152 self.js_stack = None | |
153 | |
154 def _opcb(self, cx): | |
155 # Don't do anything; if a keyboard interrupt was triggered, | |
156 # it'll get raised here automatically. | |
157 pass | |
158 | |
159 def _throwhook(self, cx): | |
160 curr_exc = cx.get_pending_exception() | |
161 if self.curr_exc != curr_exc: | |
162 self.curr_exc = curr_exc | |
163 self.py_stack = traceback.extract_stack() | |
164 self.js_stack = cx.get_stack() | |
165 | |
166 def run_script(self, filename): | |
167 contents = open(filename).read() | |
168 cx = self.cx | |
169 try: | |
170 cx.evaluate_script(self.globalobj, contents, | |
171 filename, 1) | |
172 except pydermonkey.error, e: | |
173 print format_stack(self.js_stack) | |
174 print e.args[1] | |
175 except InternalError, e: | |
176 print "An internal error occurred." | |
177 traceback.print_tb(e.exc_info[2]) | |
178 print e.exc_info[1] | |
179 | |
180 if __name__ == '__main__': | |
181 runtime = JSRuntime() | |
182 runtime.run_script('test.js') |