[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [15586] branches/soc-2008-quorn/release/ scripts: Text plugin script updates: Better error handling, variable parsing, token caching for repeat parsing of the same document.

Ian Thompson quornian at googlemail.com
Tue Jul 15 14:55:20 CEST 2008


Revision: 15586
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=15586
Author:   quorn
Date:     2008-07-15 14:55:20 +0200 (Tue, 15 Jul 2008)

Log Message:
-----------
Text plugin script updates: Better error handling, variable parsing, token caching for repeat parsing of the same document. Fixed joining of multiline statements and context detection.

Modified Paths:
--------------
    branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py
    branches/soc-2008-quorn/release/scripts/textplugin_imports.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-07-15 12:54:57 UTC (rev 15585)
+++ branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py	2008-07-15 12:55:20 UTC (rev 15586)
@@ -1,6 +1,7 @@
-import bpy, sys
+import bpy
 import __builtin__, tokenize
-from tokenize import generate_tokens
+from Blender.sys import time
+from tokenize import generate_tokens, TokenError
 # TODO: Remove the dependency for a full Python installation. Currently only the
 # tokenize module is required 
 
@@ -17,15 +18,33 @@
 			'raise', 'continue', 'finally', 'is', 'return', 'def', 'for',
 			'lambda', 'try' ]
 
+# Used to cache the return value of generate_tokens
+_token_cache = None
+_cache_update = 0
 
 def suggest_cmp(x, y):
-	"""Use this method when sorting a list for suggestions"""
+	"""Use this method when sorting a list of suggestions.
+	"""
 	
 	return cmp(x[0], y[0])
 
+def cached_generate_tokens(txt, since=1):
+	"""A caching version of generate tokens for multiple parsing of the same
+	document within a given timescale.
+	"""
+	
+	global _token_cache, _cache_update
+	
+	if _cache_update < time() - since:
+		txt.reset()
+		_token_cache = [g for g in generate_tokens(txt.readline)]
+		_cache_update = time()
+	return _token_cache
+
 def get_module(name):
-	"""Returns the module specified by its name. This module is imported and as
-	such will run any initialization code specified within the module."""
+	"""Returns the module specified by its name. The module itself is imported
+	by this method and, as such, any initialization code will be executed.
+	"""
 	
 	mod = __import__(name)
 	components = name.split('.')
@@ -34,11 +53,21 @@
 	return mod
 
 def is_module(m):
-	"""Taken from the inspect module of the standard Python installation"""
+	"""Taken from the inspect module of the standard Python installation.
+	"""
 	
 	return isinstance(m, type(bpy))
 
 def type_char(v):
+	"""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
+	"""
+	
 	if is_module(v):
 		return 'm'
 	elif callable(v):
@@ -46,8 +75,8 @@
 	else: 
 		return 'v'
 
-def get_context(line, cursor):
-	"""Establishes the context of the cursor in the given line
+def get_context(txt):
+	"""Establishes the context of the cursor in the given Blender Text object
 	
 	Returns one of:
 	  NORMAL - Cursor is in a normal context
@@ -57,28 +86,43 @@
 	
 	"""
 	
+	l, cursor = txt.getCursorPos()
+	lines = txt.asLines()[:l+1]
+	
 	# Detect context (in string or comment)
 	in_str = 0			# 1-single quotes, 2-double quotes
-	for i in range(cursor):
-		if not in_str:
-			if line[i] == "'": in_str = 1
-			elif line[i] == '"': in_str = 2
-			elif line[i] == '#': return 3 # In a comment so quit
+	for line in lines:
+		if l == 0:
+			end = cursor
 		else:
-			if in_str == 1:
-				if line[i] == "'":
-					in_str = 0
-					# In again if ' escaped, out again if \ escaped, and so on
-					for a in range(1, i+1):
-						if line[i-a] == '\\': in_str = 1-in_str
-						else: break
-			elif in_str == 2:
-				if line[i] == '"':
-					in_str = 0
-					# In again if " escaped, out again if \ escaped, and so on
-					for a in range(1, i+1):
-						if line[i-a] == '\\': in_str = 2-in_str
-						else: break
+			end = len(line)
+			l -= 1
+		
+		# Comments end at new lines
+		if in_str == 3:
+			in_str = 0
+		
+		for i in range(end):
+			if in_str == 0:
+				if line[i] == "'": in_str = 1
+				elif line[i] == '"': in_str = 2
+				elif line[i] == '#': in_str = 3
+			else:
+				if in_str == 1:
+					if line[i] == "'":
+						in_str = 0
+						# 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
+				elif in_str == 2:
+					if line[i] == '"':
+						in_str = 0
+						# 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
+		
 	return in_str
 
 def current_line(txt):
@@ -101,9 +145,10 @@
 	
 	# Join later lines while there is an explicit joining character
 	i = lineindex
-	while i < len(lines)-1 and line[i].rstrip().endswith('\\'):
+	while i < len(lines)-1 and lines[i].rstrip().endswith('\\'):
 		later = lines[i+1].strip()
 		line = line + ' ' + later[:-1]
+		i += 1
 	
 	return line, cursor
 
@@ -134,9 +179,8 @@
 	# strings open or there are other syntax errors. For now we return an empty
 	# dictionary until an alternative parse method is implemented.
 	try:
-		txt.reset()
-		tokens = generate_tokens(txt.readline)
-	except:
+		tokens = cached_generate_tokens(txt)
+	except TokenError:
 		return dict()
 	
 	imports = dict()
@@ -202,12 +246,12 @@
 					else:
 						module = get_module(impname)
 					imports[symbol] = module
-				except:
+				except (ImportError, ValueError):
 					# Try importing name as an attribute of the parent
 					try:
 						module = __import__(fromname, globals(), locals(), [impname])
 						imports[symbol] = getattr(module, impname)
-					except:
+					except (ImportError, ValueError, AttributeError):
 						pass
 			
 			# More to import from the same module?
@@ -219,7 +263,6 @@
 	
 	return imports
 
-
 def get_builtins():
 	"""Returns a dictionary of built-in modules, functions and variables."""
 	
@@ -235,15 +278,15 @@
 	
 	# See above for problems with generate_tokens
 	try:
-		txt.reset()
-		tokens = generate_tokens(txt.readline)
-	except:
+		tokens = cached_generate_tokens(txt)
+	except TokenError:
 		return dict()
 	
 	defs = dict()
 	step = 0
 	
 	for type, string, start, end, line in tokens:
+		print string
 		
 		# Look for 'def'
 		if step == 0:
@@ -269,3 +312,37 @@
 				step = 0
 		
 	return 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.
+	"""
+	
+	# See above for problems with generate_tokens
+	try:
+		tokens = cached_generate_tokens(txt)
+	except TokenError:
+		return []
+	
+	vars = []
+	accum = [] # Used for tuple/list assignment
+	foring = False
+	
+	for type, string, start, end, line in tokens:
+		
+		# Look for names
+		if string == 'for':
+			foring = True
+		if string == '=' or (foring and string == 'in'):
+			vars.extend(accum)
+			accum = []
+			foring = False
+		elif type == tokenize.NAME:
+			accum.append(string)
+		elif not string in [',', '(', ')', '[', ']']:
+			accum = []
+			foring = False
+		
+	return vars

