2008-07-16 10:33:48 +00:00
|
|
|
import bpy, sys
|
2008-07-15 07:34:46 +00:00
|
|
|
import __builtin__, tokenize
|
2008-07-15 12:55:20 +00:00
|
|
|
from Blender.sys import time
|
2008-07-21 00:38:42 +00:00
|
|
|
from tokenize import generate_tokens, TokenError, \
|
2008-07-26 20:02:10 +00:00
|
|
|
COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
class ScriptDesc():
|
2008-07-26 20:02:10 +00:00
|
|
|
"""Describes a script through lists of further descriptor objects (classes,
|
|
|
|
defs, vars) and dictionaries to built-in types (imports). If a script has
|
|
|
|
not been fully parsed, its incomplete flag will be set. The time of the last
|
|
|
|
parse is held by the time field and the name of the text object from which
|
|
|
|
it was parsed, the name field.
|
|
|
|
"""
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
def __init__(self, name, imports, classes, defs, vars, incomplete=False):
|
|
|
|
self.name = name
|
|
|
|
self.imports = imports
|
|
|
|
self.classes = classes
|
|
|
|
self.defs = defs
|
|
|
|
self.vars = vars
|
|
|
|
self.incomplete = incomplete
|
|
|
|
self.time = 0
|
|
|
|
|
|
|
|
def set_time(self):
|
|
|
|
self.time = time()
|
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
class ClassDesc():
|
2008-07-26 20:02:10 +00:00
|
|
|
"""Describes a class through lists of further descriptor objects (defs and
|
|
|
|
vars). The name of the class is held by the name field and the line on
|
|
|
|
which it is defined is held in lineno.
|
|
|
|
"""
|
2008-07-21 00:38:42 +00:00
|
|
|
|
|
|
|
def __init__(self, name, defs, vars, lineno):
|
|
|
|
self.name = name
|
|
|
|
self.defs = defs
|
|
|
|
self.vars = vars
|
|
|
|
self.lineno = lineno
|
|
|
|
|
|
|
|
class FunctionDesc():
|
2008-07-26 20:02:10 +00:00
|
|
|
"""Describes a function through its name and list of parameters (name,
|
|
|
|
params) and the line on which it is defined (lineno).
|
|
|
|
"""
|
2008-07-21 00:38:42 +00:00
|
|
|
|
|
|
|
def __init__(self, name, params, lineno):
|
|
|
|
self.name = name
|
|
|
|
self.params = params
|
|
|
|
self.lineno = lineno
|
|
|
|
|
|
|
|
class VarDesc():
|
2008-07-26 20:02:10 +00:00
|
|
|
"""Describes a variable through its name and type (if ascertainable) and the
|
|
|
|
line on which it is defined (lineno). If no type can be determined, type
|
|
|
|
will equal None.
|
|
|
|
"""
|
2008-07-21 00:38:42 +00:00
|
|
|
|
|
|
|
def __init__(self, name, type, lineno):
|
|
|
|
self.name = name
|
|
|
|
self.type = type # None for unknown (supports: dict/list/str)
|
|
|
|
self.lineno = lineno
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
# Context types
|
2008-07-21 00:38:42 +00:00
|
|
|
CTX_UNSET = -1
|
|
|
|
CTX_NORMAL = 0
|
|
|
|
CTX_SINGLE_QUOTE = 1
|
|
|
|
CTX_DOUBLE_QUOTE = 2
|
|
|
|
CTX_COMMENT = 3
|
2008-07-15 07:34:46 +00:00
|
|
|
|
2008-07-26 20:02:10 +00:00
|
|
|
# Special time period constants
|
2008-07-18 11:00:34 +00:00
|
|
|
AUTO = -1
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
# Python keywords
|
|
|
|
KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
|
|
|
|
'or', 'with', 'assert', 'else', 'if', 'pass', 'yield',
|
|
|
|
'break', 'except', 'import', 'print', 'class', 'exec', 'in',
|
|
|
|
'raise', 'continue', 'finally', 'is', 'return', 'def', 'for',
|
|
|
|
'lambda', 'try' ]
|
|
|
|
|
2008-07-16 10:33:48 +00:00
|
|
|
ModuleType = type(__builtin__)
|
2008-07-18 11:00:34 +00:00
|
|
|
NoneScriptDesc = ScriptDesc('', dict(), dict(), dict(), dict(), True)
|
|
|
|
|
2008-07-16 10:33:48 +00:00
|
|
|
_modules = dict([(n, None) for n in sys.builtin_module_names])
|
|
|
|
_modules_updated = 0
|
2008-07-18 11:00:34 +00:00
|
|
|
_parse_cache = dict()
|
|
|
|
|
|
|
|
def get_cached_descriptor(txt, period=AUTO):
|
|
|
|
"""Returns the cached ScriptDesc for the specified Text object 'txt'. If the
|
|
|
|
script has not been parsed in the last 'period' seconds it will be reparsed
|
|
|
|
to obtain this descriptor.
|
|
|
|
|
|
|
|
Specifying AUTO for the period (default) will choose a period based on the
|
|
|
|
size of the Text object. Larger texts are parsed less often.
|
|
|
|
"""
|
|
|
|
|
|
|
|
global _parse_cache, NoneScriptDesc, AUTO
|
|
|
|
|
|
|
|
if period == AUTO:
|
|
|
|
m = txt.nlines
|
|
|
|
r = 1
|
|
|
|
while True:
|
|
|
|
m = m >> 2
|
|
|
|
if not m: break
|
|
|
|
r = r << 1
|
|
|
|
period = r
|
|
|
|
|
|
|
|
parse = True
|
2008-07-26 20:02:10 +00:00
|
|
|
key = hash(txt)
|
2008-07-18 11:00:34 +00:00
|
|
|
if _parse_cache.has_key(key):
|
|
|
|
desc = _parse_cache[key]
|
|
|
|
if desc.time >= time() - period:
|
|
|
|
parse = desc.incomplete
|
|
|
|
|
|
|
|
if parse:
|
2008-07-26 20:02:10 +00:00
|
|
|
desc = parse_text(txt)
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
return desc
|
|
|
|
|
|
|
|
def parse_text(txt):
|
|
|
|
"""Parses an entire script's text and returns a ScriptDesc instance
|
|
|
|
containing information about the script.
|
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
If the text is not a valid Python script (for example if brackets are left
|
|
|
|
open), parsing may fail to complete. However, if this occurs, no exception
|
|
|
|
is thrown. Instead the returned ScriptDesc instance will have its incomplete
|
|
|
|
flag set and information processed up to this point will still be accessible.
|
2008-07-18 11:00:34 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
txt.reset()
|
|
|
|
tokens = generate_tokens(txt.readline) # Throws TokenError
|
|
|
|
|
|
|
|
curl, cursor = txt.getCursorPos()
|
|
|
|
linen = curl + 1 # Token line numbers are one-based
|
|
|
|
|
|
|
|
imports = dict()
|
|
|
|
imp_step = 0
|
|
|
|
|
|
|
|
classes = dict()
|
|
|
|
cls_step = 0
|
|
|
|
|
|
|
|
defs = dict()
|
|
|
|
def_step = 0
|
|
|
|
|
|
|
|
vars = dict()
|
2008-07-21 00:38:42 +00:00
|
|
|
var1_step = 0
|
|
|
|
var2_step = 0
|
|
|
|
var3_step = 0
|
2008-07-18 11:00:34 +00:00
|
|
|
var_accum = dict()
|
|
|
|
var_forflag = False
|
|
|
|
|
|
|
|
indent = 0
|
|
|
|
prev_type = -1
|
|
|
|
prev_string = ''
|
|
|
|
incomplete = False
|
|
|
|
|
2008-07-26 20:02:10 +00:00
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
type, string, start, end, line = tokens.next()
|
|
|
|
except StopIteration:
|
|
|
|
break
|
|
|
|
except TokenError:
|
|
|
|
incomplete = True
|
|
|
|
break
|
2008-07-18 11:00:34 +00:00
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
# Skip all comments and line joining characters
|
|
|
|
if type == COMMENT or type == NL:
|
|
|
|
continue
|
|
|
|
|
2008-07-18 11:00:34 +00:00
|
|
|
#################
|
|
|
|
## Indentation ##
|
|
|
|
#################
|
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == INDENT:
|
2008-07-18 11:00:34 +00:00
|
|
|
indent += 1
|
2008-07-21 00:38:42 +00:00
|
|
|
elif type == DEDENT:
|
2008-07-18 11:00:34 +00:00
|
|
|
indent -= 1
|
|
|
|
|
|
|
|
#########################
|
|
|
|
## Module importing... ##
|
|
|
|
#########################
|
|
|
|
|
|
|
|
imp_store = False
|
|
|
|
|
|
|
|
# Default, look for 'from' or 'import' to start
|
|
|
|
if imp_step == 0:
|
|
|
|
if string == 'from':
|
|
|
|
imp_tmp = []
|
|
|
|
imp_step = 1
|
|
|
|
elif string == 'import':
|
|
|
|
imp_from = None
|
|
|
|
imp_tmp = []
|
|
|
|
imp_step = 2
|
|
|
|
|
|
|
|
# Found a 'from', create imp_from in form '???.???...'
|
|
|
|
elif imp_step == 1:
|
|
|
|
if string == 'import':
|
|
|
|
imp_from = '.'.join(imp_tmp)
|
|
|
|
imp_tmp = []
|
|
|
|
imp_step = 2
|
2008-07-21 00:38:42 +00:00
|
|
|
elif type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
imp_tmp.append(string)
|
|
|
|
elif string != '.':
|
|
|
|
imp_step = 0 # Invalid syntax
|
|
|
|
|
|
|
|
# Found 'import', imp_from is populated or None, create imp_name
|
|
|
|
elif imp_step == 2:
|
|
|
|
if string == 'as':
|
|
|
|
imp_name = '.'.join(imp_tmp)
|
|
|
|
imp_step = 3
|
2008-07-21 00:38:42 +00:00
|
|
|
elif type == NAME or string == '*':
|
2008-07-18 11:00:34 +00:00
|
|
|
imp_tmp.append(string)
|
|
|
|
elif string != '.':
|
|
|
|
imp_name = '.'.join(imp_tmp)
|
|
|
|
imp_symb = imp_name
|
|
|
|
imp_store = True
|
|
|
|
|
|
|
|
# Found 'as', change imp_symb to this value and go back to step 2
|
|
|
|
elif imp_step == 3:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
imp_symb = string
|
|
|
|
else:
|
|
|
|
imp_store = True
|
|
|
|
|
|
|
|
# Both imp_name and imp_symb have now been populated so we can import
|
|
|
|
if imp_store:
|
|
|
|
|
|
|
|
# Handle special case of 'import *'
|
|
|
|
if imp_name == '*':
|
|
|
|
parent = get_module(imp_from)
|
|
|
|
imports.update(parent.__dict__)
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Try importing the name as a module
|
|
|
|
try:
|
|
|
|
if imp_from:
|
|
|
|
module = get_module(imp_from +'.'+ imp_name)
|
|
|
|
else:
|
|
|
|
module = get_module(imp_name)
|
|
|
|
except (ImportError, ValueError, AttributeError, TypeError):
|
|
|
|
# Try importing name as an attribute of the parent
|
|
|
|
try:
|
|
|
|
module = __import__(imp_from, globals(), locals(), [imp_name])
|
2008-08-08 15:54:04 +00:00
|
|
|
imports[imp_symb] = getattr(module, imp_name)
|
2008-07-18 11:00:34 +00:00
|
|
|
except (ImportError, ValueError, AttributeError, TypeError):
|
|
|
|
pass
|
2008-07-26 20:02:10 +00:00
|
|
|
else:
|
|
|
|
imports[imp_symb] = module
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
# More to import from the same module?
|
|
|
|
if string == ',':
|
|
|
|
imp_tmp = []
|
|
|
|
imp_step = 2
|
|
|
|
else:
|
|
|
|
imp_step = 0
|
|
|
|
|
|
|
|
###################
|
|
|
|
## Class parsing ##
|
|
|
|
###################
|
|
|
|
|
|
|
|
# If we are inside a class then def and variable parsing should be done
|
|
|
|
# for the class. Otherwise the definitions are considered global
|
|
|
|
|
|
|
|
# Look for 'class'
|
|
|
|
if cls_step == 0:
|
|
|
|
if string == 'class':
|
|
|
|
cls_name = None
|
2008-07-21 00:38:42 +00:00
|
|
|
cls_lineno = start[0]
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_indent = indent
|
|
|
|
cls_step = 1
|
|
|
|
|
|
|
|
# Found 'class', look for cls_name followed by '('
|
|
|
|
elif cls_step == 1:
|
|
|
|
if not cls_name:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_name = string
|
|
|
|
cls_sline = False
|
|
|
|
cls_defs = dict()
|
|
|
|
cls_vars = dict()
|
|
|
|
elif string == ':':
|
|
|
|
cls_step = 2
|
|
|
|
|
|
|
|
# Found 'class' name ... ':', now check if it's a single line statement
|
|
|
|
elif cls_step == 2:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NEWLINE:
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_sline = False
|
|
|
|
cls_step = 3
|
2008-07-21 00:38:42 +00:00
|
|
|
else:
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_sline = True
|
|
|
|
cls_step = 3
|
|
|
|
|
|
|
|
elif cls_step == 3:
|
|
|
|
if cls_sline:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NEWLINE:
|
|
|
|
classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno)
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_step = 0
|
|
|
|
else:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == DEDENT and indent <= cls_indent:
|
|
|
|
classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno)
|
2008-07-18 11:00:34 +00:00
|
|
|
cls_step = 0
|
|
|
|
|
|
|
|
#################
|
|
|
|
## Def parsing ##
|
|
|
|
#################
|
|
|
|
|
|
|
|
# Look for 'def'
|
|
|
|
if def_step == 0:
|
|
|
|
if string == 'def':
|
|
|
|
def_name = None
|
2008-07-21 00:38:42 +00:00
|
|
|
def_lineno = start[0]
|
2008-07-18 11:00:34 +00:00
|
|
|
def_step = 1
|
|
|
|
|
|
|
|
# Found 'def', look for def_name followed by '('
|
|
|
|
elif def_step == 1:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
def_name = string
|
|
|
|
def_params = []
|
|
|
|
elif def_name and string == '(':
|
|
|
|
def_step = 2
|
|
|
|
|
|
|
|
# Found 'def' name '(', now identify the parameters upto ')'
|
|
|
|
# TODO: Handle ellipsis '...'
|
|
|
|
elif def_step == 2:
|
2008-07-21 00:38:42 +00:00
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
def_params.append(string)
|
|
|
|
elif string == ')':
|
|
|
|
if cls_step > 0: # Parsing a class
|
2008-07-21 00:38:42 +00:00
|
|
|
cls_defs[def_name] = FunctionDesc(def_name, def_params, def_lineno)
|
2008-07-18 11:00:34 +00:00
|
|
|
else:
|
2008-07-21 00:38:42 +00:00
|
|
|
defs[def_name] = FunctionDesc(def_name, def_params, def_lineno)
|
2008-07-18 11:00:34 +00:00
|
|
|
def_step = 0
|
|
|
|
|
|
|
|
##########################
|
|
|
|
## Variable assignation ##
|
|
|
|
##########################
|
|
|
|
|
|
|
|
if cls_step > 0: # Parsing a class
|
|
|
|
# Look for 'self.???'
|
2008-07-21 00:38:42 +00:00
|
|
|
if var1_step == 0:
|
2008-07-18 11:00:34 +00:00
|
|
|
if string == 'self':
|
2008-07-21 00:38:42 +00:00
|
|
|
var1_step = 1
|
|
|
|
elif var1_step == 1:
|
2008-07-18 11:00:34 +00:00
|
|
|
if string == '.':
|
|
|
|
var_name = None
|
2008-07-21 00:38:42 +00:00
|
|
|
var1_step = 2
|
2008-07-18 11:00:34 +00:00
|
|
|
else:
|
2008-07-21 00:38:42 +00:00
|
|
|
var1_step = 0
|
|
|
|
elif var1_step == 2:
|
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
var_name = string
|
2008-07-21 00:38:42 +00:00
|
|
|
if cls_vars.has_key(var_name):
|
|
|
|
var_step = 0
|
|
|
|
else:
|
|
|
|
var1_step = 3
|
|
|
|
elif var1_step == 3:
|
2008-07-18 11:00:34 +00:00
|
|
|
if string == '=':
|
2008-07-21 00:38:42 +00:00
|
|
|
var1_step = 4
|
|
|
|
elif var1_step == 4:
|
|
|
|
var_type = None
|
|
|
|
if string == '[':
|
|
|
|
close = line.find(']', end[1])
|
|
|
|
var_type = list
|
2008-07-26 20:02:10 +00:00
|
|
|
elif type == STRING:
|
|
|
|
close = end[1]
|
2008-07-21 00:38:42 +00:00
|
|
|
var_type = str
|
|
|
|
elif string == '(':
|
|
|
|
close = line.find(')', end[1])
|
|
|
|
var_type = tuple
|
|
|
|
elif string == '{':
|
|
|
|
close = line.find('}', end[1])
|
|
|
|
var_type = dict
|
|
|
|
elif string == 'dict':
|
|
|
|
close = line.find(')', end[1])
|
|
|
|
var_type = dict
|
|
|
|
if var_type and close+1 < len(line):
|
2008-07-21 11:21:49 +00:00
|
|
|
if line[close+1] != ' ' and line[close+1] != '\t':
|
2008-07-21 00:38:42 +00:00
|
|
|
var_type = None
|
|
|
|
cls_vars[var_name] = VarDesc(var_name, var_type, start[0])
|
|
|
|
var1_step = 0
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
elif def_step > 0: # Parsing a def
|
|
|
|
# Look for 'global ???[,???]'
|
2008-07-21 00:38:42 +00:00
|
|
|
if var2_step == 0:
|
2008-07-18 11:00:34 +00:00
|
|
|
if string == 'global':
|
2008-07-21 00:38:42 +00:00
|
|
|
var2_step = 1
|
|
|
|
elif var2_step == 1:
|
|
|
|
if type == NAME:
|
2008-07-18 11:00:34 +00:00
|
|
|
vars[string] = True
|
2008-07-21 00:38:42 +00:00
|
|
|
elif string != ',' and type != NL:
|
|
|
|
var2_step == 0
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
else: # In global scope
|
2008-07-21 00:38:42 +00:00
|
|
|
if var3_step == 0:
|
|
|
|
# Look for names
|
|
|
|
if string == 'for':
|
|
|
|
var_accum = dict()
|
|
|
|
var_forflag = True
|
|
|
|
elif string == '=' or (var_forflag and string == 'in'):
|
|
|
|
var_forflag = False
|
|
|
|
var3_step = 1
|
|
|
|
elif type == NAME:
|
|
|
|
if prev_string != '.' and not vars.has_key(string):
|
|
|
|
var_accum[string] = VarDesc(string, None, start[0])
|
|
|
|
elif not string in [',', '(', ')', '[', ']']:
|
|
|
|
var_accum = dict()
|
|
|
|
var_forflag = False
|
|
|
|
elif var3_step == 1:
|
|
|
|
if len(var_accum) != 1:
|
|
|
|
var_type = None
|
|
|
|
vars.update(var_accum)
|
|
|
|
else:
|
|
|
|
var_name = var_accum.keys()[0]
|
|
|
|
var_type = None
|
|
|
|
if string == '[': var_type = list
|
2008-07-26 20:02:10 +00:00
|
|
|
elif type == STRING: var_type = str
|
2008-07-21 00:38:42 +00:00
|
|
|
elif string == '(': var_type = tuple
|
2008-07-26 20:02:10 +00:00
|
|
|
elif string == '{': var_type = dict
|
2008-07-21 00:38:42 +00:00
|
|
|
vars[var_name] = VarDesc(var_name, var_type, start[0])
|
|
|
|
var3_step = 0
|
2008-07-18 11:00:34 +00:00
|
|
|
|
|
|
|
#######################
|
|
|
|
## General utilities ##
|
|
|
|
#######################
|
|
|
|
|
|
|
|
prev_type = type
|
|
|
|
prev_string = string
|
|
|
|
|
|
|
|
desc = ScriptDesc(txt.name, imports, classes, defs, vars, incomplete)
|
|
|
|
desc.set_time()
|
|
|
|
|
|
|
|
global _parse_cache
|
|
|
|
_parse_cache[hash(txt.name)] = desc
|
|
|
|
return desc
|
2008-07-16 10:33:48 +00:00
|
|
|
|
|
|
|
def get_modules(since=1):
|
|
|
|
"""Returns the set of built-in modules and any modules that have been
|
|
|
|
imported into the system upto 'since' seconds ago.
|
|
|
|
"""
|
|
|
|
|
|
|
|
global _modules, _modules_updated
|
|
|
|
|
|
|
|
t = time()
|
|
|
|
if _modules_updated < t - since:
|
|
|
|
_modules.update(sys.modules)
|
|
|
|
_modules_updated = t
|
|
|
|
return _modules.keys()
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
def suggest_cmp(x, y):
|
2008-07-15 12:55:20 +00:00
|
|
|
"""Use this method when sorting a list of suggestions.
|
|
|
|
"""
|
2008-07-15 07:34:46 +00:00
|
|
|
|
2008-07-15 17:03:59 +00:00
|
|
|
return cmp(x[0].upper(), y[0].upper())
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
def get_module(name):
|
2008-07-15 12:55:20 +00:00
|
|
|
"""Returns the module specified by its name. The module itself is imported
|
|
|
|
by this method and, as such, any initialization code will be executed.
|
|
|
|
"""
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
mod = __import__(name)
|
|
|
|
components = name.split('.')
|
|
|
|
for comp in components[1:]:
|
|
|
|
mod = getattr(mod, comp)
|
|
|
|
return mod
|
|
|
|
|
|
|
|
def type_char(v):
|
2008-07-15 12:55:20 +00:00
|
|
|
"""Returns the character used to signify the type of a variable. Use this
|
|
|
|
method to identify the type character for an item in a suggestion list.
|
|
|
|
|
|
|
|
The following values are returned:
|
|
|
|
'm' if the parameter is a module
|
|
|
|
'f' if the parameter is callable
|
|
|
|
'v' if the parameter is variable or otherwise indeterminable
|
2008-07-18 11:00:34 +00:00
|
|
|
|
2008-07-15 12:55:20 +00:00
|
|
|
"""
|
|
|
|
|
2008-07-16 10:33:48 +00:00
|
|
|
if isinstance(v, ModuleType):
|
2008-07-15 07:34:46 +00:00
|
|
|
return 'm'
|
|
|
|
elif callable(v):
|
|
|
|
return 'f'
|
|
|
|
else:
|
|
|
|
return 'v'
|
|
|
|
|
2008-07-15 12:55:20 +00:00
|
|
|
def get_context(txt):
|
|
|
|
"""Establishes the context of the cursor in the given Blender Text object
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
Returns one of:
|
2008-07-21 00:38:42 +00:00
|
|
|
CTX_NORMAL - Cursor is in a normal context
|
|
|
|
CTX_SINGLE_QUOTE - Cursor is inside a single quoted string
|
|
|
|
CTX_DOUBLE_QUOTE - Cursor is inside a double quoted string
|
|
|
|
CTX_COMMENT - Cursor is inside a comment
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
global CTX_NORMAL, CTX_SINGLE_QUOTE, CTX_DOUBLE_QUOTE, CTX_COMMENT
|
2008-07-15 12:55:20 +00:00
|
|
|
l, cursor = txt.getCursorPos()
|
|
|
|
lines = txt.asLines()[:l+1]
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
# Detect context (in string or comment)
|
2008-07-21 00:38:42 +00:00
|
|
|
in_str = CTX_NORMAL
|
2008-07-15 12:55:20 +00:00
|
|
|
for line in lines:
|
|
|
|
if l == 0:
|
|
|
|
end = cursor
|
2008-07-15 07:34:46 +00:00
|
|
|
else:
|
2008-07-15 12:55:20 +00:00
|
|
|
end = len(line)
|
|
|
|
l -= 1
|
|
|
|
|
|
|
|
# Comments end at new lines
|
2008-07-21 00:38:42 +00:00
|
|
|
if in_str == CTX_COMMENT:
|
|
|
|
in_str = CTX_NORMAL
|
2008-07-15 12:55:20 +00:00
|
|
|
|
|
|
|
for i in range(end):
|
|
|
|
if in_str == 0:
|
2008-07-21 00:38:42 +00:00
|
|
|
if line[i] == "'": in_str = CTX_SINGLE_QUOTE
|
|
|
|
elif line[i] == '"': in_str = CTX_DOUBLE_QUOTE
|
|
|
|
elif line[i] == '#': in_str = CTX_COMMENT
|
2008-07-15 12:55:20 +00:00
|
|
|
else:
|
2008-07-21 00:38:42 +00:00
|
|
|
if in_str == CTX_SINGLE_QUOTE:
|
2008-07-15 12:55:20 +00:00
|
|
|
if line[i] == "'":
|
2008-07-21 00:38:42 +00:00
|
|
|
in_str = CTX_NORMAL
|
2008-07-15 12:55:20 +00:00
|
|
|
# In again if ' escaped, out again if \ escaped, and so on
|
|
|
|
for a in range(i-1, -1, -1):
|
|
|
|
if line[a] == '\\': in_str = 1-in_str
|
|
|
|
else: break
|
2008-07-21 00:38:42 +00:00
|
|
|
elif in_str == CTX_DOUBLE_QUOTE:
|
2008-07-15 12:55:20 +00:00
|
|
|
if line[i] == '"':
|
2008-07-21 00:38:42 +00:00
|
|
|
in_str = CTX_NORMAL
|
2008-07-15 12:55:20 +00:00
|
|
|
# In again if " escaped, out again if \ escaped, and so on
|
|
|
|
for a in range(i-1, -1, -1):
|
|
|
|
if line[i-a] == '\\': in_str = 2-in_str
|
|
|
|
else: break
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
return in_str
|
|
|
|
|
|
|
|
def current_line(txt):
|
|
|
|
"""Extracts the Python script line at the cursor in the Blender Text object
|
|
|
|
provided and cursor position within this line as the tuple pair (line,
|
2008-07-18 11:00:34 +00:00
|
|
|
cursor).
|
|
|
|
"""
|
2008-07-15 07:34:46 +00:00
|
|
|
|
2008-07-21 00:38:42 +00:00
|
|
|
lineindex, cursor = txt.getCursorPos()
|
2008-07-15 07:34:46 +00:00
|
|
|
lines = txt.asLines()
|
|
|
|
line = lines[lineindex]
|
|
|
|
|
|
|
|
# Join previous lines to this line if spanning
|
|
|
|
i = lineindex - 1
|
|
|
|
while i > 0:
|
|
|
|
earlier = lines[i].rstrip()
|
|
|
|
if earlier.endswith('\\'):
|
|
|
|
line = earlier[:-1] + ' ' + line
|
|
|
|
cursor += len(earlier)
|
|
|
|
i -= 1
|
|
|
|
|
|
|
|
# Join later lines while there is an explicit joining character
|
|
|
|
i = lineindex
|
2008-07-15 12:55:20 +00:00
|
|
|
while i < len(lines)-1 and lines[i].rstrip().endswith('\\'):
|
2008-07-15 07:34:46 +00:00
|
|
|
later = lines[i+1].strip()
|
|
|
|
line = line + ' ' + later[:-1]
|
2008-07-15 12:55:20 +00:00
|
|
|
i += 1
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
return line, cursor
|
|
|
|
|
|
|
|
def get_targets(line, cursor):
|
|
|
|
"""Parses a period separated string of valid names preceding the cursor and
|
2008-07-18 11:00:34 +00:00
|
|
|
returns them as a list in the same order.
|
|
|
|
"""
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
targets = []
|
|
|
|
i = cursor - 1
|
|
|
|
while i >= 0 and (line[i].isalnum() or line[i] == '_' or line[i] == '.'):
|
|
|
|
i -= 1
|
|
|
|
|
|
|
|
pre = line[i+1:cursor]
|
|
|
|
return pre.split('.')
|
|
|
|
|
2008-07-18 11:00:34 +00:00
|
|
|
def get_defs(txt):
|
|
|
|
"""Returns a dictionary which maps definition names in the source code to
|
|
|
|
a list of their parameter names.
|
|
|
|
|
|
|
|
The line 'def doit(one, two, three): print one' for example, results in the
|
|
|
|
mapping 'doit' : [ 'one', 'two', 'three' ]
|
|
|
|
"""
|
|
|
|
|
|
|
|
return get_cached_descriptor(txt).defs
|
|
|
|
|
|
|
|
def get_vars(txt):
|
|
|
|
"""Returns a dictionary of variable names found in the specified Text
|
|
|
|
object. This method locates all names followed directly by an equal sign:
|
|
|
|
'a = ???' or indirectly as part of a tuple/list assignment or inside a
|
|
|
|
'for ??? in ???:' block.
|
|
|
|
"""
|
|
|
|
|
|
|
|
return get_cached_descriptor(txt).vars
|
|
|
|
|
2008-07-15 07:34:46 +00:00
|
|
|
def get_imports(txt):
|
|
|
|
"""Returns a dictionary which maps symbol names in the source code to their
|
|
|
|
respective modules.
|
|
|
|
|
|
|
|
The line 'from Blender import Text as BText' for example, results in the
|
|
|
|
mapping 'BText' : <module 'Blender.Text' (built-in)>
|
|
|
|
|
|
|
|
Note that this method imports the modules to provide this mapping as as such
|
|
|
|
will execute any initilization code found within.
|
|
|
|
"""
|
|
|
|
|
2008-07-18 11:00:34 +00:00
|
|
|
return get_cached_descriptor(txt).imports
|
2008-07-15 07:34:46 +00:00
|
|
|
|
|
|
|
def get_builtins():
|
|
|
|
"""Returns a dictionary of built-in modules, functions and variables."""
|
|
|
|
|
|
|
|
return __builtin__.__dict__
|
|
|
|
|
2008-07-15 12:55:20 +00:00
|
|
|
|
2008-07-18 11:00:34 +00:00
|
|
|
#################################
|
|
|
|
## Debugging utility functions ##
|
|
|
|
#################################
|
|
|
|
|
|
|
|
def print_cache_for(txt, period=sys.maxint):
|
|
|
|
"""Prints out the data cached for a given Text object. If no period is
|
|
|
|
given the text will not be reparsed and the cached version will be returned.
|
|
|
|
Otherwise if the period has expired the text will be reparsed.
|
2008-07-15 12:55:20 +00:00
|
|
|
"""
|
|
|
|
|
2008-07-18 11:00:34 +00:00
|
|
|
desc = get_cached_descriptor(txt, period)
|
|
|
|
print '================================================'
|
|
|
|
print 'Name:', desc.name, '('+str(hash(txt))+')'
|
|
|
|
print '------------------------------------------------'
|
|
|
|
print 'Defs:'
|
2008-07-21 00:38:42 +00:00
|
|
|
for name, ddesc in desc.defs.items():
|
|
|
|
print ' ', name, ddesc.params, ddesc.lineno
|
2008-07-18 11:00:34 +00:00
|
|
|
print '------------------------------------------------'
|
|
|
|
print 'Vars:'
|
2008-07-21 00:38:42 +00:00
|
|
|
for name, vdesc in desc.vars.items():
|
|
|
|
print ' ', name, vdesc.type, vdesc.lineno
|
2008-07-18 11:00:34 +00:00
|
|
|
print '------------------------------------------------'
|
|
|
|
print 'Imports:'
|
|
|
|
for name, item in desc.imports.items():
|
|
|
|
print ' ', name.ljust(15), item
|
|
|
|
print '------------------------------------------------'
|
|
|
|
print 'Classes:'
|
|
|
|
for clsnme, clsdsc in desc.classes.items():
|
|
|
|
print ' *********************************'
|
|
|
|
print ' Name:', clsnme
|
|
|
|
print ' ---------------------------------'
|
|
|
|
print ' Defs:'
|
2008-07-21 00:38:42 +00:00
|
|
|
for name, ddesc in clsdsc.defs.items():
|
|
|
|
print ' ', name, ddesc.params, ddesc.lineno
|
2008-07-18 11:00:34 +00:00
|
|
|
print ' ---------------------------------'
|
|
|
|
print ' Vars:'
|
2008-07-21 00:38:42 +00:00
|
|
|
for name, vdesc in clsdsc.vars.items():
|
|
|
|
print ' ', name, vdesc.type, vdesc.lineno
|
2008-07-18 11:00:34 +00:00
|
|
|
print ' *********************************'
|
|
|
|
print '================================================'
|