Utilities/ModuleParser.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Parse a Python module file.
8
9 This module is based on pyclbr.py as of Python 2.2.2
10
11 <b>BUGS</b> (from pyclbr.py)
12 <ul>
13 <li>Code that doesn't pass tabnanny or python -t will confuse it, unless
14 you set the module TABWIDTH variable (default 8) to the correct tab width
15 for the file.</li>
16 </ul>
17 """
18
19 import sys
20 import os
21 import imp
22 import re
23
24 import Utilities
25
26 __all__ = ["Module", "Class", "Function", "RbModule", "readModule"]
27
28 TABWIDTH = 4
29
30 PTL_SOURCE = 128
31 RB_SOURCE = 129
32
33 SUPPORTED_TYPES = [imp.PY_SOURCE, PTL_SOURCE, RB_SOURCE]
34
35 _py_getnext = re.compile(r"""
36 (?P<String>
37 \""" (?P<StringContents1>
38 [^"\\]* (?:
39 (?: \\. | "(?!"") )
40 [^"\\]*
41 )*
42 )
43 \"""
44
45 | ''' (?P<StringContents2>
46 [^'\\]* (?:
47 (?: \\. | '(?!'') )
48 [^'\\]*
49 )*
50 )
51 '''
52
53 | " [^"\\\n]* (?: \\. [^"\\\n]*)* "
54
55 | ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
56
57 | \#\#\# (?P<StringContents3>
58 [^#\\]* (?:
59 (?: \\. | \#(?!\#\#) )
60 [^#\\]*
61 )*
62 )
63 \#\#\#
64 )
65
66 | (?P<Docstring>
67 (?<= :) \s*
68 \""" (?P<DocstringContents1>
69 [^"\\]* (?:
70 (?: \\. | "(?!"") )
71 [^"\\]*
72 )*
73 )
74 \"""
75
76 | (?<= :) \s*
77 ''' (?P<DocstringContents2>
78 [^'\\]* (?:
79 (?: \\. | '(?!'') )
80 [^'\\]*
81 )*
82 )
83 '''
84
85 | (?<= :) \s*
86 \#\#\# (?P<DocstringContents3>
87 [^#\\]* (?:
88 (?: \\. | \#(?!\#\#) )
89 [^#\\]*
90 )*
91 )
92 \#\#\#
93 )
94
95 | (?P<Method>
96 (^ [ \t]* @ (?: PyQt4 \. )? (?: QtCore \. )? (?: pyqtSignature | pyqtSlot )
97 [ \t]* \(
98 (?P<MethodPyQtSignature> [^)]* )
99 \) \s*
100 )?
101 ^
102 (?P<MethodIndent> [ \t]* )
103 def [ \t]+
104 (?P<MethodName> [a-zA-Z_] \w* )
105 (?: [ \t]* \[ (?: plain | html ) \] )?
106 [ \t]* \(
107 (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? )
108 \) [ \t]* :
109 )
110
111 | (?P<Class>
112 ^
113 (?P<ClassIndent> [ \t]* )
114 class [ \t]+
115 (?P<ClassName> [a-zA-Z_] \w* )
116 [ \t]*
117 (?P<ClassSupers> \( [^)]* \) )?
118 [ \t]* :
119 )
120
121 | (?P<Attribute>
122 ^
123 (?P<AttributeIndent> [ \t]* )
124 self [ \t]* \. [ \t]*
125 (?P<AttributeName> [a-zA-Z_] \w* )
126 [ \t]* =
127 )
128
129 | (?P<Variable>
130 ^
131 (?P<VariableIndent> [ \t]* )
132 (?P<VariableName> [a-zA-Z_] \w* )
133 [ \t]* =
134 )
135
136 | (?P<Import>
137 ^ import [ \t]+
138 (?P<ImportList> [^#;\n]+ )
139 )
140
141 | (?P<ImportFrom>
142 ^ from [ \t]+
143 (?P<ImportFromPath>
144 [a-zA-Z_.] \w*
145 (?:
146 [ \t]* \. [ \t]* [a-zA-Z_] \w*
147 )*
148 )
149 [ \t]+
150 import [ \t]+
151 (?P<ImportFromList> [^#;\n]+ )
152 )
153
154 | (?P<ConditionalDefine>
155 ^
156 (?P<ConditionalDefineIndent> [ \t]* )
157 (?: (?: if | elif ) [ \t]+ [^:]* | else [ \t]* ) : (?= \s* def)
158 )
159 """, re.VERBOSE | re.DOTALL | re.MULTILINE).search
160
161 _rb_getnext = re.compile(r"""
162 (?P<Docstring>
163 =begin [ \t]+ edoc (?P<DocstringContents> .*? ) =end
164 )
165
166 | (?P<String>
167 =begin .*? =end
168
169 | <<-? (?P<HereMarker1> [a-zA-Z0-9_]+? ) [ \t]* .*? (?P=HereMarker1)
170
171 | <<-? ['"] (?P<HereMarker2> .*? ) ['"] [ \t]* .*? (?P=HereMarker2)
172
173 | " [^"\\\n]* (?: \\. [^"\\\n]*)* "
174
175 | ' [^'\\\n]* (?: \\. [^'\\\n]*)* '
176 )
177
178 | (?P<Comment>
179 ^
180 [ \t]* \#+ .*? $
181 )
182
183 | (?P<Method>
184 ^
185 (?P<MethodIndent> [ \t]* )
186 def [ \t]+
187 (?:
188 (?P<MethodName2> [a-zA-Z0-9_]+ (?: \. | :: ) [a-zA-Z_] [a-zA-Z0-9_?!=]* )
189 |
190 (?P<MethodName> [a-zA-Z_] [a-zA-Z0-9_?!=]* )
191 |
192 (?P<MethodName3> [^( \t]{1,3} )
193 )
194 [ \t]*
195 (?:
196 \( (?P<MethodSignature> (?: [^)] | \)[ \t]*,? )*? ) \)
197 )?
198 [ \t]*
199 )
200
201 | (?P<Class>
202 ^
203 (?P<ClassIndent> [ \t]* )
204 class
205 (?:
206 [ \t]+
207 (?P<ClassName> [A-Z] [a-zA-Z0-9_]* )
208 [ \t]*
209 (?P<ClassSupers> < [ \t]* [A-Z] [a-zA-Z0-9_]* )?
210 |
211 [ \t]* << [ \t]*
212 (?P<ClassName2> [a-zA-Z_] [a-zA-Z0-9_]* )
213 )
214 [ \t]*
215 )
216
217 | (?P<ClassIgnored>
218 \(
219 [ \t]*
220 class
221 .*?
222 end
223 [ \t]*
224 \)
225 )
226
227 | (?P<Module>
228 ^
229 (?P<ModuleIndent> [ \t]* )
230 module [ \t]+
231 (?P<ModuleName> [A-Z] [a-zA-Z0-9_]* )
232 [ \t]*
233 )
234
235 | (?P<AccessControl>
236 ^
237 (?P<AccessControlIndent> [ \t]* )
238 (?:
239 (?P<AccessControlType> private | public | protected ) [^_]
240 |
241 (?P<AccessControlType2> private_class_method | public_class_method )
242 )
243 \(?
244 [ \t]*
245 (?P<AccessControlList> (?: : [a-zA-Z0-9_]+ , \s* )* (?: : [a-zA-Z0-9_]+ )+ )?
246 [ \t]*
247 \)?
248 )
249
250 | (?P<Attribute>
251 ^
252 (?P<AttributeIndent> [ \t]* )
253 (?P<AttributeName> (?: @ | @@ | [A-Z]) [a-zA-Z0-9_]* )
254 [ \t]* =
255 )
256
257 | (?P<Attr>
258 ^
259 (?P<AttrIndent> [ \t]* )
260 attr
261 (?P<AttrType> (?: _accessor | _reader | _writer ) )?
262 \(?
263 [ \t]*
264 (?P<AttrList> (?: : [a-zA-Z0-9_]+ , \s* )* (?: : [a-zA-Z0-9_]+ | true | false )+ )
265 [ \t]*
266 \)?
267 )
268
269 | (?P<Begin>
270 ^
271 [ \t]*
272 (?: if | unless | case | while | until | for | begin ) \b [^_]
273 |
274 [ \t]* do [ \t]* (?: \| .*? \| )? [ \t]* $
275 )
276
277 | (?P<BeginEnd>
278 \b (?: if ) \b [^_] .*? $
279 |
280 \b (?: if ) \b [^_] .*? end [ \t]* $
281 )
282
283 | (?P<End>
284 [ \t]*
285 (?:
286 end [ \t]* $
287 |
288 end \b [^_]
289 )
290 )
291 """, re.VERBOSE | re.DOTALL | re.MULTILINE).search
292
293 _hashsub = re.compile(r"""^([ \t]*)#[ \t]?""", re.MULTILINE).sub
294
295 _commentsub = re.compile(r"""#[^\n]*\n|#[^\n]*$""").sub
296
297 _modules = {} # cache of modules we've seen
298
299 class VisibilityBase(object):
300 """
301 Class implementing the visibility aspect of all objects.
302 """
303 def isPrivate(self):
304 """
305 Public method to check, if the visibility is Private.
306
307 @return flag indicating Private visibility (boolean)
308 """
309 return self.visibility == 0
310
311 def isProtected(self):
312 """
313 Public method to check, if the visibility is Protected.
314
315 @return flag indicating Protected visibility (boolean)
316 """
317 return self.visibility == 1
318
319 def isPublic(self):
320 """
321 Public method to check, if the visibility is Public.
322
323 @return flag indicating Public visibility (boolean)
324 """
325 return self.visibility == 2
326
327 def setPrivate(self):
328 """
329 Public method to set the visibility to Private.
330 """
331 self.visibility = 0
332
333 def setProtected(self):
334 """
335 Public method to set the visibility to Protected.
336 """
337 self.visibility = 1
338
339 def setPublic(self):
340 """
341 Public method to set the visibility to Public.
342 """
343 self.visibility = 2
344
345 class Module(object):
346 '''
347 Class to represent a Python module.
348 '''
349 def __init__(self, name, file=None, type=None):
350 """
351 Constructor
352
353 @param name name of this module (string)
354 @param file filename of file containing this module (string)
355 @param type type of this module
356 """
357 self.name = name
358 self.file = file
359 self.modules = {}
360 self.modules_counts = {}
361 self.classes = {}
362 self.classes_counts = {}
363 self.functions = {}
364 self.functions_counts = {}
365 self.description = ""
366 self.globals = {}
367 self.imports = []
368 self.from_imports = {}
369 self.package = '.'.join(name.split('.')[:-1])
370 self.type = type
371 if type in [imp.PY_SOURCE, PTL_SOURCE]:
372 self._getnext = _py_getnext
373 elif type == RB_SOURCE:
374 self._getnext = _rb_getnext
375 else:
376 self._getnext = None
377
378 def addClass(self, name, _class):
379 """
380 Public method to add information about a class.
381
382 @param name name of class to be added (string)
383 @param _class Class object to be added
384 """
385 if self.classes.has_key(name):
386 self.classes_counts[name] += 1
387 name = "%s_%d" % (name, self.classes_counts[name])
388 else:
389 self.classes_counts[name] = 0
390 self.classes[name] = _class
391
392 def addModule(self, name, module):
393 """
394 Public method to add information about a Ruby module.
395
396 @param name name of module to be added (string)
397 @param module Module object to be added
398 """
399 if self.modules.has_key(name):
400 self.modules_counts[name] += 1
401 name = "%s_%d" % (name, self.modules_counts[name])
402 else:
403 self.modules_counts[name] = 0
404 self.modules[name] = module
405
406 def addFunction(self, name, function):
407 """
408 Public method to add information about a function.
409
410 @param name name of function to be added (string)
411 @param function Function object to be added
412 """
413 if self.functions.has_key(name):
414 self.functions_counts[name] += 1
415 name = "%s_%d" % (name, self.functions_counts[name])
416 else:
417 self.functions_counts[name] = 0
418 self.functions[name] = function
419
420 def addGlobal(self, name, attr):
421 """
422 Public method to add information about global variables.
423
424 @param name name of the global to add (string)
425 @param attr Attribute object to be added
426 """
427 if not name in self.globals:
428 self.globals[name] = attr
429
430 def addDescription(self, description):
431 """
432 Protected method to store the modules docstring.
433
434 @param description the docstring to be stored (string)
435 """
436 self.description = description
437
438 def scan(self, src):
439 """
440 Public method to scan the source text and retrieve the relevant information.
441
442 @param src the source text to be scanned (string)
443 """
444 if self.type in [imp.PY_SOURCE, PTL_SOURCE]:
445 self.__py_scan(src)
446 elif self.type == RB_SOURCE:
447 self.__rb_scan(src)
448
449 def __py_setVisibility(self, object):
450 """
451 Private method to set the visibility of an object.
452
453 @param object reference to the object (Attribute, Class or Function)
454 """
455 if object.name.startswith('__'):
456 object.setPrivate()
457 elif object.name.startswith('_'):
458 object.setProtected()
459 else:
460 object.setPublic()
461
462 def __py_scan(self, src):
463 """
464 Private method to scan the source text of a Python module and retrieve the
465 relevant information.
466
467 @param src the source text to be scanned (string)
468 """
469 lineno, last_lineno_pos = 1, 0
470 classstack = [] # stack of (class, indent) pairs
471 conditionalsstack = [] # stack of indents of conditional defines
472 deltastack = []
473 deltaindent = 0
474 deltaindentcalculated = 0
475 i = 0
476 modulelevel = 1
477 cur_obj = self
478 while True:
479 m = self._getnext(src, i)
480 if not m:
481 break
482 start, i = m.span()
483
484 if m.start("Method") >= 0:
485 # found a method definition or function
486 thisindent = _indent(m.group("MethodIndent"))
487 meth_name = m.group("MethodName")
488 meth_sig = m.group("MethodSignature")
489 meth_sig = meth_sig.replace('\\\n', '')
490 if m.group("MethodPyQtSignature") is not None:
491 meth_pyqtSig = m.group("MethodPyQtSignature")\
492 .replace('\\\n', '')\
493 .split('result')[0]\
494 .split('name')[0]\
495 .strip("\"', \t")
496 else:
497 meth_pyqtSig = None
498 lineno = lineno + src.count('\n', last_lineno_pos, start)
499 last_lineno_pos = start
500 # modify indentation level for conditional defines
501 if conditionalsstack:
502 if thisindent > conditionalsstack[-1]:
503 if not deltaindentcalculated:
504 deltastack.append(thisindent - conditionalsstack[-1])
505 deltaindent = reduce(lambda x,y: x+y, deltastack)
506 deltaindentcalculated = 1
507 thisindent -= deltaindent
508 else:
509 while conditionalsstack and \
510 conditionalsstack[-1] >= thisindent:
511 del conditionalsstack[-1]
512 if deltastack:
513 del deltastack[-1]
514 deltaindentcalculated = 0
515 # close all classes indented at least as much
516 while classstack and \
517 classstack[-1][1] >= thisindent:
518 if classstack[-1][1] == thisindent and \
519 classstack[-1][0] is not None and \
520 isinstance(classstack[-1][0], Class):
521 # we got a class at the same indentation level;
522 # record the end line of this class
523 classstack[-1][0].setEndLine(lineno - 1)
524 del classstack[-1]
525 if classstack:
526 csi = -1
527 while csi >= -len(classstack):
528 # nested defs are added to the class
529 cur_class = classstack[csi][0]
530 csi -= 1
531 if cur_class is None:
532 continue
533
534 if isinstance(cur_class, Class):
535 # it's a class method
536 f = Function(None, meth_name, None, lineno,
537 meth_sig, meth_pyqtSig)
538 self.__py_setVisibility(f)
539 cur_class.addMethod(meth_name, f)
540 break
541 else:
542 # it's a nested function of a module function
543 f = Function(self.name, meth_name, self.file, lineno,
544 meth_sig, meth_pyqtSig)
545 self.__py_setVisibility(f)
546 self.addFunction(meth_name, f)
547 else:
548 # it's a module function
549 f = Function(self.name, meth_name, self.file, lineno,
550 meth_sig, meth_pyqtSig)
551 self.__py_setVisibility(f)
552 self.addFunction(meth_name, f)
553 cur_obj = f
554 classstack.append((None, thisindent)) # Marker for nested fns
555
556 elif m.start("Docstring") >= 0:
557 contents = m.group("DocstringContents3")
558 if contents is not None:
559 contents = _hashsub(r"\1", contents)
560 else:
561 if self.file.lower().endswith('.ptl'):
562 contents = ""
563 else:
564 contents = 1 and m.group("DocstringContents1") \
565 or m.group("DocstringContents2")
566 if cur_obj:
567 cur_obj.addDescription(contents)
568
569 elif m.start("String") >= 0:
570 if modulelevel and \
571 (src[start-len('\r\n'):start] == '\r\n' or \
572 src[start-len('\n'):start] == '\n' or \
573 src[start-len('\r'):start] == '\r'):
574 contents = m.group("StringContents3")
575 if contents is not None:
576 contents = _hashsub(r"\1", contents)
577 else:
578 if self.file.lower().endswith('.ptl'):
579 contents = ""
580 else:
581 contents = 1 and m.group("StringContents1") \
582 or m.group("StringContents2")
583 if cur_obj:
584 cur_obj.addDescription(contents)
585
586 elif m.start("Class") >= 0:
587 # we found a class definition
588 thisindent = _indent(m.group("ClassIndent"))
589 lineno = lineno + src.count('\n', last_lineno_pos, start)
590 last_lineno_pos = start
591 # close all classes indented at least as much
592 while classstack and \
593 classstack[-1][1] >= thisindent:
594 if classstack[-1][1] == thisindent and \
595 classstack[-1][0] is not None and \
596 isinstance(classstack[-1][0], Class):
597 # we got a class at the same indentation level;
598 # record the end line of this class
599 classstack[-1][0].setEndLine(lineno - 1)
600 del classstack[-1]
601 class_name = m.group("ClassName")
602 inherit = m.group("ClassSupers")
603 if inherit:
604 # the class inherits from other classes
605 inherit = inherit[1:-1].strip()
606 inherit = _commentsub('', inherit)
607 names = []
608 for n in inherit.split(','):
609 n = n.strip()
610 if n:
611 if self.classes.has_key(n):
612 # we know this super class
613 n = self.classes[n].name
614 else:
615 c = n.split('.')
616 if len(c) > 1:
617 # super class is of the
618 # form module.class:
619 # look in module for class
620 m = c[-2]
621 c = c[-1]
622 if _modules.has_key(m):
623 m = _modules[m]
624 n = m.name
625 names.append(n)
626 inherit = names
627 # remember this class
628 cur_class = Class(self.name, class_name, inherit,
629 self.file, lineno)
630 self.__py_setVisibility(cur_class)
631 cur_obj = cur_class
632 # add nested classes to the module
633 self.addClass(class_name, cur_class)
634 classstack.append((cur_class, thisindent))
635
636 elif m.start("Attribute") >= 0:
637 lineno = lineno + src.count('\n', last_lineno_pos, start)
638 last_lineno_pos = start
639 index = -1
640 while index >= -len(classstack):
641 if classstack[index][0] is not None:
642 attrName = m.group("AttributeName")
643 attr = Attribute(self.name, attrName, self.file, lineno)
644 self.__py_setVisibility(attr)
645 classstack[index][0].addAttribute(attrName, attr)
646 break
647 else:
648 index -= 1
649
650 elif m.start("Variable") >= 0:
651 thisindent = _indent(m.group("VariableIndent"))
652 variable_name = m.group("VariableName")
653 lineno = lineno + src.count('\n', last_lineno_pos, start)
654 last_lineno_pos = start
655 if thisindent == 0:
656 # global variable
657 attr = Attribute(self.name, variable_name, self.file, lineno)
658 self.__py_setVisibility(attr)
659 self.addGlobal(variable_name, attr)
660 else:
661 index = -1
662 while index >= -len(classstack):
663 if classstack[index][1] >= thisindent:
664 index -= 1
665 else:
666 if classstack[index][0] is not None and \
667 isinstance(classstack[index][0], Class):
668 attr = Attribute(self.name, variable_name, self.file, lineno)
669 self.__py_setVisibility(attr)
670 classstack[index][0].addGlobal(variable_name, attr)
671 break
672
673 elif m.start("Import") >= 0:
674 # import module
675 for name in m.group("ImportList").split(','):
676 name = name.strip()
677 if not name in self.imports:
678 self.imports.append(name)
679
680 elif m.start("ImportFrom") >= 0:
681 # from module import stuff
682 mod = m.group("ImportFromPath")
683 names = m.group("ImportFromList").split(',')
684 if not self.from_imports.has_key(mod):
685 self.from_imports[mod] = []
686 for n in names:
687 n = n.strip()
688 self.from_imports[mod].append(n)
689
690 elif m.start("ConditionalDefine") >= 0:
691 # a conditional function/method definition
692 thisindent = _indent(m.group("ConditionalDefineIndent"))
693 while conditionalsstack and \
694 conditionalsstack[-1] >= thisindent:
695 del conditionalsstack[-1]
696 if deltastack:
697 del deltastack[-1]
698 conditionalsstack.append(thisindent)
699 deltaindentcalculated = 0
700
701 else:
702 assert 0, "regexp _getnext found something unexpected"
703
704 modulelevel = 0
705
706 def __rb_scan(self, src):
707 """
708 Private method to scan the source text of a Python module and retrieve the
709 relevant information.
710
711 @param src the source text to be scanned (string)
712 """
713 lineno, last_lineno_pos = 1, 0
714 classstack = [] # stack of (class, indent) pairs
715 acstack = [] # stack of (access control, indent) pairs
716 indent = 0
717 i = 0
718 cur_obj = self
719 while True:
720 m = self._getnext(src, i)
721 if not m:
722 break
723 start, i = m.span()
724
725 if m.start("Method") >= 0:
726 # found a method definition or function
727 thisindent = indent
728 indent += 1
729 meth_name = m.group("MethodName") or \
730 m.group("MethodName2") or \
731 m.group("MethodName3")
732 meth_sig = m.group("MethodSignature")
733 meth_sig = meth_sig and meth_sig.replace('\\\n', '') or ''
734 lineno = lineno + src.count('\n', last_lineno_pos, start)
735 last_lineno_pos = start
736 if meth_name.startswith('self.'):
737 meth_name = meth_name[5:]
738 elif meth_name.startswith('self::'):
739 meth_name = meth_name[6:]
740 # close all classes/modules indented at least as much
741 while classstack and \
742 classstack[-1][1] >= thisindent:
743 del classstack[-1]
744 while acstack and \
745 acstack[-1][1] >= thisindent:
746 del acstack[-1]
747 if classstack:
748 csi = -1
749 while csi >= -len(classstack):
750 # nested defs are added to the class
751 cur_class = classstack[csi][0]
752 csi -= 1
753 if cur_class is None:
754 continue
755
756 if isinstance(cur_class, Class) or \
757 isinstance(cur_class, RbModule):
758 # it's a class/module method
759 f = Function(None, meth_name,
760 None, lineno, meth_sig)
761 cur_class.addMethod(meth_name, f)
762 break
763 else:
764 # it's a nested function of a module function
765 f = Function(self.name, meth_name, self.file, lineno, meth_sig)
766 self.addFunction(meth_name, f)
767 # set access control
768 if acstack:
769 accesscontrol = acstack[-1][0]
770 if accesscontrol == "private":
771 f.setPrivate()
772 elif accesscontrol == "protected":
773 f.setProtected()
774 elif accesscontrol == "public":
775 f.setPublic()
776 else:
777 # it's a function
778 f = Function(self.name, meth_name, self.file, lineno, meth_sig)
779 self.addFunction(meth_name, f)
780 cur_obj = f
781 classstack.append((None, thisindent)) # Marker for nested fns
782
783 elif m.start("Docstring") >= 0:
784 contents = m.group("DocstringContents")
785 if contents is not None:
786 contents = _hashsub(r"\1", contents)
787 if cur_obj:
788 cur_obj.addDescription(contents)
789
790 elif m.start("String") >= 0:
791 pass
792
793 elif m.start("Comment") >= 0:
794 pass
795
796 elif m.start("ClassIgnored") >= 0:
797 pass
798
799 elif m.start("Class") >= 0:
800 # we found a class definition
801 thisindent = indent
802 indent += 1
803 # close all classes/modules indented at least as much
804 while classstack and \
805 classstack[-1][1] >= thisindent:
806 del classstack[-1]
807 lineno = lineno + src.count('\n', last_lineno_pos, start)
808 last_lineno_pos = start
809 class_name = m.group("ClassName") or m.group("ClassName2")
810 inherit = m.group("ClassSupers")
811 if inherit:
812 # the class inherits from other classes
813 inherit = inherit[1:].strip()
814 inherit = [_commentsub('', inherit)]
815 # remember this class
816 cur_class = Class(self.name, class_name, inherit,
817 self.file, lineno)
818 # add nested classes to the file
819 if classstack and isinstance(classstack[-1][0], RbModule):
820 parent_obj = classstack[-1][0]
821 else:
822 parent_obj = self
823 if parent_obj.classes.has_key(class_name):
824 cur_class = parent_obj.classes[class_name]
825 elif classstack and \
826 isinstance(classstack[-1][0], Class) and \
827 class_name == "self":
828 cur_class = classstack[-1][0]
829 else:
830 parent_obj.addClass(class_name, cur_class)
831 cur_obj = cur_class
832 classstack.append((cur_class, thisindent))
833 while acstack and \
834 acstack[-1][1] >= thisindent:
835 del acstack[-1]
836 acstack.append(["public", thisindent]) # default access control is 'public'
837
838 elif m.start("Module") >= 0:
839 # we found a module definition
840 thisindent = indent
841 indent += 1
842 # close all classes/modules indented at least as much
843 while classstack and \
844 classstack[-1][1] >= thisindent:
845 del classstack[-1]
846 lineno = lineno + src.count('\n', last_lineno_pos, start)
847 last_lineno_pos = start
848 module_name = m.group("ModuleName")
849 # remember this class
850 cur_class = RbModule(self.name, module_name,
851 self.file, lineno)
852 # add nested Ruby modules to the file
853 if self.modules.has_key(module_name):
854 cur_class = self.modules[module_name]
855 else:
856 self.addModule(module_name, cur_class)
857 cur_obj = cur_class
858 classstack.append((cur_class, thisindent))
859 while acstack and \
860 acstack[-1][1] >= thisindent:
861 del acstack[-1]
862 acstack.append(["public", thisindent]) # default access control is 'public'
863
864 elif m.start("AccessControl") >= 0:
865 aclist = m.group("AccessControlList")
866 if aclist is None:
867 index = -1
868 while index >= -len(acstack):
869 if acstack[index][1] < indent:
870 actype = m.group("AccessControlType") or \
871 m.group("AccessControlType2").split('_')[0]
872 acstack[index][0] = actype.lower()
873 break
874 else:
875 index -= 1
876 else:
877 index = -1
878 while index >= -len(classstack):
879 if classstack[index][0] is not None and \
880 not isinstance(classstack[index][0], Function) and \
881 not classstack[index][1] >= indent:
882 parent = classstack[index][0]
883 actype = m.group("AccessControlType") or \
884 m.group("AccessControlType2").split('_')[0]
885 actype = actype.lower()
886 for name in aclist.split(","):
887 name = name.strip()[1:] # get rid of leading ':'
888 acmeth = parent.getMethod(name)
889 if acmeth is None:
890 continue
891 if actype == "private":
892 acmeth.setPrivate()
893 elif actype == "protected":
894 acmeth.setProtected()
895 elif actype == "public":
896 acmeth.setPublic()
897 break
898 else:
899 index -= 1
900
901 elif m.start("Attribute") >= 0:
902 lineno = lineno + src.count('\n', last_lineno_pos, start)
903 last_lineno_pos = start
904 index = -1
905 while index >= -len(classstack):
906 if classstack[index][0] is not None and \
907 not isinstance(classstack[index][0], Function) and \
908 not classstack[index][1] >= indent:
909 attrName = m.group("AttributeName")
910 attr = Attribute(self.name, attrName, self.file, lineno)
911 if attrName.startswith("@@") or attrName[0].isupper():
912 classstack[index][0].addGlobal(attrName, attr)
913 else:
914 classstack[index][0].addAttribute(attrName, attr)
915 break
916 else:
917 index -= 1
918 else:
919 attrName = m.group("AttributeName")
920 if attrName[0] != "@":
921 attr = Attribute(self.name, attrName, self.file, lineno)
922 self.addGlobal(attrName, attr)
923
924 elif m.start("Attr") >= 0:
925 lineno = lineno + src.count('\n', last_lineno_pos, start)
926 last_lineno_pos = start
927 index = -1
928 while index >= -len(classstack):
929 if classstack[index][0] is not None and \
930 not isinstance(classstack[index][0], Function) and \
931 not classstack[index][1] >= indent:
932 parent = classstack[index][0]
933 if m.group("AttrType") is None:
934 nv = m.group("AttrList").split(",")
935 if not nv:
936 break
937 name = nv[0].strip()[1:] # get rid of leading ':'
938 attr = parent.getAttribute("@"+name) or \
939 parent.getAttribute("@@"+name) or \
940 Attribute(self.name, "@"+name, file, lineno)
941 if len(nv) == 1 or nv[1].strip() == "false":
942 attr.setProtected()
943 elif nv[1].strip() == "true":
944 attr.setPublic()
945 parent.addAttribute(attr.name, attr)
946 else:
947 access = m.group("AttrType")
948 for name in m.group("AttrList").split(","):
949 name = name.strip()[1:] # get rid of leading ':'
950 attr = parent.getAttribute("@"+name) or \
951 parent.getAttribute("@@"+name) or \
952 Attribute(self.name, "@"+name, file, lineno)
953 if access == "_accessor":
954 attr.setPublic()
955 elif access == "_reader" or access == "_writer":
956 if attr.isPrivate():
957 attr.setProtected()
958 elif attr.isProtected():
959 attr.setPublic()
960 parent.addAttribute(attr.name, attr)
961 break
962 else:
963 index -= 1
964
965 elif m.start("Begin") >= 0:
966 # a begin of a block we are not interested in
967 indent += 1
968
969 elif m.start("End") >= 0:
970 # an end of a block
971 indent -= 1
972 if indent < 0:
973 # no negative indent allowed
974 if classstack:
975 # it's a class/module method
976 indent = classstack[-1][1]
977 else:
978 indent = 0
979
980 elif m.start("BeginEnd") >= 0:
981 pass
982
983 else:
984 assert 0, "regexp _getnext found something unexpected"
985
986 def createHierarchy(self):
987 """
988 Public method to build the inheritance hierarchy for all classes of this module.
989
990 @return A dictionary with inheritance hierarchies.
991 """
992 hierarchy = {}
993 for cls in self.classes.keys():
994 self.assembleHierarchy(cls, self.classes, [cls], hierarchy)
995 for mod in self.modules.keys():
996 self.assembleHierarchy(mod, self.modules, [mod], hierarchy)
997 return hierarchy
998
999 def assembleHierarchy(self, name, classes, path, result):
1000 """
1001 Public method to assemble the inheritance hierarchy.
1002
1003 This method will traverse the class hierarchy, from a given class
1004 and build up a nested dictionary of super-classes. The result is
1005 intended to be inverted, i.e. the highest level are the super classes.
1006
1007 This code is borrowed from Boa Constructor.
1008
1009 @param name name of class to assemble hierarchy (string)
1010 @param classes A dictionary of classes to look in.
1011 @param path
1012 @param result The resultant hierarchy
1013 """
1014 rv = {}
1015 if classes.has_key(name):
1016 for cls in classes[name].super:
1017 if not classes.has_key(cls):
1018 rv[cls] = {}
1019 exhausted = path + [cls]
1020 exhausted.reverse()
1021 self.addPathToHierarchy(exhausted, result, self.addPathToHierarchy)
1022 else:
1023 rv[cls] = self.assembleHierarchy(cls,
1024 classes, path + [cls], result)
1025
1026 if len(rv) == 0:
1027 exhausted = path
1028 exhausted.reverse()
1029 self.addPathToHierarchy(exhausted, result, self.addPathToHierarchy)
1030
1031 def addPathToHierarchy(self, path, result, fn):
1032 """
1033 Public method to put the exhausted path into the result dictionary.
1034
1035 @param path the exhausted path of classes
1036 @param result the result dictionary
1037 @param fn function to call for classe that are already part of the
1038 result dictionary
1039 """
1040 if path[0] in result.keys():
1041 if len(path) > 1:
1042 fn(path[1:], result[path[0]], fn)
1043 else:
1044 for part in path:
1045 result[part] = {}
1046 result = result[part]
1047
1048 def getName(self):
1049 """
1050 Public method to retrieve the modules name.
1051
1052 @return module name (string)
1053 """
1054 return self.name
1055
1056 def getFileName(self):
1057 """
1058 Public method to retrieve the modules filename.
1059
1060 @return module filename (string)
1061 """
1062 return self.file
1063
1064 def getType(self):
1065 """
1066 Public method to get the type of the module's source.
1067
1068 @return type of the modules's source (string)
1069 """
1070 if self.type in [imp.PY_SOURCE, PTL_SOURCE]:
1071 type = "Python"
1072 elif self.type == RB_SOURCE:
1073 type = "Ruby"
1074 else:
1075 type = ""
1076 return type
1077
1078 class Class(VisibilityBase):
1079 '''
1080 Class to represent a Python class.
1081 '''
1082 def __init__(self, module, name, super, file, lineno):
1083 """
1084 Constructor
1085
1086 @param module name of module containing this class (string)
1087 @param name name of the class (string)
1088 @param super list of classnames this class is inherited from
1089 (list of strings)
1090 @param file name of file containing this class (string)
1091 @param lineno linenumber of the class definition (integer)
1092 """
1093 self.module = module
1094 self.name = name
1095 if super is None:
1096 super = []
1097 self.super = super
1098 self.methods = {}
1099 self.attributes = {}
1100 self.globals = {}
1101 self.file = file
1102 self.lineno = lineno
1103 self.endlineno = -1 # marker for "not set"
1104 self.description = ""
1105 self.setPublic()
1106
1107 def addMethod(self, name, function):
1108 """
1109 Public method to add information about a method.
1110
1111 @param name name of method to be added (string)
1112 @param function Function object to be added
1113 """
1114 self.methods[name] = function
1115
1116 def getMethod(self, name):
1117 """
1118 Public method to retrieve a method by name.
1119
1120 @param name name of the method (string)
1121 @return the named method or None
1122 """
1123 try:
1124 return self.methods[name]
1125 except KeyError:
1126 return None
1127
1128 def addAttribute(self, name, attr):
1129 """
1130 Public method to add information about attributes.
1131
1132 @param name name of the attribute to add (string)
1133 @param attr Attribute object to be added
1134 """
1135 if not name in self.attributes:
1136 self.attributes[name] = attr
1137
1138 def getAttribute(self, name):
1139 """
1140 Public method to retrieve an attribute by name.
1141
1142 @param name name of the attribute (string)
1143 @return the named attribute or None
1144 """
1145 try:
1146 return self.attributes[name]
1147 except KeyError:
1148 return None
1149
1150 def addGlobal(self, name, attr):
1151 """
1152 Public method to add information about global (class) variables.
1153
1154 @param name name of the global to add (string)
1155 @param attr Attribute object to be added
1156 """
1157 if not name in self.globals:
1158 self.globals[name] = attr
1159
1160 def addDescription(self, description):
1161 """
1162 Public method to store the class docstring.
1163
1164 @param description the docstring to be stored (string)
1165 """
1166 self.description = description
1167
1168 def setEndLine(self, endLineNo):
1169 """
1170 Public method to record the number of the last line of a class.
1171
1172 @param endLineNo number of the last line (integer)
1173 """
1174 self.endlineno = endLineNo
1175
1176 class RbModule(Class):
1177 '''
1178 Class to represent a Ruby module.
1179 '''
1180 def __init__(self, module, name, file, lineno):
1181 """
1182 Constructor
1183
1184 @param module name of module containing this class (string)
1185 @param name name of the class (string)
1186 @param file name of file containing this class (string)
1187 @param lineno linenumber of the class definition (integer)
1188 """
1189 Class.__init__(self, module, name, None, file, lineno)
1190 self.classes = {}
1191
1192 def addClass(self, name, _class):
1193 """
1194 Public method to add information about a class.
1195
1196 @param name name of class to be added (string)
1197 @param _class Class object to be added
1198 """
1199 self.classes[name] = _class
1200
1201 class Function(VisibilityBase):
1202 '''
1203 Class to represent a Python function or method.
1204 '''
1205 def __init__(self, module, name, file, lineno, signature = '', pyqtSignature = None):
1206 """
1207 Constructor
1208
1209 @param module name of module containing this function (string)
1210 @param name name of the function (string)
1211 @param file name of file containing this function (string)
1212 @param lineno linenumber of the function definition (integer)
1213 @param signature the functions call signature (string)
1214 @param pyqtSignature the functions PyQt signature (string)
1215 """
1216 self.module = module
1217 self.name = name
1218 self.file = file
1219 self.lineno = lineno
1220 signature = _commentsub('', signature)
1221 self.parameters = [e.strip() for e in signature.split(',')]
1222 self.description = ""
1223 self.pyqtSignature = pyqtSignature
1224 self.setPublic()
1225
1226 def addDescription(self, description):
1227 """
1228 Public method to store the functions docstring.
1229
1230 @param description the docstring to be stored (string)
1231 """
1232 self.description = description
1233
1234 class Attribute(VisibilityBase):
1235 '''
1236 Class to represent a Python function or method.
1237 '''
1238 def __init__(self, module, name, file, lineno):
1239 """
1240 Constructor
1241
1242 @param module name of module containing this function (string)
1243 @param name name of the function (string)
1244 @param file name of file containing this function (string)
1245 @param lineno linenumber of the function definition (integer)
1246 """
1247 self.module = module
1248 self.name = name
1249 self.file = file
1250 self.lineno = lineno
1251 self.setPublic()
1252
1253 def readModule(module, path = [], inpackage = False, basename = "",
1254 extensions = None, caching = True):
1255 '''
1256 Function to read a module file and parse it.
1257
1258 The module is searched in path and sys.path, read and parsed.
1259 If the module was parsed before, the information is taken
1260 from a cache in order to speed up processing.
1261
1262 @param module Name of the module to be parsed (string)
1263 @param path Searchpath for the module (list of strings)
1264 @param inpackage Flag indicating that module is inside a
1265 package (boolean)
1266 @param basename a path basename. This basename is deleted from
1267 the filename of the module file to be read. (string)
1268 @param extensions list of extensions, which should be considered valid
1269 source file extensions (list of strings)
1270 @param caching flag indicating that the parsed module should be
1271 cached (boolean)
1272 @return reference to a Module object containing the parsed
1273 module information (Module)
1274 '''
1275 if extensions is None:
1276 _extensions = ['.py', '.pyw', '.ptl', '.rb']
1277 else:
1278 _extensions = extensions[:]
1279 try:
1280 _extensions.remove('.py')
1281 except ValueError:
1282 pass
1283
1284 modname = module
1285
1286 if os.path.exists(module):
1287 path = [os.path.dirname(module)]
1288 if module.lower().endswith(".py"):
1289 module = module[:-3]
1290 if os.path.exists(os.path.join(path[0], "__init__.py")) or \
1291 os.path.exists(os.path.join(path[0], "__init__.rb")) or \
1292 inpackage:
1293 if basename:
1294 module = module.replace(basename, "")
1295 if os.path.isabs(module):
1296 modname = os.path.splitdrive(module)[1][len(os.sep):]
1297 else:
1298 modname = module
1299 modname = modname.replace(os.sep, '.')
1300 inpackage = 1
1301 else:
1302 modname = os.path.basename(module)
1303 for ext in _extensions:
1304 if modname.lower().endswith(ext):
1305 modname = modname[:-len(ext)]
1306 break
1307 module = os.path.basename(module)
1308
1309 if caching and _modules.has_key(modname):
1310 # we've seen this module before...
1311 return _modules[modname]
1312
1313 if module in sys.builtin_module_names:
1314 # this is a built-in module
1315 mod = Module(modname, None, None)
1316 if caching:
1317 _modules[modname] = mod
1318 return mod
1319
1320 # search the path for the module
1321 f = None
1322 if inpackage:
1323 try:
1324 f, file, (suff, mode, type) = find_module(module, path, _extensions)
1325 except ImportError:
1326 f = None
1327 if f is None:
1328 fullpath = list(path) + sys.path
1329 f, file, (suff, mode, type) = find_module(module, fullpath, _extensions)
1330 if type not in SUPPORTED_TYPES:
1331 # not supported source, can't do anything with this module
1332 if f:
1333 f.close()
1334 _modules[modname] = Module(modname, None, None)
1335 return _modules[modname]
1336
1337 mod = Module(modname, file, type)
1338 try:
1339 src = Utilities.decode(f.read())[0]
1340 mod.scan(src)
1341 finally:
1342 f.close()
1343 if caching:
1344 _modules[modname] = mod
1345 return mod
1346
1347 def _indent(ws):
1348 """
1349 Protected function to determine the indent width of a whitespace string.
1350
1351 @param ws The whitespace string to be cheked. (string)
1352 @return Length of the whitespace string after tab expansion.
1353 """
1354 return len(ws.expandtabs(TABWIDTH))
1355
1356 def find_module(name, path, extensions):
1357 """
1358 Module function to extend the Python module finding mechanism.
1359
1360 This function searches for files in the given path. If the filename
1361 doesn't have an extension or an extension of .py, the normal search
1362 implemented in the imp module is used. For all other supported files
1363 only path is searched.
1364
1365 @param name filename or modulename to search for (string)
1366 @param path search path (list of strings)
1367 @param extensions list of extensions, which should be considered valid
1368 source file extensions (list of strings)
1369 @return tuple of the open file, pathname and description. Description
1370 is a tuple of file suffix, file mode and file type)
1371 @exception ImportError The file or module wasn't found.
1372 """
1373 for ext in extensions:
1374 if name.lower().endswith(ext):
1375 for p in path: # only search in path
1376 if os.path.exists(os.path.join(p, name)):
1377 pathname = os.path.join(p, name)
1378 if ext == '.ptl':
1379 # Quixote page template
1380 return (open(pathname), pathname, ('.ptl', 'r', PTL_SOURCE))
1381 elif ext == '.rb':
1382 # Ruby source file
1383 return (open(pathname), pathname, ('.rb', 'r', RB_SOURCE))
1384 else:
1385 return (open(pathname), pathname, (ext, 'r', imp.PY_SOURCE))
1386 raise ImportError
1387
1388 # standard Python module file
1389 if name.lower().endswith('.py'):
1390 name = name[:-3]
1391 if type(name) == type(u""):
1392 name = name.encode('utf-8')
1393
1394 return imp.find_module(name, path)
1395
1396 def resetParsedModules():
1397 """
1398 Module function to reset the list of modules already parsed.
1399 """
1400 _modules.clear()
1401
1402 def resetParsedModule(module, basename = ""):
1403 """
1404 Module function to clear one module from the list of parsed modules.
1405
1406 @param module Name of the module to be parsed (string)
1407 @param basename a path basename. This basename is deleted from
1408 the filename of the module file to be cleared. (string)
1409 """
1410 modname = module
1411
1412 if os.path.exists(module):
1413 path = [os.path.dirname(module)]
1414 if module.lower().endswith(".py"):
1415 module = module[:-3]
1416 if os.path.exists(os.path.join(path[0], "__init__.py")):
1417 if basename:
1418 module = module.replace(basename, "")
1419 modname = module.replace(os.sep, '.')
1420 else:
1421 modname = os.path.basename(module)
1422 if modname.lower().endswith(".ptl") or modname.lower().endswith(".pyw"):
1423 modname = modname[:-4]
1424 elif modname.lower().endswith(".rb"):
1425 modname = modname[:-3]
1426 module = os.path.basename(module)
1427
1428 if _modules.has_key(modname):
1429 del _modules[modname]
1430
1431 if __name__ == "__main__":
1432 # Main program for testing.
1433 mod = sys.argv[1]
1434 module = readModule(mod)
1435 for cls in module.classes.values():
1436 print "--------------"
1437 print cls.name
1438 for meth in cls.methods.values():
1439 print meth.name, meth.pyqtSignature

eric ide

mercurial