~siretart/ubuntu/utopic/blender/libav10

« back to all changes in this revision

Viewing changes to release/scripts/bpymodules/BPyTextPlugin.py

  • Committer: Bazaar Package Importer
  • Author(s): Kevin Roy
  • Date: 2011-02-08 22:20:54 UTC
  • mfrom: (1.4.2 upstream)
  • mto: (14.2.6 sid) (1.5.1)
  • mto: This revision was merged to the branch mainline in revision 27.
  • Revision ID: james.westby@ubuntu.com-20110208222054-kk0gwa4bu8h5lyq4
Tags: upstream-2.56.1-beta-svn34076
ImportĀ upstreamĀ versionĀ 2.56.1-beta-svn34076

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"""The BPyTextPlugin Module
2
 
 
3
 
Use get_cached_descriptor(txt) to retrieve information about the script held in
4
 
the txt Text object.
5
 
 
6
 
Use print_cache_for(txt) to print the information to the console.
7
 
 
8
 
Use line, cursor = current_line(txt) to get the logical line and cursor position
9
 
 
10
 
Use get_targets(line, cursor) to find out what precedes the cursor:
11
 
        aaa.bbb.cc|c.ddd -> ['aaa', 'bbb', 'cc']
12
 
 
13
 
Use resolve_targets(txt, targets) to turn a target list into a usable object if
14
 
one is found to match.
15
 
"""
16
 
 
17
 
import bpy, sys, os
18
 
import __builtin__, tokenize
19
 
from Blender.sys import time
20
 
from tokenize import generate_tokens, TokenError, \
21
 
                COMMENT, DEDENT, INDENT, NAME, NEWLINE, NL, STRING, NUMBER
22
 
 
23
 
class Definition:
24
 
        """Describes a definition or defined object through its name, line number
25
 
        and docstring. This is the base class for definition based descriptors.
26
 
        """
27
 
        
28
 
        def __init__(self, name, lineno, doc=''):
29
 
                self.name = name
30
 
                self.lineno = lineno
31
 
                self.doc = doc
32
 
 
33
 
class ScriptDesc:
34
 
        """Describes a script through lists of further descriptor objects (classes,
35
 
        defs, vars) and dictionaries to built-in types (imports). If a script has
36
 
        not been fully parsed, its incomplete flag will be set. The time of the last
37
 
        parse is held by the time field and the name of the text object from which
38
 
        it was parsed, the name field.
39
 
        """
40
 
        
41
 
        def __init__(self, name, imports, classes, defs, vars, incomplete=False):
42
 
                self.name = name
43
 
                self.imports = imports
44
 
                self.classes = classes
45
 
                self.defs = defs
46
 
                self.vars = vars
47
 
                self.incomplete = incomplete
48
 
                self.parse_due = 0
49
 
        
50
 
        def set_delay(self, delay):
51
 
                self.parse_due = time() + delay
52
 
 
53
 
class ClassDesc(Definition):
54
 
        """Describes a class through lists of further descriptor objects (defs and
55
 
        vars). The name of the class is held by the name field and the line on
56
 
        which it is defined is held in lineno.
57
 
        """
58
 
        
59
 
        def __init__(self, name, parents, defs, vars, lineno, doc=''):
60
 
                Definition.__init__(self, name, lineno, doc)
61
 
                self.parents = parents
62
 
                self.defs = defs
63
 
                self.vars = vars
64
 
 
65
 
class FunctionDesc(Definition):
66
 
        """Describes a function through its name and list of parameters (name,
67
 
        params) and the line on which it is defined (lineno).
68
 
        """
69
 
        
70
 
        def __init__(self, name, params, lineno, doc=''):
71
 
                Definition.__init__(self, name, lineno, doc)
72
 
                self.params = params
73
 
 
74
 
class VarDesc(Definition):
75
 
        """Describes a variable through its name and type (if ascertainable) and the
76
 
        line on which it is defined (lineno). If no type can be determined, type
77
 
        will equal None.
78
 
        """
79
 
        
80
 
        def __init__(self, name, type, lineno):
81
 
                Definition.__init__(self, name, lineno)
82
 
                self.type = type # None for unknown (supports: dict/list/str)
