|
1 | | -import enum, gc, glob, importlib, importlib.util, io, inspect, os, sys |
| 1 | +import enum, gc, glob, importlib, importlib.util, itertools, io, inspect, os |
| 2 | +import sys |
| 3 | +from pkg_resources import iter_entry_points |
2 | 4 | import typing, uuid |
3 | 5 | from . import Config, EventManager, Exports, IRCBot, Logging, Timers, utils |
4 | 6 |
|
@@ -117,19 +119,62 @@ def __init__(self, |
117 | 119 | def _list_modules(self, directory: str |
118 | 120 | ) -> typing.Dict[str, ModuleDefinition]: |
119 | 121 | modules = [] |
| 122 | + |
120 | 123 | for file_module in glob.glob(os.path.join(directory, "*.py")): |
121 | | - modules.append(self.define_module(ModuleType.FILE, file_module)) |
| 124 | + # Excluse __init__.py, etc. |
| 125 | + if not file_module.rsplit(os.path.sep)[-1].startswith('_'): |
| 126 | + modules.append(self.define_module(ModuleType.FILE, file_module)) |
122 | 127 |
|
123 | 128 | for directory_module in glob.glob(os.path.join( |
124 | 129 | directory, "*", "__init__.py")): |
125 | 130 | modules.append(self.define_module(ModuleType.DIRECTORY, |
126 | 131 | directory_module)) |
127 | 132 | return {definition.name: definition for definition in modules} |
128 | 133 |
|
| 134 | + def _list_installed_modules(self, entry_point_group |
| 135 | + ) -> typing.Dict[str, ModuleDefinition]: |
| 136 | + """Finds modules installed using a pkg_resources EntryPoint. |
| 137 | +
|
| 138 | + They are installed by `setuptools` by using: |
| 139 | +
|
| 140 | + ``` |
| 141 | + setup( |
| 142 | + # ... |
| 143 | + entry_points={ |
| 144 | + 'bitbot.extra_modules': [ |
| 145 | + 'module_name = your_package_name.your_module_name:Module', |
| 146 | + # ... |
| 147 | + } |
| 148 | + } |
| 149 | + ) |
| 150 | + ``` |
| 151 | +
|
| 152 | + (replace only `module_name`, `your_package_name`, and |
| 153 | + `your_module_name` on the example above) |
| 154 | + """ |
| 155 | + modules = {} |
| 156 | + |
| 157 | + for entry_point in iter_entry_points(entry_point_group): |
| 158 | + module_class = entry_point.load() |
| 159 | + path = sys.modules[module_class.__module__].__file__ |
| 160 | + if path.rsplit(os.path.sep, 1)[-1] == '__init__.py': |
| 161 | + type = ModuleType.DIRECTORY |
| 162 | + else: |
| 163 | + type = ModuleType.FILE |
| 164 | + modules[entry_point.name] = self.define_module(type, path) |
| 165 | + |
| 166 | + return modules |
| 167 | + |
129 | 168 | def list_modules(self, whitelist: typing.List[str], |
130 | 169 | blacklist: typing.List[str]) -> typing.Dict[str, ModuleDefinition]: |
131 | | - core_modules = self._list_modules(self._core_modules) |
132 | | - extra_modules: typing.Dict[str, ModuleDefinition] = {} |
| 170 | + """Discovers modules that are either installed or in one of the |
| 171 | + directories listed by `[self._core_modules] + self._extra_modules`.""" |
| 172 | + |
| 173 | + core_modules = self._list_installed_modules('bitbot.core_modules') |
| 174 | + extra_modules = self._list_installed_modules('bitbot.extra_modules') |
| 175 | + |
| 176 | + for name, module in self._list_modules(self._core_modules).items(): |
| 177 | + core_modules[name] = module |
133 | 178 |
|
134 | 179 | for directory in self._extra_modules: |
135 | 180 | for name, module in self._list_modules(directory).items(): |
@@ -179,6 +224,15 @@ def _module_name(self, path: str) -> str: |
179 | 224 | return os.path.basename(path).rsplit(".py", 1)[0].lower() |
180 | 225 | def _module_paths(self, name: str) -> typing.List[str]: |
181 | 226 | paths = [] |
| 227 | + |
| 228 | + entry_points = itertools.chain( |
| 229 | + iter_entry_points('bitbot.core_modules', name), |
| 230 | + iter_entry_points('bitbot.extra_modules', name)) |
| 231 | + |
| 232 | + for entry_point in entry_points: |
| 233 | + module_class = entry_point.load() |
| 234 | + paths.append(sys.modules[module_class.__module__].__path__) # type: ignore |
| 235 | + |
182 | 236 | for directory in [self._core_modules]+self._extra_modules: |
183 | 237 | paths.append(os.path.join(directory, name)) |
184 | 238 | return paths |
|
0 commit comments