Script scripterng_runtime
[hide private]
[frames] | no frames]

Source Code for Script scripterng_runtime

  1  """ 
  2  This runtime module contains everything about running  
  3  Python and QtScript scripts inside Scribus. 
  4   
  5  Look at run_filename for details. 
  6  """ 
  7  import os 
  8  import hashlib 
  9  from ConfigParser import ConfigParser 
 10   
 11  import sip 
 12  from PyQt4.QtCore import QThread, QObject, QVariant 
 13  from PyQt4.QtGui import qApp,  QMessageBox 
 14  from PyQt4.QtScript import QScriptEngine, QScriptValue 
 15   
 16  from safe_eval import checkCode 
 17  import permitdlg 
 18   
 19  import __main__ 
 20   
 21  from inspect import getargspec 
22 23 -class RuntimeConfig(ConfigParser):
24 25 # I cannot use ScripterNG.preferences because a safe script could 26 # mark other scripts as safe (=allowed) although they use import and 27 # other (possible) dangerous stuff.. 28 # Perhaps I will find a better solution later. 29
30 - def __init__(self):
31 ConfigParser.__init__(self) 32 # XXX better use ScPaths->... 33 path = os.path.expanduser("~/.scribus/scripterng") 34 if not os.path.exists(path): 35 os.makedirs(path) 36 self.filename = os.path.join(path, "runtime.cfg") 37 self.read([self.filename])
38 39
40 - def save(self):
41 fp = open(self.filename, "w") 42 self.write(fp) 43 fp.close()
44 45
46 - def set(self, section, key, value):
47 if not self.has_section(section): 48 self.add_section(section) 49 ConfigParser.set(self, section, key, value) 50 self.save()
51 52
53 - def getbool(self, section, key):
54 value = self.get(section, key).strip().lower() 55 if value and value in ["true", "on", "yes", "1"]: 56 return True 57 elif value and value in ["false", "off", "no", "0"]: 58 return False 59 else: 60 raise ValueError, "Invalid boolean value %r" % value
61 62 63 runtime_config = RuntimeConfig() 64 65 extension_namespace = __main__.__dict__ 66 67 68 qts_engine = None
69 70 # XXX share namespaces of Python and QtScript 71 72 -class QtSRuntimeError(Exception):
73 pass
74
75 76 -def qts_func_decorator(func):
77 def wrapper(context, engine): 78 args = [] 79 (fargs, fvarargs, fvarkw, fdefaults) = getargspec(func) 80 if len(fargs) and fargs[0] == "self": 81 args.append(context.thisObject()) 82 for i in xrange(context.argumentCount()): 83 args.append(context.argument(i)) 84 try: 85 result = func(*args) 86 except Exception, e: 87 # XXX correct behaviour? 88 # http://lists.trolltech.com/qt-interest/2007-06/thread00892-0.html 89 return context.throwValue(QScriptValue(engine, str(e))) 90 if result: 91 return QScriptValue(engine, result) 92 else: 93 return QScriptValue()
94 return wrapper 95
96 97 @qts_func_decorator 98 -def alert(msg_qsv):
99 msg = msg_qsv.toString() 100 QMessageBox.information(ScripterNG.dialogs.mainWindow.qt, "Alert", msg)
101
102 103 -def update_qs_namespace(engine, ns):
104 go = engine.globalObject() 105 for name, value in ns.items(): 106 if isinstance(value, QObject): 107 value = engine.newQObject(value) 108 elif callable(value): 109 value = engine.newFunction(value) 110 #elif not isinstance(value, QScriptValue): 111 # value = QScriptValue(engine, value) 112 go.setProperty(name, value)
113
114 115 -def newQScriptEngine():
116 engine = QScriptEngine() 117 update_qs_namespace(engine, 118 { 119 "Application": qApp, 120 "ScripterNG": ScripterNG.qt, 121 "alert": alert 122 }) 123 return engine
124
125 126 -def run_qtscript(filename, subroutine=None, extension=False):
127 global qts_engine 128 if not extension: 129 engine = newQScriptEngine() 130 else: 131 engine = qts_engine = qts_engine or newQScriptEngine() 132 code = open(filename).read() 133 engine.clearExceptions() 134 result = engine.evaluate(code) 135 engine.collectGarbage() 136 if not engine.hasUncaughtException() and subroutine: 137 sub = engine.globalObject().property(subroutine) 138 sub.call() 139 if engine.hasUncaughtException(): 140 bt = engine.uncaughtExceptionBacktrace() 141 raise QtSRuntimeError("%s\nTraceback:\%s" % ( 142 str(engine.uncaughtException().toString()), 143 "\n".join([" %s" % l for l in list(bt)])))
144
145 146 -def hash_source(filename, source=None):
147 # I gueses sha256 is safe enough without collisions? 148 source = source or open(filename).read() 149 return "%s:%s:%s" % ( 150 os.path.basename(filename), len(filename), hashlib.sha256(source).hexdigest())
151
152 153 -def check_python(filename):
154 filename = os.path.abspath(os.path.expanduser(filename)) 155 path = os.path.dirname(filename) 156 # Allow files from global autoload folder by default. 157 # XXX Good idea? 158 if path == os.path.join(ScripterNG.path, "autoload"): 159 return True 160 code = open(filename).read() 161 h = hash_source(filename, code) 162 if runtime_config.has_option("permissions", h): 163 return runtime_config.getbool("permissions", h) 164 165 problems = checkCode(code) 166 if problems and len(problems) == 1 and isinstance(problems[0], SyntaxError): 167 return True # let's ignore it and let excepthook hande the error later 168 elif problems: 169 ok = permitdlg.ask(filename, problems) 170 if ok == -2: # deny and remember 171 runtime_config.set("permissions", h, False) 172 return False 173 elif ok == 2: # deny 174 return False 175 elif ok == -1: # allow and remember 176 runtime_config.set("permissions", h, True) 177 elif ok == 1: # allow but now remember 178 pass 179 else: 180 raise ValueError, "Inknown return code for permission dialog: %r" % ok 181 return True
182
183 184 -def run_python(filename, subroutine=None, extension=False):
185 if not extension: 186 namespace = { 187 __name__: "__scribus__", 188 __file__: filename 189 } 190 else: 191 namespace = extension_namespace 192 if not check_python(filename): 193 return 194 execfile(filename, namespace) 195 if subroutine: 196 sub = namespace[subroutine] 197 sub() 198 if not extension: 199 del namespace
200 201 202 threads = []
203 204 -class RunThread(QThread):
205 206
207 - def __init__(self, func, *args):
208 QThread.__init__(self, ScripterNG.qt) 209 self.func = func 210 self.args = args
211 212
213 - def run(self):
214 threads.append(self) 215 self.func(*self.args) 216 threads.remove(self)
217
218 219 -def run_background(func, *args):
220 thread = RunThread(func, *args) 221 thread.start() 222 # XXX: connect done signal with cleanup? 223 return thread
224
225 226 227 -def mark_keep():
228 """ 229 mark every child of ScripterNG.collector to keep 230 """ 231 for child in ScripterNG.collector.children(): 232 if hasattr(child, "qt"): child = child.qt 233 child.setProperty("keep", QVariant(True))
234
235 236 237 -def cleanup():
238 """ 239 delete every child which is not marked as keep 240 """ 241 for child in ScripterNG.collector.children(): 242 if hasattr(child, "qt"): child = child.qt 243 v = child.property("keep") 244 if v and v.toBool() == True: 245 #print "Keeping", child 246 continue 247 print "* deleting collected", child 248 sip.delete(child)
249
250 251 252 -def run_filename(filename, subroutine=None, extension=False, background=False):
253 """ 254 Call this function to run a script and nothing else. 255 It will do everything for you, including garbage collection 256 for QtScript (very simple implementation, see mark_keep and cleanup). 257 Running as extension uses the __main__ namespace and does not 258 delete objects after execution. 259 Running in background as a thread is not much tested and 260 should only be used for non-GUI scripts. 261 """ 262 mark_keep() 263 if background: 264 run_func = run_background 265 else: 266 run_func = lambda func, *args: func(*args) 267 if filename.endswith((".sqts", ".qts", ".sjs", ".js")): 268 run_func(run_qtscript, filename, subroutine, extension) 269 else: 270 run_func(run_python, filename, subroutine, extension) 271 if not background and not extension: 272 # XXX: make sure this is called if an exception occures... 273 cleanup()
274