# Copyright (c) Pelagicore AB 2016
'''The domian module contains an object hierachy which resembles the
QFace grammar as a domain model. It is created from the QFace and the main
input for the code generation templates.
.. note:: Changes on this API will result into broken templates
.. code-block:: text
System
+- Module
+- Import
+- Interface
+- Property
+- Operation
+- Event
+- Struct (has attributes)
+- Enum (has values)
.. note::
When the API talks about an order list, the order is by appearance
in the QFace file.
'''
from collections import OrderedDict, ChainMap
import click
import logging
log = logging.getLogger(__name__)
[docs]class System(object):
"""The root entity which consist of modules"""
def __init__(self):
log.debug('System()')
self._moduleMap = OrderedDict() # type: dict[str, Module]
def __unicode__(self):
return 'system'
def __repr__(self):
return '<System>'
@property
def modules(self):
'''returns ordered list of module symbols'''
return self._moduleMap.values()
[docs] def lookup(self, name: str):
'''lookup a symbol by fully qualified name.'''
# <module>
if name in self._moduleMap:
return self._moduleMap[name]
# <module>.<Symbol>
(module_name, type_name, fragment_name) = self.split_typename(name)
if not module_name in self._moduleMap:
raise Exception('not able to lookup symbol: {0}'.format(name))
module = self._moduleMap[module_name]
# The module lookup calls this function again if the type_name doesn't exist
return module.lookup(type_name, fragment_name)
@staticmethod
def split_typename(name):
parts = name.rsplit('#', 1)
fragment_name = None
module_name = None
type_name = None
if len(parts) == 2:
fragment_name = parts[1]
name = parts[0]
parts = name.rsplit('.', 1)
if len(parts) == 1:
type_name = parts[0]
elif len(parts) == 2:
module_name = parts[0]
type_name = parts[1]
return (module_name, type_name, fragment_name)
def toJson(self):
o = OrderedDict()
o['modules'] = [o.toJson() for o in self.modules]
return o
[docs]class NamedElement(object):
def __init__(self, name, module: 'Module'):
self.name = name
"""symbol name"""
self.module = module
"""module the symbol belongs to"""
def __unicode__(self):
return self.name
def __str__(self):
return self.name
def __repr__(self):
return '<{0} name={1}>'.format(type(self), self.name)
@property
def qualified_name(self):
'''return the fully qualified name (`<module>.<name>`)'''
if self.module == self:
return self.module.name
else:
if "." not in self.name:
return '{0}.{1}'.format(self.module.name, self.name)
else:
# We have a fully qualified reference, just return it
return self.name
def toJson(self):
o = OrderedDict()
if self.name:
o['name'] = self.name
return o
[docs]class Symbol(NamedElement):
"""A symbol represents a base class for names elements"""
def __init__(self, name: str, module: 'Module'):
super().__init__(name, module)
self.comment = ''
"""comment which appeared in QFace right before symbol"""
self._tags = dict()
"""a value attached to the symbol"""
self.value = None
self._contentMap = ChainMap()
self._dependencies = set()
self.type = TypeSymbol('', self)
self.kind = self.__class__.__name__.lower()
""" the associated type information """
@property
def system(self):
'''returns reference to system'''
return self.module._system
@property
def tags(self):
return self._tags
[docs] def add_tag(self, tag):
""" add a tag to the tag list """
if tag not in self._tags:
self._tags[tag] = dict()
[docs] def add_attribute(self, tag, name, value):
""" add an attribute (nam, value pair) to the named tag """
self.add_tag(tag)
d = self._tags[tag]
d[name] = value
[docs] def tag(self, name):
""" return tag by name """
return self._tags[name]
[docs] def attribute(self, tag, name):
""" return attribute by tag and attribute name """
if tag in self._tags and name in self._tags[tag]:
return self._tags[tag][name]
@property
def contents(self):
""" return general list of symbol contents """
return self._contentMap.values()
@property
def dependencies(self):
if not self._dependencies:
self._dependencies = [x.type for x in self.contents]
return self._dependencies
def toJson(self):
o = super().toJson()
if self.type.is_valid:
o['type'] = self.type.toJson()
return o
[docs]class TypeSymbol(NamedElement):
"""Defines a type in the system"""
def __init__(self, name: str, parent: NamedElement):
super().__init__(name, parent.module)
log.debug('TypeSymbol()')
self.parent = parent
""" the parent symbol of this type """
self.is_void = False # type:bool
""" if type represents the void type """
self.is_primitive = False # type:bool
""" if type represents a primitive type """
self.is_complex = False # type:bool
""" if type represents a complex type """
self.is_list = False # type:bool
""" if type represents a list of nested types """
self.is_map = False # type:bool
""" if type represents a map of nested types. A key type is not defined """
self.is_model = False # type:bool
""" if type represents a model of nested types """
self.nested = None
"""nested type if symbol is list or model"""
self.__reference = None
self.__is_resolved = False
@property
def is_valid(self):
'''checks if type is a valid type'''
return (self.is_primitive and self.name) \
or (self.is_complex and self.name) \
or (self.is_list and self.nested) \
or (self.is_map and self.nested) \
or (self.is_model and self.nested)
@property
def is_bool(self):
'''checks if type is primitive and bool'''
return self.is_primitive and self.name == 'bool'
@property
def is_int(self):
'''checks if type is primitive and int'''
return self.is_primitive and self.name == 'int'
@property
def is_real(self):
'''checks if type is primitive and real'''
return self.is_primitive and self.name == 'real'
@property
def is_string(self):
'''checks if type is primitive and string'''
return self.is_primitive and self.name == 'string'
@property
def is_var(self):
'''checks if type is primitive and var'''
return self.is_primitive and self.name == 'var'
@property
def is_enumeration(self):
'''checks if type is complex and instance of type Enum'''
return self.is_complex and isinstance(self.reference, Enum)
@property
def is_enum(self):
'''checks if type is an enumeration and reference is enum'''
return self.is_enumeration and self.reference.is_enum
@property
def is_flag(self):
'''checks if type is an enumeration and reference is flag '''
return self.is_enumeration and self.reference.is_flag
@property
def is_struct(self):
'''checks if type is complex and struct'''
return self.is_complex and isinstance(self.reference, Struct)
@property
def is_interface(self):
'''checks if type is interface'''
return self.is_complex and isinstance(self.reference, Interface)
@property
def reference(self):
"""returns the symbol reference of the type name"""
if not self.__is_resolved:
self._resolve()
return self.__reference
def _resolve(self):
"""resolve the type symbol from name by doing a lookup"""
self.__is_resolved = True
if self.is_complex:
type = self.nested if self.nested else self
type.__reference = self.module.lookup(type.name)
@property
def type(self):
""" return the type information. In this case: self """
return self
def toJson(self):
o = super().toJson()
if self.is_void:
o['void'] = self.is_void
if self.is_primitive:
o['primitive'] = self.is_primitive
if self.is_complex:
o['complex'] = self.is_complex
if self.is_list:
o['list'] = self.is_list
if self.is_map:
o['map'] = self.is_map
if self.is_model:
o['model'] = self.is_model
if self.nested:
o['nested'] = self.nested.toJson()
return o
[docs]class Module(Symbol):
"""Module is a namespace for types, e.g. interfaces, enums, structs"""
def __init__(self, name: str, system: System):
"""init"""
super().__init__(name, self)
log.debug('Module()')
self.version = '1.0'
self._system = system
self._system._moduleMap[name] = self
self._interfaceMap = OrderedDict() # type: dict[str, Interface]
self._structMap = OrderedDict() # type: dict[str, Struct]
self._enumMap = OrderedDict() # type: dict[str, Enum]
self._contentMap = ChainMap(self._interfaceMap, self._structMap, self._enumMap)
self._importMap = OrderedDict() # type: dict[str, Module]
@property
def interfaces(self):
'''returns ordered list of interface symbols'''
return self._interfaceMap.values()
@property
def structs(self):
'''returns ordered list of struct symbols'''
return self._structMap.values()
@property
def enums(self):
'''returns ordered list of enum symbols'''
return self._enumMap.values()
@property
def imports(self):
'''returns ordered list of import symbols'''
return self._importMap.values()
def checkType(self, type: str):
if type.is_primitive:
return True
(module_name, type_name, fragment_name) = System.split_typename(type.name)
if module_name and module_name not in self._importMap:
return False
return True
@property
def name_parts(self):
'''return module name splitted by '.' in parts'''
return self.name.split('.')
@property
def majorVersion(self):
""" returns the major version number of the version information """
return self.version.split('.')[0]
@property
def minorVersion(self):
""" returns the minor version number of the version information """
return self.version.split('.')[1]
@property
def module_name(self):
""" returns the last part of the module uri """
return self.name.split('.')[-1]
[docs] def lookup(self, name: str, fragment: str = None):
'''lookup a symbol by name. If symbol is not local
it will be looked up system wide'''
if name in self._contentMap:
symbol = self._contentMap[name]
if fragment:
return symbol._contentMap[fragment]
return symbol
return self.system.lookup(name)
def toJson(self):
o = super().toJson()
o['version'] = self.version
o['interfaces'] = [s.toJson() for s in self.interfaces]
o['structs'] = [s.toJson() for s in self.structs]
o['enums'] = [s.toJson() for s in self.enums]
return o
[docs]class Interface(Symbol):
"""A interface is an object with operations, properties and signals"""
def __init__(self, name: str, module: Module):
super().__init__(name, module)
log.debug('Interface()')
self.module._interfaceMap[name] = self
self._propertyMap = OrderedDict() # type: dict[str, Property]
self._operationMap = OrderedDict() # type: dict[str, Operation]
self._signalMap = OrderedDict() # type: dict[str, Signal]
self._contentMap = ChainMap(self._propertyMap, self._operationMap, self._signalMap)
self._extends = None
@property
def properties(self):
'''returns ordered list of properties'''
return self._propertyMap.values()
@property
def operations(self):
'''returns ordered list of operations'''
return self._operationMap.values()
@property
def signals(self):
'''returns ordered list of signals'''
return self._signalMap.values()
@property
def extends(self):
''' returns the symbol defined by the extends interface attribute '''
return self.module.lookup(self._extends)
def toJson(self):
o = super().toJson()
o['properties'] = [s.toJson() for s in self.properties]
o['operations'] = [s.toJson() for s in self.operations]
o['signals'] = [s.toJson() for s in self.signals]
return o
[docs]class Operation(Symbol):
"""An operation inside a interface"""
def __init__(self, name: str, interface: Interface):
super().__init__(name, interface.module)
log.debug('Operation()')
self.interface = interface
""" the interface the operation is part of """
self.interface._operationMap[name] = self
self._parameterMap = self._contentMap = OrderedDict() # type: dict[Parameter]
self.is_const = False # type: bool
"""reflects is the operation was declared as const operation"""
@property
def qualified_name(self):
'''return the fully qualified name (`<module>.<interface>#<operation>`)'''
return '{0}.{1}#{2}'.format(self.module.name, self.interface.name, self.name)
@property
def parameters(self):
'''returns ordered list of parameters'''
return self._parameterMap.values()
def toJson(self):
o = super().toJson()
o['parameters'] = [s.toJson() for s in self.parameters]
o['type'] = self.type.toJson()
return o
class Signal(Symbol):
"""A signal inside an interface"""
def __init__(self, name: str, interface: Interface):
super().__init__(name, interface.module)
log.debug('Signal()')
self.interface = interface
self.interface._signalMap[name] = self
self._parameterMap = self._contentMap = OrderedDict() # type: dict[Parameter]
@property
def qualified_name(self):
'''return the fully qualified name (`module + "." + name`)'''
return '{0}.{1}#{2}'.format(self.module.name, self.interface.name, self.name)
@property
def parameters(self):
'''returns ordered list of parameters'''
return self._parameterMap.values()
def toJson(self):
o = super().toJson()
o['parameters'] = [s.toJson() for s in self.parameters]
return o
[docs]class Parameter(Symbol):
"""An operation parameter"""
def __init__(self, name: str, operation: Operation):
super().__init__(name, operation.module)
log.debug('Parameter()')
self.operation = operation
self.operation._parameterMap[name] = self
[docs]class Property(Symbol):
"""A typed property inside a interface"""
def __init__(self, name: str, interface: Interface):
super().__init__(name, interface.module)
log.debug('Property()')
self.interface = interface
self.interface._propertyMap[name] = self
self.readonly = False
self.const = False
@property
def is_model(self):
''' true if type is a model '''
return self.type.is_model
@property
def is_primitive_model(self):
''' true if type is a model of nested primitive types '''
return self.type.is_model and self.type.nested.is_primitive
@property
def is_complex_model(self):
''' true if type is a model of nested complex types '''
return self.type.is_model and self.type.nested.is_complex
@property
def qualified_name(self):
'''return the fully qualified name (`<module>.<interface>#<property>`)'''
return '{0}.{1}#{2}'.format(self.module.name, self.interface.name, self.name)
@property
def writeable(self):
return not self.readonly and not self.const
def toJson(self):
o = super().toJson()
if self.readonly:
o['readonly'] = True
if self.const:
o['const'] = True
return o
[docs]class Struct(Symbol):
"""Represents a data container"""
def __init__(self, name: str, module: Module):
super().__init__(name, module)
log.debug('Struct()')
self.module._structMap[name] = self
self._fieldMap = self._contentMap = OrderedDict()
@property
def fields(self):
'''returns ordered list of members'''
return self._fieldMap.values()
def toJson(self):
o = super().toJson()
o['fields'] = [s.toJson() for s in self.fields]
return o
[docs]class Field(Symbol):
"""A member in a struct"""
def __init__(self, name: str, struct: Struct):
super().__init__(name, struct.module)
log.debug('Field()')
self.struct = struct # type:Struct
self.struct._fieldMap[name] = self
@property
def qualified_name(self):
'''return the fully qualified name (`<module>.<struct>#<field>`)'''
return '{0}.{1}#{2}'.format(self.module.name, self.struct.name, self.name)
[docs]class Enum(Symbol):
"""An enum (flag) inside a module"""
def __init__(self, name: str, module: Module):
super().__init__(name, module)
log.debug('Enum()')
self.is_enum = True
self.is_flag = False
self.module._enumMap[name] = self
self._memberMap = self._contentMap = OrderedDict() # type: dict[EnumMember]
@property
def members(self):
'''returns ordered list of members'''
return self._memberMap.values()
def toJson(self):
o = super().toJson()
if self.is_enum:
o['enum'] = self.is_enum
if self.is_flag:
o['flag'] = self.is_flag
o['members'] = [s.toJson() for s in self.members]
return o
[docs]class EnumMember(Symbol):
"""A enum value"""
def __init__(self, name: str, enum: Enum):
super().__init__(name, enum.module)
log.debug('EnumMember()')
self.enum = enum
self.enum._memberMap[name] = self
self.value = 0
[docs] def qualified_name(self):
'''return the fully qualified name (`<module>.<enum>#<member>`)'''
return '{0}.{1}#{2}'.format(self.module.name, self.enum.name, self.name)
def toJson(self):
o = super().toJson()
o['value'] = self.value
return o