1
- import types
1
+ import builtins
2
2
from pathlib import Path
3
- from typing import Type , Optional
3
+ from types import ModuleType
4
+ from typing import Type , Optional , Set
4
5
from pygments .lexers .python import NumPyLexer
5
- from inspect import getmembers , isfunction , ismethod , ismodule , isclass
6
+ from inspect import getmembers , isfunction , ismethod , ismodule , isclass , isbuiltin , ismethoddescriptor
6
7
7
8
8
- def is_module_in_package (object ):
9
- """Predicate to determine if an object is a module within the project's top level package"""
10
- return ismodule (object ) and object .__name__ .startswith (TDKMethLexer . TOP_LEVEL )
9
+ def is_module_in_package (object , top_level ):
10
+ """Predicate to determine if an object is a module within a top level package"""
11
+ return ismodule (object ) and object .__name__ .startswith (top_level )
11
12
12
13
13
- def is_class_in_package (object ):
14
- """Predicate to determine if an object is a class within the project's top level package"""
15
- return isclass (object ) and object .__module__ .startswith (TDKMethLexer . TOP_LEVEL )
14
+ def is_class_in_package (object , top_level ):
15
+ """Predicate to determine if an object is a class within a top level package"""
16
+ return isclass (object ) and object .__module__ .startswith (top_level )
16
17
17
18
18
- def get_pkg_funcs (pkg_module : types .ModuleType , funcs_meths = set (), processed_modules = set ()):
19
+ def get_pkg_funcs (pkg_module : ModuleType , top_level : str , funcs_meths : Set [str ] = set (), processed_modules : Set [ModuleType ] = set ()) -> Set [str ]:
20
+ """Finds all functions and methods that are defined within a package and its subpackages.
21
+
22
+ For external modules that are imported into the package, only the functions and methods
23
+ that are directly within the module are included.
24
+
25
+ :param pkg_module: the package's top-level module
26
+ :param top_level: the name of the top-level module
27
+ :param funcs_meths: a set of function/method names
28
+ :param processed_modules: a set of already processed modules
29
+ :return: a set containing the names of all found functions and methods
30
+ """
19
31
funcs_meths .update (get_funcs (pkg_module ))
20
32
processed_modules .add (pkg_module )
21
33
22
- for class_name , _class in getmembers (pkg_module , is_class_in_package ):
34
+ for class_name , _class in getmembers (pkg_module , isclass ):
23
35
funcs_meths .update (get_funcs (_class ))
24
36
25
- for mod_name , mod in getmembers (pkg_module , is_module_in_package ):
37
+ for mod_name , mod in getmembers (pkg_module , ismodule ):
26
38
if mod in processed_modules :
27
39
continue
28
40
41
+ processed_modules .add (mod )
42
+
29
43
try :
30
44
mod_path = Path (mod .__file__ )
31
- except AttributeError :
32
- continue # It's a built-in module
45
+ except AttributeError : # It's a built-in module
46
+ funcs_meths .update (get_funcs (mod ))
47
+ continue
33
48
34
- if mod_path .name == '__init__.py' : # If it's a subpackage, call recursively to process all submodules
35
- get_pkg_funcs (mod , funcs_meths , processed_modules )
49
+ if is_module_in_package (mod , top_level ):
50
+ if mod_path .name == '__init__.py' : # If it's a subpackage, call recursively on submodules
51
+ get_pkg_funcs (mod , top_level , funcs_meths , processed_modules )
52
+ else :
53
+ funcs_meths .update (get_funcs (mod ))
54
+
55
+ else : # For external modules, avoid recursion into submodules
56
+ get_funcs_from_external_module (mod , funcs_meths )
57
+
58
+ return funcs_meths
36
59
37
- else : # If it's not a subpackage, get all funcs/meths defined in it
38
- funcs_meths .update (get_funcs (mod ))
39
- processed_modules .add (mod )
60
+
61
+ def get_funcs_from_external_module (module : ModuleType , funcs_meths : Set [str ]):
62
+ """Adds functions/methods contained within an imported external module"""
63
+ funcs_meths .update (get_funcs (module ))
64
+ top_level = module .__name__
65
+
66
+ for class_name , _class in getmembers (module , lambda obj : is_class_in_package (obj , top_level )):
67
+ funcs_meths .update (get_funcs (_class ))
68
+
69
+ return funcs_meths
70
+
71
+
72
+ def get_builtin_funcs ():
73
+ funcs_meths = set (dict (getmembers (builtins , isbuiltin )))
74
+
75
+ for class_name , _class in getmembers (builtins , isclass ):
76
+ methods = getmembers (_class , ismethoddescriptor )
77
+ funcs_meths .update (set (dict (methods )))
40
78
41
79
return funcs_meths
42
80
@@ -53,7 +91,9 @@ class TDKMethLexer(NumPyLexer):
53
91
name = 'TDK'
54
92
url = 'https://github.com/TDKorn'
55
93
aliases = ['tdk' ]
94
+
56
95
TOP_LEVEL = None
96
+ EXTRA_KEYWORDS = get_builtin_funcs ()
57
97
58
98
@classmethod
59
99
def get_pkg_lexer (cls , pkg_name : Optional [str ] = None ) -> Type ["TDKMethLexer" ]:
@@ -64,8 +104,8 @@ def get_pkg_lexer(cls, pkg_name: Optional[str] = None) -> Type["TDKMethLexer"]:
64
104
raise ValueError ('Must provide a package name' )
65
105
66
106
pkg = __import__ (cls .TOP_LEVEL )
67
- funcs = get_pkg_funcs (pkg )
68
- cls .EXTRA_KEYWORDS = funcs
107
+ funcs = get_pkg_funcs (pkg , cls . TOP_LEVEL )
108
+ cls .EXTRA_KEYWORDS . update ( funcs )
69
109
return cls
70
110
71
111
0 commit comments