| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2010-2015 Roberto Longobardi |
|---|
| 4 | # Copyright (C) 2016-2021 Cinc |
|---|
| 5 | # |
|---|
| 6 | # This file is part of the Test Manager plugin for Trac. |
|---|
| 7 | # |
|---|
| 8 | # This software is licensed as described in the file COPYING, which |
|---|
| 9 | # you should have received as part of this distribution. The terms |
|---|
| 10 | # are also available at: |
|---|
| 11 | # https://trac-hacks.org/wiki/TestManagerForTracPluginLicense |
|---|
| 12 | # |
|---|
| 13 | # Author: Roberto Longobardi <otrebor.dev@gmail.com> |
|---|
| 14 | # |
|---|
| 15 | |
|---|
| 16 | import os |
|---|
| 17 | import shutil |
|---|
| 18 | import sys |
|---|
| 19 | import traceback |
|---|
| 20 | |
|---|
| 21 | from trac.core import * |
|---|
| 22 | |
|---|
| 23 | |
|---|
| 24 | def formatExceptionInfo(maxTBlevel=5): |
|---|
| 25 | cla, exc, trbk = sys.exc_info() |
|---|
| 26 | excName = cla.__name__ |
|---|
| 27 | |
|---|
| 28 | try: |
|---|
| 29 | excArgs = exc.__dict__["args"] |
|---|
| 30 | except KeyError: |
|---|
| 31 | excArgs = "<no args>" |
|---|
| 32 | |
|---|
| 33 | excTb = traceback.format_tb(trbk, maxTBlevel) |
|---|
| 34 | |
|---|
| 35 | tracestring = "" |
|---|
| 36 | for step in excTb: |
|---|
| 37 | tracestring += step + "\n" |
|---|
| 38 | |
|---|
| 39 | return "Error name: %s\nArgs: %s\nTraceback:\n%s" % (excName, excArgs, tracestring) |
|---|
| 40 | |
|---|
| 41 | |
|---|
| 42 | def to_any_timestamp(date_obj): |
|---|
| 43 | from trac.util.datefmt import to_utimestamp |
|---|
| 44 | return to_utimestamp(date_obj) |
|---|
| 45 | |
|---|
| 46 | |
|---|
| 47 | def from_any_timestamp(ts): |
|---|
| 48 | from trac.util.datefmt import from_utimestamp |
|---|
| 49 | return from_utimestamp(ts) |
|---|
| 50 | |
|---|
| 51 | |
|---|
| 52 | def to_list(params): |
|---|
| 53 | result = [] |
|---|
| 54 | |
|---|
| 55 | for i in params: |
|---|
| 56 | if isinstance(i, list): |
|---|
| 57 | for v in i: |
|---|
| 58 | result.append(v) |
|---|
| 59 | else: |
|---|
| 60 | result.append(i) |
|---|
| 61 | |
|---|
| 62 | return tuple(result) |
|---|
| 63 | |
|---|
| 64 | |
|---|
| 65 | def get_dictionary_from_string(str): |
|---|
| 66 | result = {} |
|---|
| 67 | |
|---|
| 68 | sub = str.partition('{')[2].rpartition('}')[0] |
|---|
| 69 | tokens = sub.split(",") |
|---|
| 70 | |
|---|
| 71 | for tok in tokens: |
|---|
| 72 | name = remove_quotes(tok.partition(':')[0]) |
|---|
| 73 | value = remove_quotes(tok.partition(':')[2]) |
|---|
| 74 | |
|---|
| 75 | result[name] = value |
|---|
| 76 | |
|---|
| 77 | return result |
|---|
| 78 | |
|---|
| 79 | |
|---|
| 80 | def get_string_from_dictionary(dictionary, values=None): |
|---|
| 81 | if values is None: |
|---|
| 82 | values = dictionary |
|---|
| 83 | |
|---|
| 84 | result = '{' |
|---|
| 85 | for i, k in enumerate(dictionary): |
|---|
| 86 | # TODO: send a patch for the following line |
|---|
| 87 | # result += "'"+k+"':'"+values[k]+"'" # old code not working for int values |
|---|
| 88 | result += "'%s':'%s'" % (k, values[k]) |
|---|
| 89 | if i < len(dictionary)-1: |
|---|
| 90 | result += "," |
|---|
| 91 | |
|---|
| 92 | result += '}' |
|---|
| 93 | |
|---|
| 94 | return result |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | def remove_quotes(str, quote='\''): |
|---|
| 98 | return str.partition(quote)[2].rpartition(quote)[0] |
|---|
| 99 | |
|---|
| 100 | |
|---|
| 101 | def compatible_domain_functions(domain, function_name_list): |
|---|
| 102 | return lambda x: x, lambda x: x, lambda x: x, lambda x: x |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | def get_timestamp_db_type(): |
|---|
| 106 | return 'int64' |
|---|
| 107 | |
|---|
| 108 | |
|---|
| 109 | def db_insert_or_ignore(env, tablename, propname, value, db=None): |
|---|
| 110 | if db_get_config_property(env, tablename, propname) is None: |
|---|
| 111 | db_set_config_property(env, tablename, propname, value) |
|---|
| 112 | |
|---|
| 113 | |
|---|
| 114 | def db_get_config_property(env, tablename, propname, db=None): |
|---|
| 115 | sql = "SELECT value FROM %s WHERE propname=%%s" % tablename |
|---|
| 116 | row = None |
|---|
| 117 | with env.db_query as db: |
|---|
| 118 | cursor = db.cursor() |
|---|
| 119 | try: |
|---|
| 120 | cursor.execute(sql, (propname,)) |
|---|
| 121 | row = cursor.fetchone() |
|---|
| 122 | except: |
|---|
| 123 | pass |
|---|
| 124 | |
|---|
| 125 | if not row or len(row) == 0: |
|---|
| 126 | return None |
|---|
| 127 | |
|---|
| 128 | return row[0] |
|---|
| 129 | |
|---|
| 130 | |
|---|
| 131 | def db_set_config_property(env, tablename, propname, value): |
|---|
| 132 | with env.db_transaction as db: |
|---|
| 133 | cursor = db.cursor() |
|---|
| 134 | |
|---|
| 135 | sql = "SELECT COUNT(*) FROM %s WHERE propname = %%s" % tablename |
|---|
| 136 | cursor.execute(sql, (propname,)) |
|---|
| 137 | |
|---|
| 138 | row = cursor.fetchone() |
|---|
| 139 | if row is not None and int(row[0]) > 0: |
|---|
| 140 | cursor.execute(""" |
|---|
| 141 | UPDATE %s |
|---|
| 142 | SET value = %%s |
|---|
| 143 | WHERE propname = %%s |
|---|
| 144 | """ % tablename, (str(value), propname)) |
|---|
| 145 | else: |
|---|
| 146 | cursor.execute(""" |
|---|
| 147 | INSERT INTO %s (propname,value) |
|---|
| 148 | VALUES (%%s,%%s) |
|---|
| 149 | """ % tablename, (propname, str(value))) |
|---|
| 150 | |
|---|
| 151 | return True |
|---|
| 152 | |
|---|
| 153 | |
|---|
| 154 | def list_available_tables(dburi, cursor): |
|---|
| 155 | if dburi.startswith('sqlite:'): |
|---|
| 156 | query = """ |
|---|
| 157 | SELECT name FROM sqlite_master |
|---|
| 158 | WHERE type='table' AND NOT name='sqlite_sequence' |
|---|
| 159 | """ |
|---|
| 160 | elif dburi.startswith('postgres:'): |
|---|
| 161 | query = """ |
|---|
| 162 | SELECT tablename FROM pg_tables |
|---|
| 163 | WHERE schemaname = ANY (current_schemas(false)) |
|---|
| 164 | """ |
|---|
| 165 | elif dburi.startswith('mysql:'): |
|---|
| 166 | query = "SHOW TABLES" |
|---|
| 167 | else: |
|---|
| 168 | raise TracError('Unsupported %s database' % dburi.split(':')[0]) |
|---|
| 169 | cursor.execute(query) |
|---|
| 170 | |
|---|
| 171 | return sorted([row[0] for row in cursor]) |
|---|
| 172 | |
|---|
| 173 | |
|---|
| 174 | def fix_base_location(req): |
|---|
| 175 | return req.href('/').rstrip('/') |
|---|
| 176 | |
|---|
| 177 | |
|---|
| 178 | ## {{{ http://code.activestate.com/recipes/550804/ (r2) |
|---|
| 179 | # The list of symbols that are included by default in the generated |
|---|
| 180 | # function's environment |
|---|
| 181 | SAFE_SYMBOLS = ["list", "dict", "tuple", "set", "long", "float", "object", |
|---|
| 182 | "bool", "callable", "True", "False", "dir", |
|---|
| 183 | "frozenset", "getattr", "hasattr", "abs", "cmp", "complex", |
|---|
| 184 | "divmod", "id", "pow", "round", "slice", "vars", |
|---|
| 185 | "hash", "hex", "int", "isinstance", "issubclass", "len", |
|---|
| 186 | "map", "filter", "max", "min", "oct", "chr", "ord", "range", |
|---|
| 187 | "reduce", "repr", "str", "type", "zip", "xrange", "None", |
|---|
| 188 | "Exception", "KeyboardInterrupt"] |
|---|
| 189 | # Also add the standard exceptions |
|---|
| 190 | __bi = __builtins__ |
|---|
| 191 | if type(__bi) is not dict: |
|---|
| 192 | __bi = __bi.__dict__ |
|---|
| 193 | for k in __bi: |
|---|
| 194 | if k.endswith("Error") or k.endswith("Warning"): |
|---|
| 195 | SAFE_SYMBOLS.append(k) |
|---|
| 196 | del __bi |
|---|
| 197 | |
|---|
| 198 | |
|---|
| 199 | def createFunctionFromString(sourceCode, args="", additional_symbols=dict()): |
|---|
| 200 | """ |
|---|
| 201 | Create a python function from the given source code |
|---|
| 202 | |
|---|
| 203 | \param sourceCode A python string containing the core of the |
|---|
| 204 | function. Might include the return statement (or not), definition of |
|---|
| 205 | local functions, classes, etc. Indentation matters ! |
|---|
| 206 | |
|---|
| 207 | \param args The string representing the arguments to put in the function's |
|---|
| 208 | prototype, such as "a, b", or "a=12, b", |
|---|
| 209 | or "a=12, b=dict(akey=42, another=5)" |
|---|
| 210 | |
|---|
| 211 | \param additional_symbols A dictionary variable name => |
|---|
| 212 | variable/funcion/object to include in the generated function's |
|---|
| 213 | closure |
|---|
| 214 | |
|---|
| 215 | The sourceCode will be executed in a restricted environment, |
|---|
| 216 | containing only the python builtins that are harmless (such as map, |
|---|
| 217 | hasattr, etc.). To allow the function to access other modules or |
|---|
| 218 | functions or objects, use the additional_symbols parameter. For |
|---|
| 219 | example, to allow the source code to access the re and sys modules, |
|---|
| 220 | as well as a global function F named afunction in the sourceCode and |
|---|
| 221 | an object OoO named ooo in the sourceCode, specify: |
|---|
| 222 | additional_symbols = dict(re=re, sys=sys, afunction=F, ooo=OoO) |
|---|
| 223 | |
|---|
| 224 | \return A python function implementing the source code. It can be |
|---|
| 225 | recursive: the (internal) name of the function being defined is: |
|---|
| 226 | __TheFunction__. Its docstring is the initial sourceCode string. |
|---|
| 227 | |
|---|
| 228 | Tests show that the resulting function does not have any calling |
|---|
| 229 | time overhead (-3% to +3%, probably due to system preemption aleas) |
|---|
| 230 | compared to normal python function calls. |
|---|
| 231 | """ |
|---|
| 232 | # Include the sourcecode as the code of a function __TheFunction__: |
|---|
| 233 | s = "def __TheFunction__(%s):\n" % args |
|---|
| 234 | s += "\t" + "\n\t".join(sourceCode.split('\n')) + "\n" |
|---|
| 235 | |
|---|
| 236 | # Byte-compilation (optional) |
|---|
| 237 | byteCode = compile(s, "<string>", 'exec') |
|---|
| 238 | |
|---|
| 239 | # Setup the local and global dictionaries of the execution |
|---|
| 240 | # environment for __TheFunction__ |
|---|
| 241 | bis = dict() # builtins |
|---|
| 242 | globs = dict() |
|---|
| 243 | locs = dict() |
|---|
| 244 | |
|---|
| 245 | # Setup a standard-compatible python environment |
|---|
| 246 | bis["locals"] = lambda: locs |
|---|
| 247 | bis["globals"] = lambda: globs |
|---|
| 248 | globs["__builtins__"] = bis |
|---|
| 249 | globs["__name__"] = "SUBENV" |
|---|
| 250 | globs["__doc__"] = sourceCode |
|---|
| 251 | |
|---|
| 252 | # Determine how the __builtins__ dictionary should be accessed |
|---|
| 253 | if type(__builtins__) is dict: |
|---|
| 254 | bi_dict = __builtins__ |
|---|
| 255 | else: |
|---|
| 256 | bi_dict = __builtins__.__dict__ |
|---|
| 257 | |
|---|
| 258 | # Include the safe symbols |
|---|
| 259 | for k in SAFE_SYMBOLS: |
|---|
| 260 | # try from current locals |
|---|
| 261 | try: |
|---|
| 262 | locs[k] = locals()[k] |
|---|
| 263 | continue |
|---|
| 264 | except KeyError: |
|---|
| 265 | pass |
|---|
| 266 | # Try from globals |
|---|
| 267 | try: |
|---|
| 268 | globs[k] = globals()[k] |
|---|
| 269 | continue |
|---|
| 270 | except KeyError: |
|---|
| 271 | pass |
|---|
| 272 | # Try from builtins |
|---|
| 273 | try: |
|---|
| 274 | bis[k] = bi_dict[k] |
|---|
| 275 | except KeyError: |
|---|
| 276 | # Symbol not available anywhere: silently ignored |
|---|
| 277 | pass |
|---|
| 278 | |
|---|
| 279 | # Include the symbols added by the caller, in the globals dictionary |
|---|
| 280 | globs.update(additional_symbols) |
|---|
| 281 | |
|---|
| 282 | # Finally execute the def __TheFunction__ statement: |
|---|
| 283 | eval(byteCode, globs, locs) |
|---|
| 284 | # As a result, the function is defined as the item __TheFunction__ |
|---|
| 285 | # in the locals dictionary |
|---|
| 286 | fct = locs["__TheFunction__"] |
|---|
| 287 | # Attach the function to the globals so that it can be recursive |
|---|
| 288 | del locs["__TheFunction__"] |
|---|
| 289 | globs["__TheFunction__"] = fct |
|---|
| 290 | # Attach the actual source code to the docstring |
|---|
| 291 | fct.__doc__ = sourceCode |
|---|
| 292 | return fct |
|---|
| 293 | ## end of http://code.activestate.com/recipes/550804/ }}} |
|---|
| 294 | |
|---|
| 295 | # Example: |
|---|
| 296 | # |
|---|
| 297 | # import sys, re |
|---|
| 298 | # |
|---|
| 299 | # f = createFunction("print a\nprint b", "a=3, b=4", |
|---|
| 300 | # additional_symbols = dict(re=re, sys=sys, |
|---|
| 301 | # afunction=F, ooo=OoO)) |
|---|
| 302 | # f() |
|---|
| 303 | # f(12) |
|---|
| 304 | # f(b=7, a=8) |
|---|
| 305 | # f(9, 10) |
|---|