83
 
 
84
 
# Context types
85
 
CTX_UNSET = -1
86
 
CTX_NORMAL = 0
87
 
CTX_SINGLE_QUOTE = 1
88
 
CTX_DOUBLE_QUOTE = 2
89
 
CTX_COMMENT = 3
90
 
 
91
 
# Python keywords
92
 
KEYWORDS = ['and', 'del', 'from', 'not', 'while', 'as', 'elif', 'global',
93
 
                        'or', 'with', 'assert', 'else', 'if', 'pass', 'yield',
94
 
                        'break', 'except', 'import', 'print', 'class', 'exec', 'in',
95
 
                        'raise', 'continue', 'finally', 'is', 'return', 'def', 'for',
96
 
                        'lambda', 'try' ]
97
 
 
98
 
# Module file extensions
99
 
MODULE_EXTS = ['.py', '.pyc', '.pyo', '.pyw', '.pyd']
100
 
 
101
 
ModuleType = type(__builtin__)
102
 
NoneScriptDesc = ScriptDesc('', dict(), dict(), dict(), dict(), True)
103
 
 
104
 
_modules = {}
105
 
_modules_updated = 0
106
 
_parse_cache = dict()
107
 
 
108
 
def _load_module_names():
109
 
        """Searches the sys.path for module files and lists them, along with
110
 
        sys.builtin_module_names, in the global dict _modules.
111
 
        """
112
 
        
113
 
        global _modules
114
 
        
115
 
        for n in sys.builtin_module_names:
116
 
                _modules[n] = None
117
 
        for p in sys.path:
118
 
                if p == '': p = os.curdir
119
 
                if not os.path.isdir(p): continue
120
 
                for f in os.listdir(p):
121
 
                        for ext in MODULE_EXTS:
122
 
                                if f.endswith(ext):
123
 
                                        _modules[f[:-len(ext)]] = None
124
 
                                        break
125
 
 
126
 
_load_module_names()
127
 
 
128
 
def _trim_doc(doc):
129
 
        """Trims the quotes from a quoted STRING token (eg. "'''text'''" -> "text")
130
 
        """
131
 
        
132
 
        l = len(doc)
133
 
        i = 0
134
 
        while i < l/2 and (doc[i] == "'" or doc[i] == '"'):
135
 
                i += 1
136
 
        return doc[i:-i]
137
 
 
138
 
def resolve_targets(txt, targets):
139
 
        """Attempts to return a useful object for the locally or externally defined
140
 
        entity described by targets. If the object is local (defined in txt), a
141
 
        Definition instance is returned. If the object is external (imported or
142
 
        built in), the object itself is returned. If no object can be found, None is
143
 
        returned.
144
 
        """
145
 
        
146
 
        count = len(targets)
147
 
        if count==0: return None
148
 
        
149
 
        obj = None
150
 
        local = None
151
 
        i = 1
152
 
        
153
 
        desc = get_cached_descriptor(txt)
154
 
        b = targets[0].find('(')
155
 
        if b==-1: b = None # Trick to let us use [:b] and get the whole string
156
 
        
157
 
        if desc.classes.has_key(targets[0][:b]):
158
 
                local = desc.classes[targets[0][:b]]
159
 
        elif desc.defs.has_key(targets[0]):
160
 
                local = desc.defs[targets[0]]
161
 
        elif desc.vars.has_key(targets[0]):
162
 
                obj = desc.vars[targets[0]].type
163
 
        
164
 
        if local:
165
 
                while i < count:
166
 
                        b = targets[i].find('(')
167
 
                        if b==-1: b = None
168
 
                        if hasattr(local, 'classes') and local.classes.has_key(targets[i][:b]):
169
 
                                local = local.classes[targets[i][:b]]
170
 
                        elif hasattr(local, 'defs') and local.defs.has_key(targets[i]):
171
 
                                local = local.defs[targets[i]]
172
 
                        elif hasattr(local, 'vars') and local.vars.has_key(targets[i]):
173
 
                                obj = local.vars[targets[i]].type
174
 
                                local = None
175
 
                                i += 1
176
 
                                break
177
 
                        else:
178
 
                                local = None
179
 
                                break
180
 
                        i += 1
