[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