eric7/Utilities/ModuleParser.py

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

eric ide

mercurial