181
 
        
182
 
        if local: return local
183
 
        
184
 
        if not obj:
185
 
                if desc.imports.has_key(targets[0]):
186
 
                        obj = desc.imports[targets[0]]
187
 
                else:
188
 
                        builtins = get_builtins()
189
 
                        if builtins.has_key(targets[0]):
190
 
                                obj = builtins[targets[0]]
191
 
        
192
 
        while obj and i < count:
193
 
                if hasattr(obj, targets[i]):
194
 
                        obj = getattr(obj, targets[i])
195
 
                else:
196
 
                        obj = None
197
 
                        break
198
 
                i += 1
199
 
        
200
 
        return obj
201
 
 
202
 
def get_cached_descriptor(txt, force_parse=0):
203
 
        """Returns the cached ScriptDesc for the specified Text object 'txt'. If the
204
 
        script has not been parsed in the last 'period' seconds it will be reparsed
205
 
        to obtain this descriptor.
206
 
        
207
 
        Specifying TP_AUTO for the period (default) will choose a period based on the
208
 
        size of the Text object. Larger texts are parsed less often.
209
 
        """
210
 
        
211
 
        global _parse_cache
212
 
        
213
 
        parse = True
214
 
        key = hash(txt)
215
 
        if not force_parse and _parse_cache.has_key(key):
216
 
                desc = _parse_cache[key]
217
 
                if desc.parse_due > time():
218
 
                        parse = desc.incomplete
219
 
        
220
 
        if parse:
221
 
                desc = parse_text(txt)
222
 
        
223
 
        return desc
224
 
 
225
 
def parse_text(txt):
226
 
        """Parses an entire script's text and returns a ScriptDesc instance
227
 
        containing information about the script.
228
 
        
229
 
        If the text is not a valid Python script (for example if brackets are left
230
 
        open), parsing may fail to complete. However, if this occurs, no exception
231
 
        is thrown. Instead the returned ScriptDesc instance will have its incomplete
232
 
        flag set and information processed up to this point will still be accessible.
233
 
        """
234
 
        
235
 
        start_time = time()
236
 
        txt.reset()
237
 
        tokens = generate_tokens(txt.readline) # Throws TokenError
238
 
        
239
 
        curl, cursor = txt.getCursorPos()
240
 
        linen = curl + 1 # Token line numbers are one-based
241
 
        
242
 
        imports = dict()
243
 
        imp_step = 0
244
 
        
245
 
        classes = dict()
246
 
        cls_step = 0
247
 
        
248
 
        defs = dict()
249
 
        def_step = 0
250
 
        
251
 
        vars = dict()
252
 
        var1_step = 0
253
 
        var2_step = 0
254
 
        var3_step = 0
255
 
        var_accum = dict()
256
 
        var_forflag = False
257
 
        
258
 
        indent = 0
259
 
        prev_type = -1
260
 
        prev_text = ''
261
 
        incomplete = False
262
 
        
263
 
        while True:
264
 
                try:
265
 
                        type, text, start, end, line = tokens.next()
266
 
                except StopIteration:
267
 
                        break
268
 
                except (TokenError, IndentationError):
269
 
                        incomplete = True
270
 
                        break
271
 
                
272
 
                # Skip all comments and line joining characters
273
 
                if type == COMMENT or type == NL:
274
 
                        continue
275
 
                
276
 
                #################
277
 
                ## Indentation ##
278
 
                #################
279
 
                
280
 
                if type == INDENT:
281
 
                        indent += 1
282
 
                elif type == DEDENT:
283
 
                        indent -= 1
284
 
                
285
 
                #########################
286
 
                ## Module importing... ##
287
 
                #########################
288
 
                
289
 
                imp_store = False
290
 
                
291
 
                # Default, look for 'from' or 'import' to start
292
 
                if imp_step == 0:
293
 
                        if text == 'from':
294
 
                                imp_tmp = []
295
 
                                imp_step = 1
296
 
                        elif text == 'import':
297
 
                                imp_from = None
298
 
                                imp_tmp = []
299
 
                                imp_step = 2
300
 
                
301
 
                # Found a 'from', create imp_from in form '???.???...'
302
 
                elif imp_step == 1:
