[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [15583] branches/soc-2008-quorn/release/ scripts: Created a BPy module BPyTextPlugin to centralize functions used across the text plugin scripts .

Ian Thompson quornian at googlemail.com
Tue Jul 15 09:34:46 CEST 2008


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

Log Message:
-----------
Created a BPy module BPyTextPlugin to centralize functions used across the text plugin scripts. Also created two more scripts to handle imports and member suggestions.

Modified Paths:
--------------
    branches/soc-2008-quorn/release/scripts/textplugin_suggest.py

Added 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

Added: branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py	                        (rev 0)
+++ branches/soc-2008-quorn/release/scripts/bpymodules/BPyTextPlugin.py	2008-07-15 07:34:46 UTC (rev 15583)
@@ -0,0 +1,271 @@
+import bpy, sys
+import __builtin__, tokenize
+from tokenize import generate_tokens
+# TODO: Remove the dependency for a full Python installation. Currently only the
+# tokenize module is required 
+
+# Context types
+NORMAL = 0
+SINGLE_QUOTE = 1
+DOUBLE_QUOTE = 2
+COMMENT = 3
+
+# 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' ]
+
+
+def suggest_cmp(x, y):
+	"""Use this method when sorting a list for suggestions"""
+	
+	return cmp(x[0], y[0])
+
+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."""
+	
+	mod = __import__(name)
+	components = name.split('.')
+	for comp in components[1:]:
+		mod = getattr(mod, comp)
+	return mod
+
+def is_module(m):
+	"""Taken from the inspect module of the standard Python installation"""
+	
+	return isinstance(m, type(bpy))
+
+def type_char(v):
+	if is_module(v):
+		return 'm'
+	elif callable(v):
+		return 'f'
+	else: 
+		return 'v'
+
+def get_context(line, cursor):
+	"""Establishes the context of the cursor in the given line
+	
+	Returns one of:
+	  NORMAL - Cursor is in a normal context
+	  SINGLE_QUOTE - Cursor is inside a single quoted string
+	  DOUBLE_QUOTE - Cursor is inside a double quoted string
+	  COMMENT - Cursor is inside a comment
+	
+	"""
+	
+	# 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
+		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
+	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,
+	cursor)"""
+	
+	(lineindex, cursor) = txt.getCursorPos()
+	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
+	while i < len(lines)-1 and line[i].rstrip().endswith('\\'):
+		later = lines[i+1].strip()
+		line = line + ' ' + later[:-1]
+	
+	return line, cursor
+
+def get_targets(line, cursor):
+	"""Parses a period separated string of valid names preceding the cursor and
+	returns them as a list in the same order."""
+	
+	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('.')
+
+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.
+	"""
+	
+	# Unfortunately, generate_tokens may fail if the script leaves brackets or
+	# 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:
+		return dict()
+	
+	imports = dict()
+	step = 0
+	
+	for type, string, start, end, line in tokens:
+		store = False
+		
+		# Default, look for 'from' or 'import' to start
+		if step == 0:
+			if string == 'from':
+				tmp = []
+				step = 1
+			elif string == 'import':
+				fromname = None
+				tmp = []
+				step = 2
+		
+		# Found a 'from', create fromname in form '???.???...'
+		elif step == 1:
+			if string == 'import':
+				fromname = '.'.join(tmp)
+				tmp = []
+				step = 2
+			elif type == tokenize.NAME:
+				tmp.append(string)
+			elif string != '.':
+				step = 0 # Invalid syntax
+		
+		# Found 'import', fromname is populated or None, create impname
+		elif step == 2:
+			if string == 'as':
+				impname = '.'.join(tmp)
+				step = 3
+			elif type == tokenize.NAME:
+				tmp.append(string)
+			elif string != '.':
+				impname = '.'.join(tmp)
+				symbol = impname
+				store = True
+		
+		# Found 'as', change symbol to this value and go back to step 2
+		elif step == 3:
+			if type == tokenize.NAME:
+				symbol = string
+			else:
+				store = True
+		
+		# Both impname and symbol have now been populated so we can import
+		if store:
+			
+			# Handle special case of 'import *'
+			if impname == '*':
+				parent = get_module(fromname)
+				for symbol, attr in parent.__dict__.items():
+					imports[symbol] = attr
+				
+			else:
+				# Try importing the name as a module
+				try:
+					if fromname:
+						module = get_module(fromname +'.'+ impname)
+					else:
+						module = get_module(impname)
+					imports[symbol] = module
+				except:
+					# Try importing name as an attribute of the parent
+					try:
+						module = __import__(fromname, globals(), locals(), [impname])
+						imports[symbol] = getattr(module, impname)
+					except:
+						pass
+			
+			# More to import from the same module?
+			if string == ',':
+				tmp = []
+				step = 2
+			else:
+				step = 0
+	
+	return imports
+
+
+def get_builtins():
+	"""Returns a dictionary of built-in modules, functions and variables."""
+	
+	return __builtin__.__dict__
+
+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' ]
+	"""
+	
+	# See above for problems with generate_tokens
+	try:
+		txt.reset()
+		tokens = generate_tokens(txt.readline)
+	except:
+		return dict()
+	
+	defs = dict()
+	step = 0
+	
+	for type, string, start, end, line in tokens:
+		
+		# Look for 'def'
+		if step == 0:
+			if string == 'def':
+				name = None
+				step = 1
+		
+		# Found 'def', look for name followed by '('
+		elif step == 1:
+			if type == tokenize.NAME:
+				name = string
+				params = []
+			elif name and string == '(':
+				step = 2
+		
+		# Found 'def' name '(', now identify the parameters upto ')'
+		# TODO: Handle ellipsis '...'
+		elif step == 2:
+			if type == tokenize.NAME:
+				params.append(string)
+			elif string == ')':
+				defs[name] = params
+				step = 0
+		
+	return defs

Added: branches/soc-2008-quorn/release/scripts/textplugin_imports.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/textplugin_imports.py	                        (rev 0)
+++ branches/soc-2008-quorn/release/scripts/textplugin_imports.py	2008-07-15 07:34:46 UTC (rev 15583)
@@ -0,0 +1,71 @@
+#!BPY
+"""
+Name: 'Import Complete'
+Blender: 246
+Group: 'TextPlugin'
+Shortcut: 'Space'
+Tooltip: 'Lists modules when import or from is typed'
+"""
+
+# Only run if we have the required modules
+OK = False
+try:
+	import bpy, sys
+	from BPyTextPlugin import *
+	OK = True
+except:
+	pass
+
+def main():
+	txt = bpy.data.texts.active
+	line, c = current_line(txt)
+	
+	# Check we are in a normal context
+	if get_context(line, c) != 0:
+		return
+	
+	pos = line.rfind('from ', 0, c)
+	
+	# No 'from' found
+	if pos == -1:
+		# Check instead for straight 'import'
+		pos2 = line.rfind('import ', 0, c)
+		if pos2 != -1 and pos2 == c-7:
+			items = [(m, 'm') for m in sys.builtin_module_names]
+			items.sort(cmp = suggest_cmp)
+			txt.suggest(items, '')
+	
+	# Immediate 'from' before cursor
+	elif pos == c-5:
+		items = [(m, 'm') for m in sys.builtin_module_names]
+		items.sort(cmp = suggest_cmp)
+		txt.suggest(items, '')
+	
+	# Found 'from' earlier
+	else:
+		pos2 = line.rfind('import ', pos+5, c)
+		
+		# No 'import' found after 'from' so suggest it
+		if pos2 == -1:
+			txt.suggest([('import', 'k')], '')
+			
+		# Immediate 'import' before cursor and after 'from...'
+		elif pos2 == c-7 or line[c-2] == ',':
+			between = line[pos+5:pos2-1].strip()
+			try:
+				mod = get_module(between)
+			except:
+				print 'Module not found:', between
+				return
+			
+			items = [('*', 'k')]
+			for (k,v) in mod.__dict__.items():
+				if is_module(v): t = 'm'
+				elif callable(v): t = 'f'
+				else: t = 'v'
+				items.append((k, t))
+			items.sort(cmp = suggest_cmp)
+			txt.suggest(items, '')
+
+if OK:
+	main()

Added: branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py
===================================================================
--- branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py	                        (rev 0)
+++ branches/soc-2008-quorn/release/scripts/textplugin_membersuggest.py	2008-07-15 07:34:46 UTC (rev 15583)
@@ -0,0 +1,67 @@
+#!BPY
+"""
+Name: 'Member Suggest'
+Blender: 246
+Group: 'TextPlugin'
+Shortcut: 'Period'
+Tooltip: 'Lists members of the object preceding the cursor in the current text \
+space'
+"""
+
+# Only run if we have the required modules
+try:
+	import bpy
+	from BPyTextPlugin import *
+	OK = True
+except:
+	OK = False
+
+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:
+		return
+	
+	pre = get_targets(line, c)
+	
+	if len(pre) <= 1:
+		return
+	
+	list = []
+	
+	imports = get_imports(txt)
+	
+	# Identify the root (root.sub.sub.)
+	if imports.has_key(pre[0]):
+		obj = imports[pre[0]]
+	else:
+		return
+	
+	# Step through sub-attributes
+	try:

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list