Source code for aiatools.component_types

# -*- mode: python; coding: utf-8 -*-
# Copyright © 2017 Massachusetts Institute of Technology, All rights reserved.

"""
The :py:mod:`aiatools.component_types` module defines the components for App Inventor. Component types are
programmatically constructed during module compilation from the simple_components.json used to populate App Inventor's
online development environment.
"""

import json
import xml.etree.ElementTree as ETree

import pkg_resources

from aiatools.selectors import Selector, NamedCollection, Selectors
from .common import *

__author__ = 'Evan W. Patton <ewpatton@mit.edu>'


# noinspection PyShadowingBuiltins
[docs]class ComponentContainer(Component, Selectors): """ :py:class:`ComponentContainer` models the App Inventor ComponentContainer class. Parameters: parent (ComponentContainer, optional): The parent of the container. May be None for :py:class:`.Screen`. uuid (basestring): The UUID for the component container. type (str): The type of the component container. name (basestring): The name of the component container. version (str): The version number of the component container's type at the time the project was last saved. properties (dict[basestring, T], optional): The properties of the component as a dictionary. The values are dependent on the key (property). components (list[Component], optional): A list of components contained by the container. """ def __init__(self, parent, uuid, type, name, version, properties=None, components=None): super(ComponentContainer, self).__init__(parent, uuid, type, name, version, properties) self._children = [] if components is None else list(components) def __iter__(self): """ Iterate over the children of the container. :return: An iterator over the container's children. :rtype: collections.Iterable[Component] """ return iter(self._children)
[docs] def itervalues(self): """ Iterate over the values of this container. For :py:class:`ComponentContainer`, the values are the children of the container. :return: Iterator over the container's children. :rtype: collections.Iterable[Component] """ return iter(self)
[docs] def children(self): """ Iterate over the child components in the container :return: An iterator over the components in the container. :rtype: collections.Iterable[Component] .. versionadded:: 0.1 """ return self._children
def _components(self, *args, **kwargs): items = {item.id: item for item in RecursiveIterator(self)} items[self.id] = self return Selector(NamedCollection(items)).components(*args, **kwargs)
[docs] @classmethod def from_json(cls, parent, json_repr): """ Constructs a :py:class:`ComponentContainer` :param parent: The parent container of the new container. :type parent: ComponentContainer :param json_repr: The JSON representation of the component from the Screen definition. :type json_repr: dict[str, T] :return: A newly constructed ComponentContainer from the JSON representation. :rtype: ComponentContainer """ typename = json_repr['$Type'] if typename in globals(): type = globals()[typename] else: type = Extension(typename) properties = {k: v for k, v in json_repr.items() if k not in Component._DISALLOWED_KEYS} container = cls(parent, json_repr['Uuid'], type, json_repr['$Name'], json_repr['$Version'], properties) for component_description in json_repr['$Components']: if '$Components' in component_description: child = ComponentContainer.from_json(container, component_description) else: child = Component.from_json(container, component_description) container._children.append(child) return container
components = property(_components, doc=""" Returns a :py:class:`~aiatools.selectors.Selector` over the components in the container. :type: Selector[Component] """)
[docs]class Screen(ComponentContainer): """ The :py:class:`Screen` class provides a Python representation of an App Inventor Screen. The Screen object encapsulates both its descendant components and the blocks code prescribing the behavior of the Screen's contents. Parameters ---------- name : basestring, optional The name of the screen. components : list[Component], optional A list of immediate components that are the children of the screen. form : string | file, optional A pathanme, string, or file-like that contains a Screen's Scheme (.scm) file. blocks : string | file, optional A pathname, string, or file-like that contains a Screen's Blocks (.bky) file. """ def __init__(self, name=None, components=None, form=None, blocks=None): self.uuid = 0 self.name = name self.path = name self._children = components self._blocks = NamedCollection() self.ya_version = None self.blocks_version = None if form is not None: if isinstance(form, str): form_json = json.loads(form) else: form_contents = [line.decode('utf-8') if hasattr(line, 'decode') else line for line in form.readlines()] if form_contents[1] != '$JSON\n' and form_contents[1] != b'$JSON\n': raise RuntimeError('Unknown Screen format: %s' % form_contents[1]) form_json = json.loads(form_contents[2]) self.name = name or form_json['Properties']['$Name'] super(Screen, self).__init__(parent=None, uuid=form_json['Properties']['Uuid'], type=Form, name=self.name, version=form_json['Properties']['$Version'], components=components) self._process_components_json(form_json['Properties']['$Components'] if '$Components' in form_json['Properties'] else []) self.properties = { key: value for key, value in form_json.items() if key not in Component._DISALLOWED_KEYS } self.ya_version = int(form_json['YaVersion']) else: super(Screen, self).__init__(None, '0', Form, name or 'Screen1', '20', components=components) self.id = self.name if isinstance(blocks, str): blocks_content = blocks else: blocks_content = None if blocks is None else blocks.read() blocks_content = blocks_content if blocks_content and len(blocks_content) > 0 else None xml_root = None if blocks_content is None else ETree.fromstring(blocks_content) if xml_root is not None: for child in xml_root: if child.tag.endswith('yacodeblocks'): self.blocks_version = int(child.attrib['language-version']) self.ya_version = max(self.ya_version, int(child.attrib['ya-version'])) else: block = Block.from_xml(self, child, self.blocks_version) self._blocks[block.id] = block self.blocks = Selector(self._blocks) def _process_components_json(self, components): self._children = [] for component_description in components: if '$Components' in component_description: # component container component = ComponentContainer.from_json(self, component_description) else: component = Component.from_json(self, component_description) self._children.append(component) def __iter__(self): for child in RecursiveIterator(self): yield child for child in self._blocks.values(): yield child def __repr__(self): return "Screen(%s)" % repr(self.name) def __str__(self): return self.name
[docs]def list_to_dict(iterable, key='name'): return {i[key]: i for i in iterable}
def _load_component_types(): """ Loads the descriptions of App Inventor components from simple_components.json and populates the module with instances of ComponentType for each known type. """ with open(pkg_resources.resource_filename('aiatools', 'simple_components.json')) as _f: _component_descriptors = json.load(_f) for _descriptor in _component_descriptors: _methods = list_to_dict(_descriptor['methods']) _events = list_to_dict(_descriptor['events']) _properties = list_to_dict(_descriptor['properties']) for _prop in _descriptor['blockProperties']: if _prop['name'] in _properties: _properties[_prop['name']].update(_prop) else: _properties[_prop['name']] = _prop _prop['editorType'] = None _prop['defaultValue'] = None _component = Screen if _descriptor == 'Form' else \ ComponentType(_descriptor['name'], _methods, _events, _properties) _component.type = _descriptor['type'] _component.external = bool(_descriptor['external']) _component.version = int(_descriptor['version']) _component.category_string = _descriptor['categoryString'] _component.help_string = _descriptor['helpString'] _component.show_on_palette = bool(_descriptor['showOnPalette']) _component.visible = not bool(_descriptor['nonVisible']) _component.icon_name = _descriptor['iconName'] globals()[_component.name] = _component Component.TYPES[_component.name] = _component _load_component_types()