303
 
                        if text == 'import':
304
 
                                imp_from = '.'.join(imp_tmp)
305
 
                                imp_tmp = []
306
 
                                imp_step = 2
307
 
                        elif type == NAME:
308
 
                                imp_tmp.append(text)
309
 
                        elif text != '.':
310
 
                                imp_step = 0 # Invalid syntax
311
 
                
312
 
                # Found 'import', imp_from is populated or None, create imp_name
313
 
                elif imp_step == 2:
314
 
                        if text == 'as':
315
 
                                imp_name = '.'.join(imp_tmp)
316
 
                                imp_step = 3
317
 
                        elif type == NAME or text == '*':
318
 
                                imp_tmp.append(text)
319
 
                        elif text != '.':
320
 
                                imp_name = '.'.join(imp_tmp)
321
 
                                imp_symb = imp_name
322
 
                                imp_store = True
323
 
                
324
 
                # Found 'as', change imp_symb to this value and go back to step 2
325
 
                elif imp_step == 3:
326
 
                        if type == NAME:
327
 
                                imp_symb = text
328
 
                        else:
329
 
                                imp_store = True
330
 
                
331
 
                # Both imp_name and imp_symb have now been populated so we can import
332
 
                if imp_store:
333
 
                        
334
 
                        # Handle special case of 'import *'
335
 
                        if imp_name == '*':
336
 
                                parent = get_module(imp_from)
337
 
                                imports.update(parent.__dict__)
338
 
                                
339
 
                        else:
340
 
                                # Try importing the name as a module
341
 
                                try:
342
 
                                        if imp_from:
343
 
                                                module = get_module(imp_from +'.'+ imp_name)
344
 
                                        else:
345
 
                                                module = get_module(imp_name)
346
 
                                except (ImportError, ValueError, AttributeError, TypeError):
347
 
                                        # Try importing name as an attribute of the parent
348
 
                                        try:
349
 
                                                module = __import__(imp_from, globals(), locals(), [imp_name])
350
 
                                                imports[imp_symb] = getattr(module, imp_name)
351
 
                                        except (ImportError, ValueError, AttributeError, TypeError):
352
 
                                                pass
353
 
                                else:
354
 
                                        imports[imp_symb] = module
355
 
                        
356
 
                        # More to import from the same module?
357
 
                        if text == ',':
358
 
                                imp_tmp = []
359
 
                                imp_step = 2
360
 
                        else:
361
 
                                imp_step = 0
362
 
                
363
 
                ###################
364
 
                ## Class parsing ##
365
 
                ###################
366
 
                
367
 
                # If we are inside a class then def and variable parsing should be done
368
 
                # for the class. Otherwise the definitions are considered global
369
 
                
370
 
                # Look for 'class'
371
 
                if cls_step == 0:
372
 
                        if text == 'class':
373
 
                                cls_name = None
374
 
                                cls_lineno = start[0]
375
 
                                cls_indent = indent
376
 
                                cls_step = 1
377
 
                
378
 
                # Found 'class', look for cls_name followed by '(' parents ')'
379
 
                elif cls_step == 1:
380
 
                        if not cls_name:
381
 
                                if type == NAME:
382
 
                                        cls_name = text
383
 
                                        cls_sline = False
384
 
                                        cls_parents = dict()
385
 
                                        cls_defs = dict()
386
 
                                        cls_vars = dict()
387
 
                        elif type == NAME:
388
 
                                if classes.has_key(text):
389
 
                                        parent = classes[text]
390
 
                                        cls_parents[text] = parent
391
 
                                        cls_defs.update(parent.defs)
392
 
                                        cls_vars.update(parent.vars)
393
 
                        elif text == ':':
394
 
                                cls_step = 2
395
 
                
396
 
                # Found 'class' name ... ':', now check if it's a single line statement
397
 
                elif cls_step == 2:
398
 
                        if type == NEWLINE:
399
 
                                cls_sline = False
400
 
                        else:
401
 
                                cls_sline = True
402
 
                        cls_doc = ''
403
 
                        cls_step = 3
404
 
                
405
 
                elif cls_step == 3:
406
 
                        if not cls_doc and type == STRING:
407
 
                                cls_doc = _trim_doc(text)
