1####################################################################### 2# Name: scoping.__init__.py 3# Purpose: Meta-model / scope providers. 4# Author: Pierre Bayerl 5# License: MIT License 6####################################################################### 7 8import glob 9import os 10import errno 11from os.path import join, exists, abspath 12 13 14def metamodel_for_file_or_default_metamodel(filename, the_metamodel): 15 from textx import metamodel_for_file 16 from textx.exceptions import TextXRegistrationError 17 try: 18 return metamodel_for_file(filename) 19 except TextXRegistrationError: 20 return the_metamodel 21 22 23# ----------------------------------------------------------------------------- 24# Scope helper classes: 25# ----------------------------------------------------------------------------- 26 27class Postponed(object): 28 """ 29 Return an object of this class to postpone a reference resolution. 30 If you get circular dependencies in resolution logic, an error 31 is raised. 32 """ 33 34 35class ModelRepository(object): 36 """ 37 This class has the responsibility to hold a set of (model-identifiers, 38 model) pairs as dictionary. 39 In case of some scoping providers the model-identifier is the absolute 40 filename of the model. 41 """ 42 43 def __init__(self): 44 self.name_idx = 1 45 self.filename_to_model = {} 46 47 def has_model(self, filename): 48 return abspath(filename) in self.filename_to_model 49 50 def add_model(self, model): 51 if model._tx_filename: 52 filename = abspath(model._tx_filename) 53 else: 54 filename = 'builtin_model_{}'.format(self.name_idx) 55 self.name_idx += 1 56 self.filename_to_model[filename] = model 57 58 def remove_model(self, model): 59 filename = None 60 for f, m in self.filename_to_model.items(): 61 if m == model: 62 filename = f 63 if filename: 64 # print("*** delete {}".format(filename)) 65 del self.filename_to_model[filename] 66 67 def __contains__(self, filename): 68 return self.has_model(filename) 69 70 def __iter__(self): 71 return iter(self.filename_to_model.values()) 72 73 def __len__(self): 74 return len(self.filename_to_model) 75 76 def __getitem__(self, filename): 77 return self.filename_to_model[filename] 78 79 def __setitem__(self, filename, model): 80 self.filename_to_model[filename] = model 81 82 83class GlobalModelRepository(object): 84 """ 85 This class has the responsibility to hold two ModelRepository objects: 86 87 - one for model-local visible models 88 - one for all models (globally, starting from some root model). 89 90 The second `ModelRepository` `all_models` is to cache already loaded models 91 and to prevent to load one model twice. 92 93 The class allows loading local models visible to the current model. The 94 current model is the model which references this `GlobalModelRepository` as 95 attribute `_tx_model_repository` 96 97 When loading a new local model, the current `GlobalModelRepository` 98 forwards the embedded `ModelRepository` `all_models` to the new 99 `GlobalModelRepository` of the next model. This is done using the 100 `pre_ref_resolution_callback` to set the necessary information before 101 resolving the references in the new loaded model. 102 103 """ 104 105 def __init__(self, all_models=None): 106 """ 107 Create a new repo for a model. 108 109 Args: 110 all_models: models to be added to this new repository. 111 """ 112 self.local_models = ModelRepository() # used for current model 113 if all_models is not None: 114 self.all_models = all_models # used to reuse already loaded models 115 else: 116 self.all_models = ModelRepository() 117 118 def remove_model(self, model): 119 self.all_models.remove_model(model) 120 self.local_models.remove_model(model) 121 122 def remove_models(self, models): 123 for m in models: 124 self.remove_model(m) 125 126 def load_models_using_filepattern( 127 self, filename_pattern, model, glob_args, is_main_model=False, 128 encoding='utf-8', add_to_local_models=True, model_params=None): 129 """ 130 Add a new model to all relevant objects. 131 132 Args: 133 filename_pattern: models to be loaded 134 model: model holding the loaded models in its _tx_model_repository 135 field (may be None). 136 glob_args: arguments passed to the glob.glob function. 137 138 Returns: 139 the list of loaded models 140 """ 141 from textx import get_metamodel 142 if model is not None: 143 self.update_model_in_repo_based_on_filename(model) 144 the_metamodel = get_metamodel(model) # default metamodel 145 else: 146 the_metamodel = None 147 filenames = glob.glob(filename_pattern, **glob_args) 148 if len(filenames) == 0: 149 raise IOError( 150 errno.ENOENT, os.strerror(errno.ENOENT), filename_pattern) 151 loaded_models = [] 152 for filename in filenames: 153 the_metamodel = metamodel_for_file_or_default_metamodel( 154 filename, the_metamodel) 155 loaded_models.append( 156 self.load_model(the_metamodel, filename, is_main_model, 157 encoding=encoding, 158 add_to_local_models=add_to_local_models, 159 model_params=model_params)) 160 return loaded_models 161 162 def load_model_using_search_path( 163 self, filename, model, search_path, is_main_model=False, 164 encoding='utf8', add_to_local_models=True, model_params=None): 165 """ 166 Add a new model to all relevant objects 167 168 Args: 169 filename: models to be loaded 170 model: model holding the loaded models in its _tx_model_repository 171 field (may be None). 172 search_path: list of search directories. 173 174 Returns: 175 the loaded model 176 """ 177 from textx import get_metamodel 178 if model: 179 self.update_model_in_repo_based_on_filename(model) 180 for the_path in search_path: 181 full_filename = join(the_path, filename) 182 # print(full_filename) 183 if exists(full_filename): 184 if model is not None: 185 the_metamodel = get_metamodel(model) 186 else: 187 the_metamodel = None 188 the_metamodel = metamodel_for_file_or_default_metamodel( 189 filename, the_metamodel) 190 return self.load_model(the_metamodel, 191 full_filename, 192 is_main_model, 193 encoding=encoding, 194 add_to_local_models=add_to_local_models, 195 model_params=model_params) 196 197 raise IOError( 198 errno.ENOENT, os.strerror(errno.ENOENT), filename) 199 200 def load_model( 201 self, the_metamodel, filename, is_main_model, encoding='utf-8', 202 add_to_local_models=True, model_params=None): 203 """ 204 Load a single model 205 206 Args: 207 the_metamodel: the metamodel used to load the model 208 filename: the model to be loaded (if not cached) 209 210 Returns: 211 the loaded/cached model 212 """ 213 assert model_params is not None,\ 214 "model_params needs to be specified" 215 216 filename = abspath(filename) 217 if not self.local_models.has_model(filename): 218 if self.all_models.has_model(filename): 219 # print("CACHED {}".format(filename)) 220 new_model = self.all_models[filename] 221 else: 222 # print("LOADING {}".format(filename)) 223 # all models loaded here get their references resolved from the 224 # root model 225 new_model = the_metamodel.internal_model_from_file( 226 filename, pre_ref_resolution_callback=lambda 227 other_model: self.pre_ref_resolution_callback(other_model), 228 is_main_model=is_main_model, encoding=encoding, 229 model_params=model_params) 230 self.all_models[filename] = new_model 231 # print("ADDING {}".format(filename)) 232 if add_to_local_models: 233 self.local_models[filename] = new_model 234 else: 235 # print("LOCALLY CACHED {}".format(filename)) 236 pass 237 238 assert filename in self.all_models # to be sure... 239 return self.all_models[filename] 240 241 def _add_model(self, model): 242 filename = self.update_model_in_repo_based_on_filename(model) 243 # print("ADDED {}".format(filename)) 244 self.local_models[filename] = model 245 246 def update_model_in_repo_based_on_filename(self, model): 247 """ 248 Adds a model to the repo (not initially visible) 249 250 Args: 251 model: the model to be added. If the model 252 has no filename, a name is invented 253 254 Returns: 255 the filename of the model added to the repo 256 """ 257 if model._tx_filename is None: 258 for fn in self.all_models.filename_to_model: 259 if self.all_models.filename_to_model[fn] == model: 260 # print("UPDATED/CACHED {}".format(fn)) 261 return fn 262 i = 0 263 while self.all_models.has_model("anonymous{}".format(i)): 264 i += 1 265 myfilename = "anonymous{}".format(i) 266 self.all_models[myfilename] = model 267 else: 268 myfilename = abspath(model._tx_filename) 269 if (not self.all_models.has_model(myfilename)): 270 self.all_models[myfilename] = model 271 # print("UPDATED/ADDED/CACHED {}".format(myfilename)) 272 return myfilename 273 274 def pre_ref_resolution_callback(self, other_model): 275 """ 276 internal: used to store a model after parsing into the repository 277 278 Args: 279 other_model: the parsed model 280 281 Returns: 282 nothing 283 """ 284 filename = other_model._tx_filename 285 # print("PRE-CALLBACK -> {}".format(filename)) 286 assert (filename) 287 filename = abspath(filename) 288 other_model._tx_model_repository = \ 289 GlobalModelRepository(self.all_models) 290 self.all_models[filename] = other_model 291 292 293class ModelLoader(object): 294 """ 295 This class is an interface to mark a scope provider as an additional model 296 loader. 297 """ 298 299 def __init__(self): 300 pass 301 302 def load_models(self, model): 303 pass 304 305 306def get_all_models_including_attached_models(model): 307 """ 308 get a list of all models stored within a model 309 (including the owning model). 310 311 @deprecated (BIC): use model_object.get_included_models() 312 313 Args: 314 model: the owning model 315 316 Returns: 317 a list of all models 318 """ 319 return get_included_models(model) 320 321 322def get_included_models(model): 323 """ 324 get a list of all models stored within a model 325 (including the owning model). 326 327 Args: 328 model: the owning model 329 330 Returns: 331 a list of all models 332 """ 333 if (hasattr(model, "_tx_model_repository")): 334 models = list(model._tx_model_repository.all_models) 335 if model not in models: 336 models.append(model) 337 else: 338 models = [model] 339 return models 340 341 342def is_file_included(filename, model): 343 """ 344 Determines if a file is included by a model. Also checks 345 for indirect inclusions (files included by included files). 346 347 Args: 348 filename: the file to be checked (filename is normalized) 349 model: the owning model 350 351 Returns: 352 True if the file is included, else False 353 (Note: if no _tx_model_repository is present, 354 the function always returns False) 355 """ 356 if (hasattr(model, "_tx_model_repository")): 357 all_entries = model._tx_model_repository.all_models 358 return all_entries.has_model(filename) 359 else: 360 return False 361 362 363def remove_models_from_repositories(models, 364 models_to_be_removed): 365 """ 366 Remove models from all relevant repositories (_tx_model_repository 367 of models and related metamodel(s), if applicable). 368 369 Args: 370 models: the list of models from 371 which the models_to_be_removed have to be removed. 372 models_to_be_removed: models to be removed 373 374 Returns: 375 None 376 """ 377 assert isinstance(models, list) 378 for model in models: 379 if hasattr(model._tx_metamodel, "_tx_model_repository"): 380 model._tx_metamodel. \ 381 _tx_model_repository.remove_models(models_to_be_removed) 382 if hasattr(model, "_tx_model_repository"): 383 model._tx_model_repository.remove_models(models_to_be_removed) 384