| #!/usr/bin/env python3 |
| # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| """Module to manage IDL files.""" |
| |
| import copy |
| import pickle |
| import logging |
| import os |
| import os.path |
| import shutil |
| import idlnode |
| import idlrenderer |
| from generator import IsDartCollectionType, IsPureInterface |
| |
| _logger = logging.getLogger('database') |
| |
| |
| class Database(object): |
| """The Database class manages a collection of IDL files stored |
| inside a directory. |
| |
| Each IDL is describing a single interface. The IDL files are written in the |
| FremontCut syntax, which is derived from the Web IDL syntax and includes |
| annotations. |
| |
| Database operations include adding, updating and removing IDL files. |
| """ |
| |
| def __init__(self, root_dir): |
| """Initializes a Database over a given directory. |
| |
| Args: |
| root_dir -- a directory. If directory does not exist, it will |
| be created. |
| """ |
| self._root_dir = root_dir |
| if not os.path.exists(root_dir): |
| _logger.debug('creating root directory %s' % root_dir) |
| os.makedirs(root_dir) |
| self._all_interfaces = {} |
| self._interfaces_to_delete = [] |
| self._enums = {} |
| self._all_dictionaries = {} |
| # TODO(terry): Hack to remember all typedef unions. |
| self._all_type_defs = {} |
| |
| def Clone(self): |
| new_database = Database(self._root_dir) |
| new_database._all_interfaces = copy.deepcopy(self._all_interfaces) |
| new_database._interfaces_to_delete = copy.deepcopy( |
| self._interfaces_to_delete) |
| new_database._enums = copy.deepcopy(self._enums) |
| new_database._all_dictionaries = copy.deepcopy(self._all_dictionaries) |
| new_database._all_type_defs = copy.deepcopy(self._all_type_defs) |
| |
| return new_database |
| |
| def Delete(self): |
| """Deletes the database by deleting its directory""" |
| if os.path.exists(self._root_dir): |
| shutil.rmtree(self._root_dir) |
| # reset in-memory constructs |
| self._all_interfaces = {} |
| |
| def _ScanForInterfaces(self): |
| """Iteratores over the database files and lists all interface names. |
| |
| Return: |
| A list of interface names. |
| """ |
| res = [] |
| |
| def Visitor(_, dirname, names): |
| for name in names: |
| if os.path.isfile(os.path.join(dirname, name)): |
| root, ext = os.path.splitext(name) |
| if ext == '.idl': |
| res.append(root) |
| |
| os.path.walk(self._root_dir, Visitor, None) |
| return res |
| |
| def _FilePath(self, interface_name): |
| """Calculates the file path that a given interface should |
| be saved to. |
| |
| Args: |
| interface_name -- the name of the interface. |
| """ |
| return os.path.join(self._root_dir, '%s.idl' % interface_name) |
| |
| def _LoadInterfaceFile(self, interface_name): |
| """Loads an interface from the database. |
| |
| Returns: |
| An IDLInterface instance or None if the interface is not found. |
| Args: |
| interface_name -- the name of the interface. |
| """ |
| file_name = self._FilePath(interface_name) |
| _logger.info('loading %s' % file_name) |
| if not os.path.exists(file_name): |
| return None |
| |
| f = open(file_name, 'r') |
| content = f.read() |
| f.close() |
| |
| # Parse file: |
| idl_file = idlnode.IDLFile(self._idlparser.parse(content), file_name) |
| |
| if not idl_file.interfaces: |
| raise RuntimeError('No interface found in %s' % file_name) |
| elif len(idl_file.interfaces) > 1: |
| raise RuntimeError('Expected one interface in %s' % file_name) |
| |
| interface = idl_file.interfaces[0] |
| self._all_interfaces[interface_name] = interface |
| return interface |
| |
| def Load(self): |
| """Loads all interfaces into memory. |
| """ |
| # FIXME: Speed this up by multi-threading. |
| for (interface_name) in self._ScanForInterfaces(): |
| self._LoadInterfaceFile(interface_name) |
| self.Cache() |
| |
| def Cache(self): |
| """Serialize the database using pickle for faster startup in the future |
| """ |
| output_file = open(os.path.join(self._root_dir, 'cache.pickle'), 'wb') |
| pickle.dump(self._all_interfaces, output_file) |
| pickle.dump(self._interfaces_to_delete, output_file) |
| |
| def LoadFromCache(self): |
| """Deserialize the database using pickle for fast startup |
| """ |
| input_file_name = os.path.join(self._root_dir, 'cache.pickle') |
| if not os.path.isfile(input_file_name): |
| self.Load() |
| return |
| input_file = open(input_file_name, 'rb') |
| self._all_interfaces = pickle.load(input_file) |
| self._interfaces_to_delete = pickle.load(input_file) |
| input_file.close() |
| |
| def Save(self): |
| """Saves all in-memory interfaces into files.""" |
| for interface in self._all_interfaces.values(): |
| self._SaveInterfaceFile(interface) |
| for interface_name in self._interfaces_to_delete: |
| self._DeleteInterfaceFile(interface_name) |
| |
| def _SaveInterfaceFile(self, interface): |
| """Saves an interface into the database. |
| |
| Args: |
| interface -- an IDLInterface instance. |
| """ |
| |
| interface_name = interface.id |
| |
| # Actual saving |
| file_path = self._FilePath(interface_name) |
| _logger.debug('writing %s' % file_path) |
| |
| dir_name = os.path.dirname(file_path) |
| if not os.path.exists(dir_name): |
| _logger.debug('creating directory %s' % dir_name) |
| os.mkdir(dir_name) |
| |
| # Render the IDLInterface object into text. |
| text = idlrenderer.render(interface) |
| |
| f = open(file_path, 'w') |
| f.write(text) |
| f.close() |
| |
| def HasInterface(self, interface_name): |
| """Returns True if the interface is in memory""" |
| return interface_name in self._all_interfaces |
| |
| def GetInterface(self, interface_name): |
| """Returns an IDLInterface corresponding to the interface_name |
| from memory. |
| |
| Args: |
| interface_name -- the name of the interface. |
| """ |
| if interface_name not in self._all_interfaces: |
| raise RuntimeError('Interface %s is not loaded' % interface_name) |
| return self._all_interfaces[interface_name] |
| |
| def AddInterface(self, interface): |
| """Returns an IDLInterface corresponding to the interface_name |
| from memory. |
| |
| Args: |
| interface -- the name of the interface. |
| """ |
| interface_name = interface.id |
| if interface_name in self._all_interfaces: |
| raise RuntimeError('Interface %s already exists' % interface_name) |
| self._all_interfaces[interface_name] = interface |
| |
| def GetInterfaces(self): |
| """Returns a list of all loaded interfaces.""" |
| res = [] |
| for _, interface in sorted(self._all_interfaces.items()): |
| res.append(interface) |
| return res |
| |
| def DeleteInterface(self, interface_name): |
| """Deletes an interface from the database. File is deleted when |
| Save() is called. |
| |
| Args: |
| interface_name -- the name of the interface. |
| """ |
| if interface_name not in self._all_interfaces: |
| raise RuntimeError('Interface %s not found' % interface_name) |
| self._interfaces_to_delete.append(interface_name) |
| del self._all_interfaces[interface_name] |
| |
| def _DeleteInterfaceFile(self, interface_name): |
| """Actual file deletion""" |
| file_path = self._FilePath(interface_name) |
| if os.path.exists(file_path): |
| _logger.debug('deleting %s' % file_path) |
| os.remove(file_path) |
| |
| def Hierarchy(self, interface): |
| yield interface |
| for parent in interface.parents: |
| parent_name = parent.type.id |
| if not self.HasInterface(parent.type.id): |
| continue |
| for parent_interface in self.Hierarchy( |
| self.GetInterface(parent.type.id)): |
| yield parent_interface |
| |
| def HasEnum(self, enum_name): |
| return enum_name in self._enums |
| |
| def GetEnum(self, enum_name): |
| return self._enums[enum_name] |
| |
| def AddEnum(self, enum): |
| self._enums[enum.id] = enum |
| |
| def HasDictionary(self, dictionary_name): |
| """Returns True if the dictionary is in memory""" |
| return dictionary_name in self._all_dictionaries |
| |
| def GetDictionary(self, dictionary_name): |
| """Returns an IDLDictionary corresponding to the dictionary_name |
| from memory. |
| |
| Args: |
| dictionary_name -- the name of the dictionary. |
| """ |
| if dictionary_name not in self._all_dictionaries: |
| raise RuntimeError('Dictionary %s is not loaded' % dictionary_name) |
| return self._all_dictionaries[dictionary_name] |
| |
| def AddDictionary(self, dictionary): |
| """Returns an IDLDictionary corresponding to the dictionary_name |
| from memory. |
| |
| Args: |
| dictionary -- the name of the dictionary. |
| """ |
| dictionary_name = dictionary.id |
| if dictionary_name in self._all_dictionaries: |
| raise RuntimeError('Dictionary %s already exists' % dictionary_name) |
| self._all_dictionaries[dictionary_name] = dictionary |
| |
| def GetDictionaries(self): |
| """Returns a list of all loaded dictionaries.""" |
| res = [] |
| for _, dictionary in sorted(self._all_dictionaries.items()): |
| res.append(dictionary) |
| return res |
| |
| def HasTypeDef(self, type_def_name): |
| """Returns True if the typedef is in memory""" |
| return type_def_name in self._all_type_defs |
| |
| def GetTypeDef(self, type_def_name): |
| """Returns an IDLTypeDef corresponding to the type_def_name |
| from memory. |
| |
| Args: |
| type_def_name -- the name of the typedef. |
| """ |
| if type_def_name not in self._all_type_defs: |
| raise RuntimeError('Typedef %s is not loaded' % type_def_name) |
| return self._all_type_defs[type_def_name] |
| |
| def AddTypeDef(self, type_def): |
| """Add only a typedef that a unions they map to any (no type).""" |
| type_def_name = type_def.id |
| if type_def_name in self._all_type_defs: |
| raise RuntimeError('Typedef %s already exists' % type_def_name) |
| self._all_type_defs[type_def_name] = type_def |
| print(' Added typedef %s' % type_def_name) |
| |
| def TransitiveSecondaryParents(self, interface, propagate_event_target): |
| """Returns a list of all non-primary parents. |
| |
| The list contains the interface objects for interfaces defined in the |
| database, and the name for undefined interfaces. |
| """ |
| |
| def walk(parents, walk_result): |
| for parent in parents: |
| parent_name = parent.type.id |
| if IsDartCollectionType(parent_name): |
| if not (parent_name in walk_result): |
| walk_result.append(parent_name) |
| continue |
| if self.HasInterface(parent_name): |
| parent_interface = self.GetInterface(parent_name) |
| if not (parent_interface in walk_result): |
| # Interface has multi-inherited don't add interfaces more than once |
| # to our parent result list. |
| walk_result.append(parent_interface) |
| walk(parent_interface.parents, walk_result) |
| return walk_result |
| |
| result = [] |
| if interface.parents: |
| parent = interface.parents[0] |
| if (IsPureInterface(parent.type.id, self) or |
| (propagate_event_target and parent.type.id == 'EventTarget')): |
| result = walk(interface.parents, []) |
| else: |
| result = walk(interface.parents[1:], []) |
| |
| return result |