408
 
                        if cls_sline:
409
 
                                if type == NEWLINE:
410
 
                                        classes[cls_name] = ClassDesc(cls_name, cls_parents, cls_defs, cls_vars, cls_lineno, cls_doc)
411
 
                                        cls_step = 0
412
 
                        else:
413
 
                                if type == DEDENT and indent <= cls_indent:
414
 
                                        classes[cls_name] = ClassDesc(cls_name, cls_parents, cls_defs, cls_vars, cls_lineno, cls_doc)
415
 
                                        cls_step = 0
416
 
                
417
 
                #################
418
 
                ## Def parsing ##
419
 
                #################
420
 
                
421
 
                # Look for 'def'
422
 
                if def_step == 0:
423
 
                        if text == 'def':
424
 
                                def_name = None
425
 
                                def_lineno = start[0]
426
 
                                def_step = 1
427
 
                
428
 
                # Found 'def', look for def_name followed by '('
429
 
                elif def_step == 1:
430
 
                        if type == NAME:
431
 
                                def_name = text
432
 
                                def_params = []
433
 
                        elif def_name and text == '(':
434
 
                                def_step = 2
435
 
                
436
 
                # Found 'def' name '(', now identify the parameters upto ')'
437
 
                # TODO: Handle ellipsis '...'
438
 
                elif def_step == 2:
439
 
                        if type == NAME:
440
 
                                def_params.append(text)
441
 
                        elif text == ':':
442
 
                                def_step = 3
443
 
                
444
 
                # Found 'def' ... ':', now check if it's a single line statement
445
 
                elif def_step == 3:
446
 
                        if type == NEWLINE:
447
 
                                def_sline = False
448
 
                        else:
449
 
                                def_sline = True
450
 
                        def_doc = ''
451
 
                        def_step = 4
452
 
                
453
 
                elif def_step == 4:
454
 
                        if type == STRING:
455
 
                                def_doc = _trim_doc(text)
456
 
                        newdef = None
457
 
                        if def_sline:
458
 
                                if type == NEWLINE:
459
 
                                        newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
460
 
                        else:
461
 
                                if type == NAME:
462
 
                                        newdef = FunctionDesc(def_name, def_params, def_lineno, def_doc)
463
 
                        if newdef:
464
 
                                if cls_step > 0: # Parsing a class
465
 
                                        cls_defs[def_name] = newdef
466
 
                                else:
467
 
                                        defs[def_name] = newdef
468
 
                                def_step = 0
469
 
                
470
 
                ##########################
471
 
                ## Variable assignation ##
472
 
                ##########################
473
 
                
474
 
                if cls_step > 0: # Parsing a class
475
 
                        # Look for 'self.???'
476
 
                        if var1_step == 0:
477
 
                                if text == 'self':
478
 
                                        var1_step = 1
479
 
                        elif var1_step == 1:
480
 
                                if text == '.':
481
 
                                        var_name = None
482
 
                                        var1_step = 2
483
 
                                else:
484
 
                                        var1_step = 0
485
 
                        elif var1_step == 2:
486
 
                                if type == NAME:
487
 
                                        var_name = text
488
 
                                        if cls_vars.has_key(var_name):
489
 
                                                var_step = 0
490
 
                                        else:
491
 
                                                var1_step = 3
492
 
                        elif var1_step == 3:
493
 
                                if text == '=':
494
 
                                        var1_step = 4
495
 
                                elif text != ',':
496
 
                                        var1_step = 0
497
 
                        elif var1_step == 4:
498
 
                                var_type = None
499
 
                                if type == NUMBER:
500
 
                                        close = end[1]
501
 
                                        if text.find('.') != -1: var_type = float
502
 
                                        else: var_type = int
503
 
                                elif type == STRING:
504
 
                                        close = end[1]
505
 
                                        var_type = str
506
 
                                elif text == '[':
507
 
                                        close = line.find(']', end[1])
508
 
                                        var_type = list
509
 
                                elif text == '(':
510
 
                                        close = line.find(')', end[1])
511
 
                                        var_type = tuple
512
 
                                elif text == '{':
513
 
                                        close = line.find('}', end[1])
514
 
                                        var_type = dict
