src/eric7/Utilities/ModuleParser.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2022 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() and not line.lstrip().startswith("#"):
547 # line contains some text and does not start with
548 # a comment sign
549 lineIndent = _indent(line.replace(line.lstrip(), ""))
550 if lineIndent <= indent:
551 return lineno
552 lineno += 1
553
554 # nothing found
555 return -1
556
557 srcLines = src.splitlines()
558
559 lineno, last_lineno_pos = 1, 0
560 classstack = [] # stack of (class, indent) pairs
561 conditionalsstack = [] # stack of indents of conditional defines
562 deltastack = []
563 deltaindent = 0
564 deltaindentcalculated = 0
565 i = 0
566 modulelevel = True
567 cur_obj = self
568 modifierType = Function.General
569 modifierIndent = -1
570 while True:
571 m = self._getnext(src, i)
572 if not m:
573 break
574 start, i = m.span()
575
576 if m.start("MethodModifier") >= 0:
577 modifierIndent = _indent(m.group("MethodModifierIndent"))
578 modifierType = m.group("MethodModifierType")
579
580 elif m.start("Method") >= 0:
581 # found a method definition or function
582 thisindent = _indent(m.group("MethodIndent"))
583 meth_name = m.group("MethodName")
584 meth_sig = m.group("MethodSignature")
585 meth_sig = meth_sig.replace('\\\n', '')
586 meth_ret = m.group("MethodReturnAnnotation")
587 meth_ret = meth_ret.replace('\\\n', '')
588 if m.group("MethodPyQtSignature") is not None:
589 meth_pyqtSig = (
590 m.group("MethodPyQtSignature")
591 .replace('\\\n', '')
592 .split('result')[0]
593 .split('name')[0]
594 .strip("\"', \t")
595 )
596 else:
597 meth_pyqtSig = None
598 lineno += src.count('\n', last_lineno_pos, start)
599 last_lineno_pos = start
600 if modifierType and modifierIndent == thisindent:
601 if modifierType == "@staticmethod":
602 modifier = Function.Static
603 elif modifierType == "@classmethod":
604 modifier = Function.Class
605 else:
606 modifier = Function.General
607 else:
608 modifier = Function.General
609 # modify indentation level for conditional defines
610 if conditionalsstack:
611 if thisindent > conditionalsstack[-1]:
612 if not deltaindentcalculated:
613 deltastack.append(
614 thisindent - conditionalsstack[-1])
615 deltaindent = reduce(
616 lambda x, y: x + y, deltastack)
617 deltaindentcalculated = 1
618 thisindent -= deltaindent
619 else:
620 while (
621 conditionalsstack and
622 conditionalsstack[-1] >= thisindent
623 ):
624 del conditionalsstack[-1]
625 if deltastack:
626 del deltastack[-1]
627 deltaindentcalculated = 0
628 # close all classes indented at least as much
629 while classstack and classstack[-1][1] >= thisindent:
630 del classstack[-1]
631 if classstack:
632 csi = -1
633 while csi >= -len(classstack):
634 # nested defs are added to the class
635 cur_class = classstack[csi][0]
636 csi -= 1
637 if cur_class is None:
638 continue
639
640 if isinstance(cur_class, Class):
641 # it's a class method
642 f = Function(
643 None, meth_name, None, lineno,
644 meth_sig, meth_pyqtSig, modifierType=modifier,
645 annotation=meth_ret)
646 self.__py_setVisibility(f)
647 cur_class.addMethod(meth_name, f)
648 break
649 else:
650 # it's a nested function of a module function
651 f = Function(
652 self.name, meth_name, self.file, lineno,
653 meth_sig, meth_pyqtSig, modifierType=modifier,
654 annotation=meth_ret)
655 self.__py_setVisibility(f)
656 self.addFunction(meth_name, f)
657 else:
658 # it's a module function
659 f = Function(self.name, meth_name, self.file, lineno,
660 meth_sig, meth_pyqtSig, modifierType=modifier,
661 annotation=meth_ret)
662 self.__py_setVisibility(f)
663 self.addFunction(meth_name, f)
664 endlineno = calculateEndline(lineno, srcLines, thisindent)
665 f.setEndLine(endlineno)
666 cur_obj = f
667 classstack.append((None, thisindent)) # Marker for nested fns
668
669 # reset the modifier settings
670 modifierType = Function.General
671 modifierIndent = -1
672
673 elif m.start("Docstring") >= 0:
674 contents = m.group("DocstringContents3")
675 if contents is not None:
676 contents = _hashsub(r"\1", contents)
677 else:
678 if self.file.lower().endswith('.ptl'):
679 contents = ""
680 else:
681 contents = (
682 m.group("DocstringContents1") or
683 m.group("DocstringContents2")
684 )
685 if cur_obj:
686 cur_obj.addDescription(contents)
687
688 elif m.start("String") >= 0:
689 if (
690 modulelevel and (
691 src[start - len('\r\n'):start] == '\r\n' or
692 src[start - len('\n'):start] == '\n' or
693 src[start - len('\r'):start] == '\r'
694 )
695 ):
696 contents = m.group("StringContents3")
697 if contents is not None:
698 contents = _hashsub(r"\1", contents)
699 else:
700 if self.file.lower().endswith('.ptl'):
701 contents = ""
702 else:
703 contents = (
704 m.group("StringContents1") or
705 m.group("StringContents2")
706 )
707 if cur_obj:
708 cur_obj.addDescription(contents)
709
710 elif m.start("Class") >= 0:
711 # we found a class definition
712 thisindent = _indent(m.group("ClassIndent"))
713 lineno += src.count('\n', last_lineno_pos, start)
714 last_lineno_pos = start
715 # close all classes indented at least as much
716 while classstack and classstack[-1][1] >= thisindent:
717 del classstack[-1]
718 class_name = m.group("ClassName")
719 inherit = m.group("ClassSupers")
720 if inherit:
721 # the class inherits from other classes
722 inherit = inherit[1:-1].strip()
723 inherit = _commentsub('', inherit)
724 names = []
725 for n in inherit.split(','):
726 n = n.strip()
727 if n:
728 if n in self.classes:
729 # we know this super class
730 n = self.classes[n].name
731 else:
732 c = n.split('.')
733 if len(c) > 1:
734 # super class is of the
735 # form module.class:
736 # look in module for class
737 m = c[-2]
738 c = c[-1]
739 if m in _modules:
740 m = _modules[m]
741 n = m.name
742 names.append(n)
743 inherit = names
744 # modify indentation level for conditional defines
745 if conditionalsstack:
746 if thisindent > conditionalsstack[-1]:
747 if not deltaindentcalculated:
748 deltastack.append(
749 thisindent - conditionalsstack[-1]
750 )
751 deltaindent = reduce(
752 lambda x, y: x + y, deltastack
753 )
754 deltaindentcalculated = True
755 thisindent -= deltaindent
756 else:
757 while (
758 conditionalsstack and
759 conditionalsstack[-1] >= thisindent
760 ):
761 del conditionalsstack[-1]
762 if deltastack:
763 del deltastack[-1]
764 deltaindentcalculated = False
765 # remember this class
766 cur_class = Class(self.name, class_name, inherit,
767 self.file, lineno)
768 self.__py_setVisibility(cur_class)
769 endlineno = calculateEndline(lineno, srcLines, thisindent)
770 cur_class.setEndLine(endlineno)
771 cur_obj = cur_class
772 self.addClass(class_name, cur_class)
773 # add nested classes to the module
774 classstack.append((cur_class, thisindent))
775
776 elif m.start("Attribute") >= 0:
777 lineno += src.count('\n', last_lineno_pos, start)
778 last_lineno_pos = start
779 index = -1
780 while index >= -len(classstack):
781 if classstack[index][0] is not None:
782 attrName = m.group("AttributeName")
783 attr = Attribute(
784 self.name, attrName, self.file, lineno)
785 self.__py_setVisibility(attr)
786 classstack[index][0].addAttribute(attrName, attr)
787 break
788 else:
789 index -= 1
790
791 elif m.start("Main") >= 0:
792 # 'main' part of the script, reset class stack
793 lineno += src.count('\n', last_lineno_pos, start)
794 last_lineno_pos = start
795 classstack = []
796
797 elif m.start("Variable") >= 0:
798 thisindent = _indent(m.group("VariableIndent"))
799 variable_name = m.group("VariableName")
800 isSignal = m.group("VariableSignal") != ""
801 lineno += src.count('\n', last_lineno_pos, start)
802 last_lineno_pos = start
803 if thisindent == 0:
804 # global variable
805 attr = Attribute(
806 self.name, variable_name, self.file, lineno,
807 isSignal=isSignal)
808 self.__py_setVisibility(attr)
809 self.addGlobal(variable_name, attr)
810 else:
811 index = -1
812 while index >= -len(classstack):
813 if classstack[index][1] >= thisindent:
814 index -= 1
815 else:
816 if (
817 classstack[index][0] is not None and
818 isinstance(classstack[index][0], Class)
819 ):
820 attr = Attribute(
821 self.name, variable_name, self.file,
822 lineno, isSignal=isSignal)
823 self.__py_setVisibility(attr)
824 classstack[index][0].addGlobal(
825 variable_name, attr)
826 break
827
828 elif m.start("Import") >= 0:
829 #- import module
830 names = [n.strip() for n in
831 "".join(m.group("ImportList").splitlines())
832 .replace("\\", "").split(',')]
833 self.imports.extend(
834 [name for name in names
835 if name not in self.imports])
836
837 elif m.start("ImportFrom") >= 0:
838 #- from module import stuff
839 mod = m.group("ImportFromPath")
840 namesLines = (m.group("ImportFromList")
841 .replace("(", "").replace(")", "")
842 .replace("\\", "")
843 .strip().splitlines())
844 namesLines = [line.split("#")[0].strip()
845 for line in namesLines]
846 names = [n.strip() for n in
847 "".join(namesLines)
848 .split(',')]
849 if mod not in self.from_imports:
850 self.from_imports[mod] = []
851 self.from_imports[mod].extend(
852 [name for name in names
853 if name not in self.from_imports[mod]])
854
855 elif m.start("ConditionalDefine") >= 0:
856 # a conditional function/method definition
857 thisindent = _indent(m.group("ConditionalDefineIndent"))
858 while (
859 conditionalsstack and
860 conditionalsstack[-1] >= thisindent
861 ):
862 del conditionalsstack[-1]
863 if deltastack:
864 del deltastack[-1]
865 conditionalsstack.append(thisindent)
866 deltaindentcalculated = 0
867
868 elif m.start("Comment") >= 0 and modulelevel:
869 continue
870
871 modulelevel = False
872
873 def __rb_scan(self, src):
874 """
875 Private method to scan the source text of a Python module and retrieve
876 the relevant information.
877
878 @param src the source text to be scanned
879 @type str
880 """
881 lineno, last_lineno_pos = 1, 0
882 classstack = [] # stack of (class, indent) pairs
883 acstack = [] # stack of (access control, indent) pairs
884 indent = 0
885 i = 0
886 cur_obj = self
887 lastGlobalEntry = None
888 while True:
889 m = self._getnext(src, i)
890 if not m:
891 break
892 start, i = m.span()
893
894 if m.start("Method") >= 0:
895 # found a method definition or function
896 thisindent = indent
897 indent += 1
898 meth_name = (
899 m.group("MethodName") or
900 m.group("MethodName2") or
901 m.group("MethodName3")
902 )
903 meth_sig = m.group("MethodSignature")
904 meth_sig = meth_sig and meth_sig.replace('\\\n', '') or ''
905 lineno += src.count('\n', last_lineno_pos, start)
906 last_lineno_pos = start
907 if meth_name.startswith('self.'):
908 meth_name = meth_name[5:]
909 elif meth_name.startswith('self::'):
910 meth_name = meth_name[6:]
911 # close all classes/modules indented at least as much
912 while classstack and classstack[-1][1] >= thisindent:
913 if (
914 classstack[-1][0] is not None and
915 isinstance(classstack[-1][0],
916 (Class, Function, RbModule))
917 ):
918 # record the end line of this class, function or module
919 classstack[-1][0].setEndLine(lineno - 1)
920 del classstack[-1]
921 while acstack and acstack[-1][1] >= thisindent:
922 del acstack[-1]
923 if classstack:
924 csi = -1
925 while csi >= -len(classstack):
926 # nested defs are added to the class
927 cur_class = classstack[csi][0]
928 csi -= 1
929 if cur_class is None:
930 continue
931
932 if isinstance(cur_class, (Class, RbModule)):
933 # it's a class/module method
934 f = Function(None, meth_name,
935 None, lineno, meth_sig)
936 cur_class.addMethod(meth_name, f)
937 break
938 else:
939 # it's a nested function of a module function
940 f = Function(
941 self.name, meth_name, self.file, lineno, meth_sig)
942 self.addFunction(meth_name, f)
943 # set access control
944 if acstack:
945 accesscontrol = acstack[-1][0]
946 if accesscontrol == "private":
947 f.setPrivate()
948 elif accesscontrol == "protected":
949 f.setProtected()
950 elif accesscontrol == "public":
951 f.setPublic()
952 else:
953 # it's a function
954 f = Function(
955 self.name, meth_name, self.file, lineno, meth_sig)
956 self.addFunction(meth_name, f)
957 if not classstack:
958 if lastGlobalEntry:
959 lastGlobalEntry.setEndLine(lineno - 1)
960 lastGlobalEntry = f
961 if cur_obj and isinstance(cur_obj, Function):
962 cur_obj.setEndLine(lineno - 1)
963 cur_obj = f
964 classstack.append((None, thisindent)) # Marker for nested fns
965
966 elif m.start("Docstring") >= 0:
967 contents = m.group("DocstringContents")
968 if contents is not None:
969 contents = _hashsub(r"\1", contents)
970 if cur_obj:
971 cur_obj.addDescription(contents)
972
973 elif m.start("Class") >= 0:
974 # we found a class definition
975 thisindent = indent
976 indent += 1
977 lineno += src.count('\n', last_lineno_pos, start)
978 last_lineno_pos = start
979 # close all classes/modules indented at least as much
980 while classstack and classstack[-1][1] >= thisindent:
981 if (
982 classstack[-1][0] is not None and
983 isinstance(classstack[-1][0],
984 (Class, Function, RbModule))
985 ):
986 # record the end line of this class, function or module
987 classstack[-1][0].setEndLine(lineno - 1)
988 del classstack[-1]
989 class_name = m.group("ClassName") or m.group("ClassName2")
990 inherit = m.group("ClassSupers")
991 if inherit:
992 # the class inherits from other classes
993 inherit = inherit[1:].strip()
994 inherit = [_commentsub('', inherit)]
995 # remember this class
996 cur_class = Class(self.name, class_name, inherit,
997 self.file, lineno)
998 # add nested classes to the file
999 if classstack and isinstance(classstack[-1][0], RbModule):
1000 parent_obj = classstack[-1][0]
1001 else:
1002 parent_obj = self
1003 if class_name in parent_obj.classes:
1004 cur_class = parent_obj.classes[class_name]
1005 elif (
1006 classstack and
1007 isinstance(classstack[-1][0], Class) and
1008 class_name == "self"
1009 ):
1010 cur_class = classstack[-1][0]
1011 else:
1012 parent_obj.addClass(class_name, cur_class)
1013 if not classstack:
1014 if lastGlobalEntry:
1015 lastGlobalEntry.setEndLine(lineno - 1)
1016 lastGlobalEntry = cur_class
1017 cur_obj = cur_class
1018 classstack.append((cur_class, thisindent))
1019 while acstack and acstack[-1][1] >= thisindent:
1020 del acstack[-1]
1021 acstack.append(["public", thisindent])
1022 # default access control is 'public'
1023
1024 elif m.start("Module") >= 0:
1025 # we found a module definition
1026 thisindent = indent
1027 indent += 1
1028 lineno += src.count('\n', last_lineno_pos, start)
1029 last_lineno_pos = start
1030 # close all classes/modules indented at least as much
1031 while classstack and classstack[-1][1] >= thisindent:
1032 if (
1033 classstack[-1][0] is not None and
1034 isinstance(classstack[-1][0],
1035 (Class, Function, RbModule))
1036 ):
1037 # record the end line of this class, function or module
1038 classstack[-1][0].setEndLine(lineno - 1)
1039 del classstack[-1]
1040 module_name = m.group("ModuleName")
1041 # remember this class
1042 cur_class = RbModule(self.name, module_name,
1043 self.file, lineno)
1044 # add nested Ruby modules to the file
1045 if module_name in self.modules:
1046 cur_class = self.modules[module_name]
1047 else:
1048 self.addModule(module_name, cur_class)
1049 if not classstack:
1050 if lastGlobalEntry:
1051 lastGlobalEntry.setEndLine(lineno - 1)
1052 lastGlobalEntry = cur_class
1053 cur_obj = cur_class
1054 classstack.append((cur_class, thisindent))
1055 while acstack and acstack[-1][1] >= thisindent:
1056 del acstack[-1]
1057 acstack.append(["public", thisindent])
1058 # default access control is 'public'
1059
1060 elif m.start("AccessControl") >= 0:
1061 aclist = m.group("AccessControlList")
1062 if aclist is None:
1063 index = -1
1064 while index >= -len(acstack):
1065 if acstack[index][1] < indent:
1066 actype = (
1067 m.group("AccessControlType") or
1068 m.group("AccessControlType2").split('_')[0]
1069 )
1070 acstack[index][0] = actype.lower()
1071 break
1072 else:
1073 index -= 1
1074 else:
1075 index = -1
1076 while index >= -len(classstack):
1077 if (
1078 classstack[index][0] is not None and
1079 not isinstance(classstack[index][0], Function) and
1080 classstack[index][1] < indent
1081 ):
1082 parent = classstack[index][0]
1083 actype = (
1084 m.group("AccessControlType") or
1085 m.group("AccessControlType2").split('_')[0]
1086 )
1087 actype = actype.lower()
1088 for name in aclist.split(","):
1089 # get rid of leading ':'
1090 name = name.strip()[1:]
1091 acmeth = parent.getMethod(name)
1092 if acmeth is None:
1093 continue
1094 if actype == "private":
1095 acmeth.setPrivate()
1096 elif actype == "protected":
1097 acmeth.setProtected()
1098 elif actype == "public":
1099 acmeth.setPublic()
1100 break
1101 else:
1102 index -= 1
1103
1104 elif m.start("Attribute") >= 0:
1105 lineno += src.count('\n', last_lineno_pos, start)
1106 last_lineno_pos = start
1107 index = -1
1108 while index >= -len(classstack):
1109 if (
1110 classstack[index][0] is not None and
1111 not isinstance(classstack[index][0], Function) and
1112 classstack[index][1] < indent
1113 ):
1114 attrName = m.group("AttributeName")
1115 attr = Attribute(
1116 self.name, attrName, self.file, lineno)
1117 if attrName.startswith("@@") or attrName[0].isupper():
1118 classstack[index][0].addGlobal(attrName, attr)
1119 else:
1120 classstack[index][0].addAttribute(attrName, attr)
1121 break
1122 else:
1123 index -= 1
1124 else:
1125 attrName = m.group("AttributeName")
1126 if attrName[0] != "@":
1127 attr = Attribute(
1128 self.name, attrName, self.file, lineno)
1129 self.addGlobal(attrName, attr)
1130 if lastGlobalEntry:
1131 lastGlobalEntry.setEndLine(lineno - 1)
1132 lastGlobalEntry = None
1133
1134 elif m.start("Attr") >= 0:
1135 lineno += src.count('\n', last_lineno_pos, start)
1136 last_lineno_pos = start
1137 index = -1
1138 while index >= -len(classstack):
1139 if (
1140 classstack[index][0] is not None and
1141 not isinstance(classstack[index][0], Function) and
1142 classstack[index][1] < indent
1143 ):
1144 parent = classstack[index][0]
1145 if m.group("AttrType") is None:
1146 nv = m.group("AttrList").split(",")
1147 if not nv:
1148 break
1149 # get rid of leading ':'
1150 name = nv[0].strip()[1:]
1151 attr = (
1152 parent.getAttribute("@" + name) or
1153 parent.getAttribute("@@" + name) or
1154 Attribute(
1155 self.name, "@" + name, self.file, lineno)
1156 )
1157 if len(nv) == 1 or nv[1].strip() == "false":
1158 attr.setProtected()
1159 elif nv[1].strip() == "true":
1160 attr.setPublic()
1161 parent.addAttribute(attr.name, attr)
1162 else:
1163 access = m.group("AttrType")
1164 for name in m.group("AttrList").split(","):
1165 # get rid of leading ':'
1166 name = name.strip()[1:]
1167 attr = (
1168 parent.getAttribute("@" + name) or
1169 parent.getAttribute("@@" + name) or
1170 Attribute(
1171 self.name, "@" + name, self.file,
1172 lineno)
1173 )
1174 if access == "_accessor":
1175 attr.setPublic()
1176 elif access in ("_reader", "_writer"):
1177 if attr.isPrivate():
1178 attr.setProtected()
1179 elif attr.isProtected():
1180 attr.setPublic()
1181 parent.addAttribute(attr.name, attr)
1182 break
1183 else:
1184 index -= 1
1185
1186 elif m.start("Begin") >= 0:
1187 # a begin of a block we are not interested in
1188 indent += 1
1189
1190 elif m.start("End") >= 0:
1191 # an end of a block
1192 indent -= 1
1193 if indent < 0:
1194 # no negative indent allowed
1195 if classstack:
1196 # it's a class/module method
1197 indent = classstack[-1][1]
1198 else:
1199 indent = 0
1200
1201 elif (
1202 m.start("String") >= 0 or
1203 m.start("Comment") >= 0 or
1204 m.start("ClassIgnored") >= 0 or
1205 m.start("BeginEnd") >= 0
1206 ):
1207 pass
1208
1209 def createHierarchy(self):
1210 """
1211 Public method to build the inheritance hierarchy for all classes of
1212 this module.
1213
1214 @return A dictionary with inheritance hierarchies.
1215 """
1216 hierarchy = {}
1217 for class_ in self.classes:
1218 self.assembleHierarchy(class_, self.classes, [class_], hierarchy)
1219 for module in self.modules:
1220 self.assembleHierarchy(module, self.modules, [module], hierarchy)
1221 return hierarchy
1222
1223 def assembleHierarchy(self, name, classes, path, result):
1224 """
1225 Public method to assemble the inheritance hierarchy.
1226
1227 This method will traverse the class hierarchy, from a given class
1228 and build up a nested dictionary of super-classes. The result is
1229 intended to be inverted, i.e. the highest level are the super classes.
1230
1231 This code is borrowed from Boa Constructor.
1232
1233 @param name name of class to assemble hierarchy (string)
1234 @param classes A dictionary of classes to look in.
1235 @param path
1236 @param result The resultant hierarchy
1237 """
1238 rv = {}
1239 if name in classes:
1240 for class_ in classes[name].super:
1241 if class_ not in classes:
1242 rv[class_] = {}
1243 exhausted = path + [class_]
1244 exhausted.reverse()
1245 self.addPathToHierarchy(
1246 exhausted, result, self.addPathToHierarchy)
1247 else:
1248 rv[class_] = self.assembleHierarchy(
1249 class_, classes, path + [class_], result)
1250
1251 if len(rv) == 0:
1252 exhausted = path
1253 exhausted.reverse()
1254 self.addPathToHierarchy(exhausted, result, self.addPathToHierarchy)
1255
1256 def addPathToHierarchy(self, path, result, fn):
1257 """
1258 Public method to put the exhausted path into the result dictionary.
1259
1260 @param path the exhausted path of classes
1261 @param result the result dictionary
1262 @param fn function to call for classe that are already part of the
1263 result dictionary
1264 """
1265 if path[0] in result:
1266 if len(path) > 1:
1267 fn(path[1:], result[path[0]], fn)
1268 else:
1269 for part in path:
1270 result[part] = {}
1271 result = result[part]
1272
1273 def getName(self):
1274 """
1275 Public method to retrieve the modules name.
1276
1277 @return module name (string)
1278 """
1279 return self.name
1280
1281 def getFileName(self):
1282 """
1283 Public method to retrieve the modules filename.
1284
1285 @return module filename (string)
1286 """
1287 return self.file
1288
1289 def getType(self):
1290 """
1291 Public method to get the type of the module's source.
1292
1293 @return type of the modules's source (string)
1294 """
1295 if self.type in [PY_SOURCE, PTL_SOURCE]:
1296 moduleType = "Python3"
1297 elif self.type == RB_SOURCE:
1298 moduleType = "Ruby"
1299 else:
1300 moduleType = ""
1301 return moduleType
1302
1303
1304 class Class(VisibilityBase):
1305 """
1306 Class to represent a Python class.
1307 """
1308 def __init__(self, module, name, superClasses, file, lineno):
1309 """
1310 Constructor
1311
1312 @param module name of module containing this class (string)
1313 @param name name of the class (string)
1314 @param superClasses list of classnames this class is inherited from
1315 (list of strings)
1316 @param file name of file containing this class (string)
1317 @param lineno linenumber of the class definition (integer)
1318 """
1319 self.module = module
1320 self.name = name
1321 if superClasses is None:
1322 superClasses = []
1323 self.super = superClasses
1324 self.methods = {}
1325 self.attributes = {}
1326 self.globals = {}
1327 self.file = file
1328 self.lineno = lineno
1329 self.endlineno = -1 # marker for "not set"
1330 self.description = ""
1331 self.setPublic()
1332
1333 def addMethod(self, name, function):
1334 """
1335 Public method to add information about a method.
1336
1337 @param name name of method to be added (string)
1338 @param function Function object to be added
1339 """
1340 self.methods[name] = function
1341
1342 def getMethod(self, name):
1343 """
1344 Public method to retrieve a method by name.
1345
1346 @param name name of the method (string)
1347 @return the named method or None
1348 """
1349 try:
1350 return self.methods[name]
1351 except KeyError:
1352 return None
1353
1354 def addAttribute(self, name, attr):
1355 """
1356 Public method to add information about attributes.
1357
1358 @param name name of the attribute to add (string)
1359 @param attr Attribute object to be added
1360 """
1361 if name not in self.attributes:
1362 self.attributes[name] = attr
1363 else:
1364 self.attributes[name].addAssignment(attr.lineno)
1365
1366 def getAttribute(self, name):
1367 """
1368 Public method to retrieve an attribute by name.
1369
1370 @param name name of the attribute (string)
1371 @return the named attribute or None
1372 """
1373 try:
1374 return self.attributes[name]
1375 except KeyError:
1376 return None
1377
1378 def addGlobal(self, name, attr):
1379 """
1380 Public method to add information about global (class) variables.
1381
1382 @param name name of the global to add (string)
1383 @param attr Attribute object to be added
1384 """
1385 if name not in self.globals:
1386 self.globals[name] = attr
1387 else:
1388 self.globals[name].addAssignment(attr.lineno)
1389
1390 def addDescription(self, description):
1391 """
1392 Public method to store the class docstring.
1393
1394 @param description the docstring to be stored (string)
1395 """
1396 self.description = description
1397
1398 def setEndLine(self, endLineNo):
1399 """
1400 Public method to record the number of the last line of a class.
1401
1402 @param endLineNo number of the last line (integer)
1403 """
1404 self.endlineno = endLineNo
1405
1406
1407 class RbModule(Class):
1408 """
1409 Class to represent a Ruby module.
1410 """
1411 def __init__(self, module, name, file, lineno):
1412 """
1413 Constructor
1414
1415 @param module name of module containing this class (string)
1416 @param name name of the class (string)
1417 @param file name of file containing this class (string)
1418 @param lineno linenumber of the class definition (integer)
1419 """
1420 Class.__init__(self, module, name, None, file, lineno)
1421 self.classes = {}
1422
1423 def addClass(self, name, _class):
1424 """
1425 Public method to add information about a class.
1426
1427 @param name name of class to be added (string)
1428 @param _class Class object to be added
1429 """
1430 self.classes[name] = _class
1431
1432
1433 class Function(VisibilityBase):
1434 """
1435 Class to represent a Python function or method.
1436 """
1437 General = 0
1438 Static = 1
1439 Class = 2
1440
1441 def __init__(self, module, name, file, lineno, signature='',
1442 pyqtSignature=None, modifierType=General, annotation=""):
1443 """
1444 Constructor
1445
1446 @param module name of module containing this function (string)
1447 @param name name of the function (string)
1448 @param file name of file containing this function (string)
1449 @param lineno linenumber of the function definition (integer)
1450 @param signature the functions call signature (string)
1451 @param pyqtSignature the functions PyQt signature (string)
1452 @param modifierType type of the function
1453 @param annotation return annotation
1454 """
1455 self.module = module
1456 self.name = name
1457 self.file = file
1458 self.lineno = lineno
1459 self.endlineno = -1 # marker for "not set"
1460 signature = _commentsub('', signature)
1461 self.parameters = [e.strip() for e in signature.split(',')]
1462 self.description = ""
1463 self.pyqtSignature = pyqtSignature
1464 self.modifier = modifierType
1465 self.annotation = annotation
1466 self.setPublic()
1467
1468 def addDescription(self, description):
1469 """
1470 Public method to store the functions docstring.
1471
1472 @param description the docstring to be stored (string)
1473 """
1474 self.description = description
1475
1476 def setEndLine(self, endLineNo):
1477 """
1478 Public method to record the number of the last line of a class.
1479
1480 @param endLineNo number of the last line (integer)
1481 """
1482 self.endlineno = endLineNo
1483
1484
1485 class Attribute(VisibilityBase):
1486 """
1487 Class to represent a Python function or method.
1488 """
1489 def __init__(self, module, name, file, lineno, isSignal=False):
1490 """
1491 Constructor
1492
1493 @param module name of module containing this function (string)
1494 @param name name of the function (string)
1495 @param file name of file containing this function (string)
1496 @param lineno linenumber of the first attribute assignment (integer)
1497 @param isSignal flag indicating a signal definition (boolean)
1498 """
1499 self.module = module
1500 self.name = name
1501 self.file = file
1502 self.lineno = lineno
1503 self.isSignal = isSignal
1504 self.setPublic()
1505 self.linenos = [lineno]
1506
1507 def addAssignment(self, lineno):
1508 """
1509 Public method to add another assignment line number.
1510
1511 @param lineno linenumber of the additional attribute assignment
1512 (integer)
1513 """
1514 if lineno not in self.linenos:
1515 self.linenos.append(lineno)
1516
1517
1518 def readModule(module, path=None, inpackage=False, basename="",
1519 extensions=None, caching=True, ignoreBuiltinModules=False):
1520 """
1521 Function to read a module file and parse it.
1522
1523 The module is searched in path and sys.path, read and parsed.
1524 If the module was parsed before, the information is taken
1525 from a cache in order to speed up processing.
1526
1527 @param module name of the module to be parsed (string)
1528 @param path search path for the module (list of strings)
1529 @param inpackage flag indicating that module is inside a
1530 package (boolean)
1531 @param basename a path basename that is deleted from the filename of
1532 the module file to be read (string)
1533 @param extensions list of extensions, which should be considered valid
1534 source file extensions (list of strings)
1535 @param caching flag indicating that the parsed module should be
1536 cached (boolean)
1537 @param ignoreBuiltinModules flag indicating to ignore the builtin modules
1538 (boolean)
1539 @return reference to a Module object containing the parsed
1540 module information (Module)
1541 """
1542 global _modules
1543
1544 _extensions = (
1545 ['.py', '.pyw', '.ptl', '.rb']
1546 if extensions is None else
1547 extensions[:]
1548 )
1549 with contextlib.suppress(ValueError):
1550 _extensions.remove('.py')
1551
1552 modname = module
1553
1554 if os.path.exists(module):
1555 path = [os.path.dirname(module)]
1556 if module.lower().endswith(".py"):
1557 module = module[:-3]
1558 if (
1559 os.path.exists(os.path.join(path[0], "__init__.py")) or
1560 os.path.exists(os.path.join(path[0], "__init__.rb")) or
1561 inpackage
1562 ):
1563 if basename:
1564 module = module.replace(basename, "")
1565 if os.path.isabs(module):
1566 modname = os.path.splitdrive(module)[1][len(os.sep):]
1567 else:
1568 modname = module
1569 modname = modname.replace(os.sep, '.')
1570 inpackage = 1
1571 else:
1572 modname = os.path.basename(module)
1573 for ext in _extensions:
1574 if modname.lower().endswith(ext):
1575 modname = modname[:-len(ext)]
1576 break
1577 module = os.path.basename(module)
1578
1579 if caching and modname in _modules:
1580 # we've seen this module before...
1581 return _modules[modname]
1582
1583 if not ignoreBuiltinModules and module in sys.builtin_module_names:
1584 # this is a built-in module
1585 mod = Module(modname, None, None)
1586 if caching:
1587 _modules[modname] = mod
1588 return mod
1589
1590 # search the path for the module
1591 path = [] if path is None else path[:]
1592 f = None
1593 if inpackage:
1594 try:
1595 f, file, (suff, mode, moduleType) = find_module(
1596 module, path, _extensions)
1597 except ImportError:
1598 f = None
1599 if f is None:
1600 fullpath = path[:] + sys.path[:]
1601 f, file, (suff, mode, moduleType) = find_module(
1602 module, fullpath, _extensions)
1603 if f:
1604 f.close()
1605 if moduleType not in SUPPORTED_TYPES:
1606 # not supported source, can't do anything with this module
1607 _modules[modname] = Module(modname, None, None)
1608 return _modules[modname]
1609
1610 mod = Module(modname, file, moduleType)
1611 with contextlib.suppress(UnicodeError, OSError):
1612 src = Utilities.readEncodedFile(file)[0]
1613 mod.scan(src)
1614 if caching:
1615 _modules[modname] = mod
1616 return mod
1617
1618
1619 def _indent(ws):
1620 """
1621 Protected function to determine the indent width of a whitespace string.
1622
1623 @param ws The whitespace string to be cheked. (string)
1624 @return Length of the whitespace string after tab expansion.
1625 """
1626 return len(ws.expandtabs(TABWIDTH))
1627
1628
1629 def find_module(name, path, extensions):
1630 """
1631 Module function to extend the Python module finding mechanism.
1632
1633 This function searches for files in the given path. If the filename
1634 doesn't have an extension or an extension of .py, the normal search
1635 implemented in the imp module is used. For all other supported files
1636 only path is searched.
1637
1638 @param name filename or modulename to search for (string)
1639 @param path search path (list of strings)
1640 @param extensions list of extensions, which should be considered valid
1641 source file extensions (list of strings)
1642 @return tuple of the open file, pathname and description. Description
1643 is a tuple of file suffix, file mode and file type)
1644 @exception ImportError The file or module wasn't found.
1645 """
1646 for ext in extensions:
1647 if name.lower().endswith(ext):
1648 for p in path: # only search in path
1649 if os.path.exists(os.path.join(p, name)):
1650 pathname = os.path.join(p, name)
1651 if ext == '.ptl':
1652 # Quixote page template
1653 return (
1654 open(pathname), pathname,
1655 # __IGNORE_WARNING_Y115__
1656 ('.ptl', 'r', PTL_SOURCE)
1657 )
1658 elif ext == '.rb':
1659 # Ruby source file
1660 return (
1661 open(pathname), pathname,
1662 # __IGNORE_WARNING_Y115__
1663 ('.rb', 'r', RB_SOURCE)
1664 )
1665 else:
1666 return (
1667 open(pathname), pathname,
1668 # __IGNORE_WARNING_Y115__
1669 (ext, 'r', PY_SOURCE)
1670 )
1671 raise ImportError
1672
1673 # standard Python module file
1674 if name.lower().endswith('.py'):
1675 name = name[:-3]
1676
1677 spec = importlib.machinery.PathFinder.find_spec(name, path)
1678 if spec is None:
1679 raise ImportError
1680 if isinstance(spec.loader, importlib.machinery.SourceFileLoader):
1681 ext = os.path.splitext(spec.origin)[-1]
1682 return (open(spec.origin), spec.origin, (ext, 'r', PY_SOURCE))
1683 # __IGNORE_WARNING_Y115__
1684
1685 raise ImportError
1686
1687
1688 def resetParsedModules():
1689 """
1690 Module function to reset the list of modules already parsed.
1691 """
1692 _modules.clear()
1693
1694
1695 def resetParsedModule(module, basename=""):
1696 """
1697 Module function to clear one module from the list of parsed modules.
1698
1699 @param module Name of the module to be parsed (string)
1700 @param basename a path basename. This basename is deleted from
1701 the filename of the module file to be cleared. (string)
1702 """
1703 modname = module
1704
1705 if os.path.exists(module):
1706 path = [os.path.dirname(module)]
1707 if module.lower().endswith(".py"):
1708 module = module[:-3]
1709 if os.path.exists(os.path.join(path[0], "__init__.py")):
1710 if basename:
1711 module = module.replace(basename, "")
1712 modname = module.replace(os.sep, '.')
1713 else:
1714 modname = os.path.basename(module)
1715 if (
1716 modname.lower().endswith(".ptl") or
1717 modname.lower().endswith(".pyw")
1718 ):
1719 modname = modname[:-4]
1720 elif modname.lower().endswith(".rb"):
1721 modname = modname[:-3]
1722 module = os.path.basename(module)
1723
1724 if modname in _modules:
1725 del _modules[modname]

eric ide

mercurial