Modified: branches/soc-2008-quorn/release/scripts/textplugin_imports.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/textplugin_imports.py	2008-07-15 12:54:57 UTC (rev 15585)
+++ branches/soc-2008-quorn/release/scripts/textplugin_imports.py	2008-07-15 12:55:20 UTC (rev 15586)
@@ -13,7 +13,7 @@
 	import bpy, sys
 	from BPyTextPlugin import *
 	OK = True
-except:
+except ImportError:
 	pass
 
 def main():
@@ -21,7 +21,7 @@
 	line, c = current_line(txt)
 	
 	# Check we are in a normal context
-	if get_context(line, c) != 0:
+	if get_context(txt) != 0:
 		return
 	
 	pos = line.rfind('from ', 0, c)
@@ -30,7 +30,7 @@
 	if pos == -1:
 		# Check instead for straight 'import'
 		pos2 = line.rfind('import ', 0, c)
-		if pos2 != -1 and pos2 == c-7:
+		if pos2 != -1 and (pos2 == c-7 or (pos2 < c-7 and line[c-2]==',')):
 			items = [(m, 'm') for m in sys.builtin_module_names]
 			items.sort(cmp = suggest_cmp)
 			txt.suggest(items, '')
@@ -54,7 +54,7 @@
 			between = line[pos+5:pos2-1].strip()
 			try:
 				mod = get_module(between)
-			except:
+			except ImportError:
 				print 'Module not found:', between
 				return
 			

Modified: branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py	2008-07-15 12:54:57 UTC (rev 15585)
+++ branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py	2008-07-15 12:55:20 UTC (rev 15586)
@@ -13,7 +13,7 @@
 	import bpy
 	from BPyTextPlugin import *
 	OK = True
-except:
+except ImportError:
 	OK = False
 
 def main():
@@ -21,7 +21,7 @@
 	(line, c) = current_line(txt)
 	
 	# Check we are in a normal context
-	if get_context(line, c) != NORMAL:
+	if get_context(txt) != NORMAL:
 		return
 	
 	pre = get_targets(line, c)
@@ -43,21 +43,24 @@
 	try:
 		for name in pre[1:-1]:
 			obj = getattr(obj, name)
-	except:
+	except AttributeError:
 		print "Attribute not found '%s' in '%s'" % (name, '.'.join(pre))
 		return
 	
 	try:
 		attr = obj.__dict__.keys()
-	except:
+	except AttributeError:
 		attr = dir(obj)
 	
 	for k in attr:
-		v = getattr(obj, k)
-		if is_module(v): t = 'm'
-		elif callable(v): t = 'f'
-		else: t = 'v'
-		list.append((k, t))
+		try:
+			v = getattr(obj, k)
+			if is_module(v): t = 'm'
+			elif callable(v): t = 'f'
+			else: t = 'v'
+			list.append((k, t))
+		except (AttributeError, TypeError): # Some attributes are not readable
+			pass
 	
 	if list != []:
 		list.sort(cmp = suggest_cmp)

Modified: branches/soc-2008-quorn/release/scripts/textplugin_suggest.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/textplugin_suggest.py	2008-07-15 12:54:57 UTC (rev 15585)
+++ branches/soc-2008-quorn/release/scripts/textplugin_suggest.py	2008-07-15 12:55:20 UTC (rev 15586)
@@ -12,36 +12,36 @@
 	import bpy
 	from BPyTextPlugin import *
 	OK = True
-except:
+except ImportError:
 	OK = False
 
+def check_membersuggest(line, c):
+	return c > 0 and line[c-1] == '.'
+
+def check_imports(line, c):
+	return line.rfind('import ', 0, c) == c-7 or line.rfind('from ', 0, c) == c-5
+
 def main():
 	txt = bpy.data.texts.active
 	(line, c) = current_line(txt)
 	
 	# Check we are in a normal context
-	if get_context(line, c) != NORMAL:
+	if get_context(txt) != NORMAL:
 		return
 	
-	# Check that which precedes the cursor and perform the following:
-	# Period(.)				- Run textplugin_membersuggest.py
-	# 'import' or 'from'	- Run textplugin_imports.py

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list