515
 
                                elif text == 'dict':
516
 
                                        close = line.find(')', end[1])
517
 
                                        var_type = dict
518
 
                                if var_type and close+1 < len(line):
519
 
                                        if line[close+1] != ' ' and line[close+1] != '\t':
520
 
                                                var_type = None
521
 
                                cls_vars[var_name] = VarDesc(var_name, var_type, start[0])
522
 
                                var1_step = 0
523
 
                
524
 
                elif def_step > 0: # Parsing a def
525
 
                        # Look for 'global ???[,???]'
526
 
                        if var2_step == 0:
527
 
                                if text == 'global':
528
 
                                        var2_step = 1
529
 
                        elif var2_step == 1:
530
 
                                if type == NAME:
531
 
                                        if not vars.has_key(text):
532
 
                                                vars[text] = VarDesc(text, None, start[0])
533
 
                                elif text != ',' and type != NL:
534
 
                                        var2_step == 0
535
 
                
536
 
                else: # In global scope
537
 
                        if var3_step == 0:
538
 
                                # Look for names
539
 
                                if text == 'for':
540
 
                                        var_accum = dict()
541
 
                                        var_forflag = True
542
 
                                elif text == '=' or (var_forflag and text == 'in'):
543
 
                                        var_forflag = False
544
 
                                        var3_step = 1
545
 
                                elif type == NAME:
546
 
                                        if prev_text != '.' and not vars.has_key(text):
547
 
                                                var_accum[text] = VarDesc(text, None, start[0])
548
 
                                elif not text in [',', '(', ')', '[', ']']:
549
 
                                        var_accum = dict()
550
 
                                        var_forflag = False
551
 
                        elif var3_step == 1:
552
 
                                if len(var_accum) != 1:
553
 
                                        var_type = None
554
 
                                        vars.update(var_accum)
555
 
                                else:
556
 
                                        var_name = var_accum.keys()[0]
557
 
                                        var_type = None
558
 
                                        if type == NUMBER:
559
 
                                                if text.find('.') != -1: var_type = float
560
 
                                                else: var_type = int
561
 
                                        elif type == STRING: var_type = str
562
 
                                        elif text == '[': var_type = list
563
 
                                        elif text == '(': var_type = tuple
564
 
                                        elif text == '{': var_type = dict
565
 
                                        vars[var_name] = VarDesc(var_name, var_type, start[0])
566
 
                                var3_step = 0
567
 
                
568
 
                #######################
569
 
                ## General utilities ##
570
 
                #######################
571
 
                
572
 
                prev_type = type
573
 
                prev_text = text
574
 
        
575
 
        desc = ScriptDesc(txt.name, imports, classes, defs, vars, incomplete)
576
 
        desc.set_delay(10 * (time()-start_time) + 0.05)
577
 
        
578
 
        global _parse_cache
579
 
        _parse_cache[hash(txt)] = desc
580
 
        return desc
581
 
 
582
 
def get_modules(since=1):
583
 
        """Returns the set of built-in modules and any modules that have been
584
 
        imported into the system upto 'since' seconds ago.
585
 
        """
586
 
        
587
 
        global _modules, _modules_updated
588
 
        
589
 
        t = time()
590
 
        if _modules_updated < t - since:
591
 
                _modules.update(sys.modules)
592
 
                _modules_updated = t
593
 
        return _modules.keys()
594
 
 
595
 
def suggest_cmp(x, y):
596
 
        """Use this method when sorting a list of suggestions.
597
 
        """
598
 
        
599
 
        return cmp(x[0].upper(), y[0].upper())
600
 
 
601
 
def get_module(name):
602
 
        """Returns the module specified by its name. The module itself is imported
603
 
        by this method and, as such, any initialization code will be executed.
604
 
        """
605
 
        
606
 
        mod = __import__(name)
607
 
        components = name.split('.')
608
 
        for comp in components[1:]:
609
 
                mod = getattr(mod, comp)
610
 
        return mod
611
 
 
612
 
def type_char(v):
613
 
        """Returns the character used to signify the type of a variable. Use this
614
 
        method to identify the type character for an item in a suggestion list.
615
 
        
616
 
        The following values are returned:
617
 
          'm' if the parameter is a module
618
 
          'f' if the parameter is callable
619
 
          'v' if the parameter is variable or otherwise indeterminable
620
 
        
621
 
        """
