[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [16128] branches/soc-2008-quorn/release/ scripts: Improvements to the base BPyTextPlugin module:

Ian Thompson quornian at googlemail.com
Sat Aug 16 01:14:22 CEST 2008


Revision: 16128
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=16128
Author:   quorn
Date:     2008-08-16 01:14:22 +0200 (Sat, 16 Aug 2008)

Log Message:
-----------
Improvements to the base BPyTextPlugin module:
 - Added a centralized function for resolving targets (aaa.bbb.ccc)
 - Added documentation support for locally defined classes and methods
 - The time taken to parse now dictates how long to use the cache before parsing again
 - Other tweaks and comments and support for numeric var types

The text plugin scripts have been updated to make use of these features.

Modified Paths:
--------------
    branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py
    branches/soc-2008-quorn/release/scripts/textplugin_functiondocs.py
    branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py
    branches/soc-2008-quorn/release/scripts/textplugin_suggest.py

Modified: branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py	2008-08-15 22:17:50 UTC (rev 16127)
+++ branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py	2008-08-15 23:14:22 UTC (rev 16128)
@@ -1,3 +1,19 @@
+"""The BPyTextPlugin Module
+
+Use get_cached_descriptor(txt) to retrieve information about the script held in
+the txt Text object.
+
+Use print_cache_for(txt) to print the information to the console.
+
+Use line, cursor = current_line(txt) to get the logical line and cursor position
+
+Use get_targets(line, cursor) to find out what precedes the cursor:
+	aaa.bbb.cc|c.ddd -> ['aaa', 'bbb', 'cc']
+
+Use resolve_targets(txt, targets) to turn a target list into a usable object if
+one is found to match.
+"""
+
 import bpy, sys, os
 import __builtin__, tokenize
 from Blender.sys import time
@@ -2,4 +18,14 @@
 from tokenize import generate_tokens, TokenError, \
-		COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING
+		COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING, NUMBER
 
+class Definition():
+	"""Describes a definition or defined object through its name, line number
+	and docstring. This is the base class for definition based descriptors.
+	"""
+	
+	def __init__(self, name, lineno, doc=''):
+		self.name = name
+		self.lineno = lineno
+		self.doc = doc
+
 class ScriptDesc():
@@ -19,43 +45,40 @@
 		self.defs = defs
 		self.vars = vars
 		self.incomplete = incomplete
-		self.time = 0
+		self.parse_due = 0
 	
-	def set_time(self):
-		self.time = time()
+	def set_delay(self, delay):
+		self.parse_due = time() + delay
 
-class ClassDesc():
+class ClassDesc(Definition):
 	"""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.
 	"""
 	
-	def __init__(self, name, defs, vars, lineno):
-		self.name = name
+	def __init__(self, name, defs, vars, lineno, doc=''):
+		Definition.__init__(self, name, lineno, doc)
 		self.defs = defs
 		self.vars = vars
-		self.lineno = lineno
 
-class FunctionDesc():
+class FunctionDesc(Definition):
 	"""Describes a function through its name and list of parameters (name,
 	params) and the line on which it is defined (lineno).
 	"""
 	
-	def __init__(self, name, params, lineno):
-		self.name = name
+	def __init__(self, name, params, lineno, doc=''):
+		Definition.__init__(self, name, lineno, doc)
 		self.params = params
-		self.lineno = lineno
 
-class VarDesc():
+class VarDesc(Definition):
 	"""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.
 	"""
 	
 	def __init__(self, name, type, lineno):
-		self.name = name
+		Definition.__init__(self, name, lineno)
 		self.type = type # None for unknown (supports: dict/list/str)
-		self.lineno = lineno
 
 # Context types
 CTX_UNSET = -1
@@ -64,9 +87,6 @@
 CTX_DOUBLE_QUOTE = 2
 CTX_COMMENT = 3
 
-# Special time period constants
-TP_AUTO = -1
-
 # Python keywords
 KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
 			'or', 'with', 'assert', 'else', 'if', 'pass', 'yield',
@@ -104,8 +124,76 @@
 
 _load_module_names()
 
+def _trim_doc(doc):
+	"""Trims the quotes from a quoted STRING token (eg. "'''text'''" -> "text")
+	"""
+	
+	l = len(doc)
+	i = 0
+	while i < l/2 and (doc[i] == "'" or doc[i] == '"'):
+		i += 1
+	return doc[i:-i]
 
-def get_cached_descriptor(txt, period=TP_AUTO):
+def resolve_targets(txt, targets):
+	"""Attempts to return a useful object for the locally or externally defined
+	entity described by targets. If the object is local (defined in txt), a
+	Definition instance is returned. If the object is external (imported or
+	built in), the object itself is returned. If no object can be found, None is
+	returned.
+	"""
+	
+	count = len(targets)
+	if count==0: return None
+	
+	obj = None
+	local = None
+	i = 1
+	
+	desc = get_cached_descriptor(txt)
+	if desc.classes.has_key(targets[0]):
+		local = desc.classes[targets[0]]
+	elif desc.defs.has_key(targets[0]):
+		local = desc.defs[targets[0]]
+	elif desc.vars.has_key(targets[0]):
+		obj = desc.vars[targets[0]].type
+	
+	if local:
+		while i < count:
+			if hasattr(local, 'classes') and local.classes.has_key(targets[i]):
+				local = local.classes[targets[i]]
+			elif hasattr(local, 'defs') and local.defs.has_key(targets[i]):
+				local = local.defs[targets[i]]
+			elif hasattr(local, 'vars') and local.vars.has_key(targets[i]):
+				obj = local.vars[targets[i]].type
+				local = None
+				i += 1
+				break
+			else:
+				local = None
+				break
+			i += 1
+	
+	if local: return local
+	
+	if not obj:
+		if desc.imports.has_key(targets[0]):
+			obj = desc.imports[targets[0]]
+		else:
+			builtins = get_builtins()
+			if builtins.has_key(targets[0]):
+				obj = builtins[targets[0]]
+	
+	while obj and i < count:
+		if hasattr(obj, targets[i]):
+			obj = getattr(obj, targets[i])
+		else:
+			obj = None
+			break
+		i += 1
+	
+	return obj
+
+def get_cached_descriptor(txt, force_parse=0):
 	"""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.
@@ -116,20 +204,11 @@
 	
 	global _parse_cache
 	
-	if period == TP_AUTO:
-		m = txt.nlines
-		r = 1
-		while True:
-			m = m >> 2
-			if not m: break
-			r = r << 1
-		period = r
-	
 	parse = True
 	key = hash(txt)
-	if _parse_cache.has_key(key):
+	if not force_parse and _parse_cache.has_key(key):
 		desc = _parse_cache[key]
-		if desc.time >= time() - period:
+		if desc.parse_due > time():
 			parse = desc.incomplete
 	
 	if parse:
@@ -147,6 +226,7 @@
 	flag set and information processed up to this point will still be accessible.
 	"""
 	
+	start_time = time()
 	txt.reset()
 	tokens = generate_tokens(txt.readline) # Throws TokenError
 	
@@ -171,12 +251,12 @@
 	
 	indent = 0
 	prev_type = -1
-	prev_string = ''
+	prev_text = ''
 	incomplete = False
 	
 	while True:
 		try:
-			type, string, start, end, line = tokens.next()
+			type, text, start, end, line = tokens.next()
 		except StopIteration:
 			break
 		except TokenError, IndentationError:
@@ -204,33 +284,33 @@
 		
 		# Default, look for 'from' or 'import' to start
 		if imp_step == 0:
-			if string == 'from':
+			if text == 'from':
 				imp_tmp = []
 				imp_step = 1
-			elif string == 'import':
+			elif text == 'import':
 				imp_from = None
 				imp_tmp = []
 				imp_step = 2
 		
 		# Found a 'from', create imp_from in form '???.???...'
 		elif imp_step == 1:
-			if string == 'import':
+			if text == 'import':
 				imp_from = '.'.join(imp_tmp)
 				imp_tmp = []
 				imp_step = 2
 			elif type == NAME:
-				imp_tmp.append(string)
-			elif string != '.':
+				imp_tmp.append(text)
+			elif text != '.':
 				imp_step = 0 # Invalid syntax
 		
 		# Found 'import', imp_from is populated or None, create imp_name
 		elif imp_step == 2:
-			if string == 'as':
+			if text == 'as':
 				imp_name = '.'.join(imp_tmp)
 				imp_step = 3
-			elif type == NAME or string == '*':
-				imp_tmp.append(string)
-			elif string != '.':
+			elif type == NAME or text == '*':
+				imp_tmp.append(text)
+			elif text != '.':
 				imp_name = '.'.join(imp_tmp)
 				imp_symb = imp_name
 				imp_store = True
@@ -238,7 +318,7 @@
 		# Found 'as', change imp_symb to this value and go back to step 2
 		elif imp_step == 3:
 			if type == NAME:
-				imp_symb = string
+				imp_symb = text
 			else:
 				imp_store = True
 		
@@ -268,7 +348,7 @@
 					imports[imp_symb] = module
 			
 			# More to import from the same module?
-			if string == ',':
+			if text == ',':
 				imp_tmp = []
 				imp_step = 2
 			else:
@@ -283,7 +363,7 @@
 		
 		# Look for 'class'
 		if cls_step == 0:
-			if string == 'class':
+			if text == 'class':
 				cls_name = None
 				cls_lineno = start[0]
 				cls_indent = indent
@@ -293,30 +373,32 @@
 		elif cls_step == 1:
 			if not cls_name:
 				if type == NAME:
-					cls_name = string
+					cls_name = text
 					cls_sline = False
 					cls_defs = dict()
 					cls_vars = dict()
-			elif string == ':':
+			elif text == ':':
 				cls_step = 2
 		
 		# Found 'class' name ... ':', now check if it's a single line statement
 		elif cls_step == 2:
 			if type == NEWLINE:
 				cls_sline = False
-				cls_step = 3
 			else:
 				cls_sline = True
-				cls_step = 3
+			cls_doc = ''
+			cls_step = 3
 		
 		elif cls_step == 3:
+			if not cls_doc and type == STRING:
+				cls_doc = _trim_doc(text)
 			if cls_sline:
 				if type == NEWLINE:
-					classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno)
+					classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
 					cls_step = 0
 			else:
 				if type == DEDENT and indent <= cls_indent:
-					classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno)
+					classes[cls_name] = ClassDesc(cls_name, cls_defs, cls_vars, cls_lineno, cls_doc)
 					cls_step = 0
 		
 		#################
@@ -325,7 +407,7 @@
 		
 		# Look for 'def'
 		if def_step == 0:
-			if string == 'def':
+			if text == 'def':
 				def_name = None
 				def_lineno = start[0]
 				def_step = 1
@@ -333,21 +415,43 @@
 		# Found 'def', look for def_name followed by '('
 		elif def_step == 1:
 			if type == NAME:
-				def_name = string
+				def_name = text
 				def_params = []
-			elif def_name and string == '(':
+			elif def_name and text == '(':
 				def_step = 2
 		
 		# Found 'def' name '(', now identify the parameters upto ')'
 		# TODO: Handle ellipsis '...'
 		elif def_step == 2:
 			if type == NAME:
-				def_params.append(string)
-			elif string == ')':
+				def_params.append(text)
+			elif text == ':':
+				def_step = 3
+		
+		# Found 'def' ... ':', now check if it's a single line statement
+		elif def_step == 3:
+			if type == NEWLINE:
+				def_sline = False
+			else:
+				def_sline = True
+			def_doc = ''
+			def_step = 4
+		
+		elif def_step == 4:
+			if type == STRING:
+				def_doc = _trim_doc(text)
+			newdef = None
+			if def_sline:
+				if type == NEWLINE:
+					newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
+			else:
+				if type == NAME:
+					newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
+			if newdef:
 				if cls_step > 0: # Parsing a class
-					cls_defs[def_name] = FunctionDesc(def_name, def_params, def_lineno)
+					cls_defs[def_name] = newdef
 				else:

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list