622
 
        
623
 
        if isinstance(v, ModuleType):
624
 
                return 'm'
625
 
        elif callable(v):
626
 
                return 'f'
627
 
        else: 
628
 
                return 'v'
629
 
 
630
 
def get_context(txt):
631
 
        """Establishes the context of the cursor in the given Blender Text object
632
 
        
633
 
        Returns one of:
634
 
          CTX_NORMAL - Cursor is in a normal context
635
 
          CTX_SINGLE_QUOTE - Cursor is inside a single quoted string
636
 
          CTX_DOUBLE_QUOTE - Cursor is inside a double quoted string
637
 
          CTX_COMMENT - Cursor is inside a comment
638
 
        
639
 
        """
640
 
        
641
 
        l, cursor = txt.getCursorPos()
642
 
        lines = txt.asLines(0, l+1)
643
 
        
644
 
        # FIXME: This method is too slow in large files for it to be called as often
645
 
        # as it is. So for lines below the 1000th line we do this... (quorn)
646
 
        if l > 1000: return CTX_NORMAL
647
 
        
648
 
        # Detect context (in string or comment)
649
 
        in_str = CTX_NORMAL
650
 
        for line in lines:
651
 
                if l == 0:
652
 
                        end = cursor
653
 
                else:
654
 
                        end = len(line)
655
 
                        l -= 1
656
 
                
657
 
                # Comments end at new lines
658
 
                if in_str == CTX_COMMENT:
659
 
                        in_str = CTX_NORMAL
660
 
                
661
 
                for i in range(end):
662
 
                        if in_str == 0:
663
 
                                if line[i] == "'": in_str = CTX_SINGLE_QUOTE
664
 
                                elif line[i] == '"': in_str = CTX_DOUBLE_QUOTE
665
 
                                elif line[i] == '#': in_str = CTX_COMMENT
666
 
                        else:
667
 
                                if in_str == CTX_SINGLE_QUOTE:
668
 
                                        if line[i] == "'":
669
 
                                                in_str = CTX_NORMAL
670
 
                                                # In again if ' escaped, out again if \ escaped, and so on
671
 
                                                for a in range(i-1, -1, -1):
672
 
                                                        if line[a] == '\\': in_str = 1-in_str
673
 
                                                        else: break
674
 
                                elif in_str == CTX_DOUBLE_QUOTE:
675
 
                                        if line[i] == '"':
676
 
                                                in_str = CTX_NORMAL
677
 
                                                # In again if " escaped, out again if \ escaped, and so on
678
 
                                                for a in range(i-1, -1, -1):
679
 
                                                        if line[i-a] == '\\': in_str = 2-in_str
680
 
                                                        else: break
681
 
                
682
 
        return in_str
683
 
 
684
 
def current_line(txt):
685
 
        """Extracts the Python script line at the cursor in the Blender Text object
686
 
        provided and cursor position within this line as the tuple pair (line,
687
 
        cursor).
688
 
        """
689
 
        
690
 
        lineindex, cursor = txt.getCursorPos()
691
 
        lines = txt.asLines()
692
 
        line = lines[lineindex]
693
 
        
694
 
        # Join previous lines to this line if spanning
695
 
        i = lineindex - 1
696
 
        while i > 0:
697
 
                earlier = lines[i].rstrip()
698
 
                if earlier.endswith('\\'):
699
 
                        line = earlier[:-1] + ' ' + line
700
 
                        cursor += len(earlier)
701
 
                i -= 1
702
 
        
703
 
        # Join later lines while there is an explicit joining character
704
 
        i = lineindex
705
 
        while i < len(lines)-1 and lines[i].rstrip().endswith('\\'):
706
 
                later = lines[i+1].strip()
707
 
                line = line + ' ' + later[:-1]
708
 
                i += 1
709
 
        
710
 
        return line, cursor
711
 
 
712
 
def get_targets(line, cursor):
713
 
        """Parses a period separated string of valid names preceding the cursor and
714
 
        returns them as a list in the same order.
715
 
        """
716
 
        
717
 
        brk = 0
718
 
        targets = []
719
 
        j = cursor
720
 
        i = j-1
721
 
        while i >= 0:
722
 
                if line[i] == ')': brk += 1
723
 
                elif brk:
724
 
                        if line[i] == '(': brk -= 1
725
 
                else:
726
 
                        if line[i] == '.':
727
 
                                targets.insert(0, line[i+1:j]); j=i
728
 
                        elif not (line[i].isalnum() or line[i] == '_' or line[i] == '.'):
729
 
                                break
730
 
                i -= 1
731
 
        targets.insert(0, line[i+1:j])
732
 
        return targets
733
 
 
734
 
def get_defs(txt):
735
 
        """Returns a dictionary which maps definition names in the source code to
736
 
        a list of their parameter names.
737
 
        
738
 
        The line 'def doit(one, two, three): print one' for example, results in the
739
 
        mapping 'doit' : [ 'one', 'two', 'three' ]
740
 
        """
741
 
        
742
 
        return get_cached_descriptor(txt).defs
743
 
 
744
 
def get_vars(txt):
745
 
        """Returns a dictionary of variable names found in the specified Text
746
 
        object. This method locates all names followed directly by an equal sign:
747
 
        'a = ???' or indirectly as part of a tuple/list assignment or inside a
748
 
        'for ??? in ???:' block.
749
 
        """
750
 
        
751
 
        return get_cached_descriptor(txt).vars
752
 
 
753
 
def get_imports(txt):
754
 
        """Returns a dictionary which maps symbol names in the source code to their
755
 
        respective modules.
756
 
        
757
 
        The line 'from Blender import Text as BText' for example, results in the
758
 
        mapping 'BText' : <module 'Blender.Text' (built-in)>
759
 
        
760
 
        Note that this method imports the modules to provide this mapping as as such
761
 
        will execute any initilization code found within.
762
 
        """
763
 
        
764
 
        return get_cached_descriptor(txt).imports
765
 
 
766
 
def get_builtins():
767
 
        """Returns a dictionary of built-in modules, functions and variables."""
768
 
        
769
 
        return __builtin__.__dict__
770
 
 
771
 
 
772
 
#################################
773
 
## Debugging utility functions ##
774
 
#################################
775
 
 
776
 
def print_cache_for(txt, period=sys.maxint):
777
 
        """Prints out the data cached for a given Text object. If no period is
778
 
        given the text will not be reparsed and the cached version will be returned.
779
 
        Otherwise if the period has expired the text will be reparsed.
780
 
        """
781
 
        
782
 
        desc = get_cached_descriptor(txt, period)
783
 
        print '================================================'
784
 
        print 'Name:', desc.name, '('+str(hash(txt))+')'
785
 
        print '------------------------------------------------'
786
 
        print 'Defs:'
787
 
        for name, ddesc in desc.defs.items():
788
 
                print ' ', name, ddesc.params, ddesc.lineno
789
 
                print '   ', ddesc.doc
790
 
        print '------------------------------------------------'
791
 
        print 'Vars:'
792
 
        for name, vdesc in desc.vars.items():
793
 
                print ' ', name, vdesc.type, vdesc.lineno
794
 
        print '------------------------------------------------'
795
 
        print 'Imports:'
796
 
        for name, item in desc.imports.items():
797
 
                print ' ', name.ljust(15), item
798
 
        print '------------------------------------------------'
799
 
        print 'Classes:'
800
 
        for clsnme, clsdsc in desc.classes.items():
801
 
                print '  *********************************'
802
 
                print '  Name:', clsnme
803
 
                print ' ', clsdsc.doc
804
 
                print '  ---------------------------------'
805
 
                print '  Defs:'
806
 
                for name, ddesc in clsdsc.defs.items():
807
 
                        print '   ', name, ddesc.params, ddesc.lineno
808
 
                        print '     ', ddesc.doc
809
 
                print '  ---------------------------------'
810
 
                print '  Vars:'
811
 
                for name, vdesc in clsdsc.vars.items():
812
 
                        print '   ', name, vdesc.type, vdesc.lineno
813
 
                print '  *********************************'
814
 
        print '================================================'