Ejemplo n.º 1
0
class Archiver(ContextManager['Archiver']):
    def __init__(self, archive_path: str, src_dir: str) -> None:
        self.zipfile = ZipFile(archive_path, 'w', ZIP_LZMA)
        self.src_dir = src_dir
        self.added_paths = set()  # type: Set[str]

    def __enter__(self) -> 'Archiver':
        self.zipfile.__enter__()
        return self

    def __exit__(self, exc_type: Optional[Type[BaseException]],
                 exc_value: Optional[BaseException],
                 traceback: Optional[TracebackType]) -> Optional[bool]:
        self.zipfile.__exit__(exc_type, exc_value, traceback)
        return False

    def _resolve_src(self, src: str) -> str:
        src = src.replace('/', os.path.sep)
        return os.path.join(self.src_dir, src)

    def _resolve_dest(self, dest: str, src: str) -> str:
        if dest.endswith('/'):
            dest += os.path.basename(src)
        return dest

    def _add(self, src: str, dest: str) -> None:
        print(f'{src} -> {dest}')
        if dest not in self.added_paths:
            self.added_paths.add(dest)
            self.zipfile.write(src, dest)

    def add_static(self, dest: str, src: str) -> None:
        src = self._resolve_src(src)
        dest = self._resolve_dest(dest, src)
        self._add(src, dest)

    def add_project_output(self, dest: str, project: str) -> None:
        # TODO: netcoreapp2.0?
        bin_dir = os.path.join(self._resolve_src(project), 'bin', 'Release',
                               'net471')
        assert os.path.exists(bin_dir), 'missing bin dir: %s' % bin_dir
        for pattern in ('*.exe', '*.dll'):
            for this_src in glob.iglob(os.path.join(bin_dir, pattern),
                                       recursive=True):
                this_dest = self._resolve_dest(dest, this_src)
                self._add(this_src, this_dest)

    def add_glob(self, dest: str, pattern: str) -> None:
        pattern = self._resolve_src(pattern)
        for this_src in glob.iglob(pattern, recursive=True):
            this_dest = self._resolve_dest(dest, this_src)
            self._add(this_src, this_dest)
Ejemplo n.º 2
0
class AIAFile(object):
    """
    :py:class:`AIAFile` encapsulates an App Inventor project (AIA) file.

    Opens an App Inventor project (AIA) with the given filename. ``filename`` can be any file-like object that is
    acceptable to :py:class:`ZipFile`'s constructor, or a path to a directory containing an unzipped project.

    Parameters
    ----------
    filename : basestring | file
        A string or file-like containing the contents of an App Inventor project.
    strict : bool, optional
        Process the AIAFile in strict mode, i.e., if a blocks file is missing then it is an error. Default: false
    """
    def __init__(self, filename, strict=False):
        if not isinstance(filename, str) or (not isdir(filename)
                                             and filename[-4:] == '.aia'):
            self.zipfile = ZipFile(filename)
        else:
            self.zipfile = None

        self.assets = []
        """
        A list of assets contained in the project.

        :type: list[aiatools.aia.AIAAsset]
        """

        self.filename = filename
        """
        The filename or file-like that is the source of the project.

        :type: basestring or file
        """

        self.properties = {}
        """
        The contents of the project.properties file.

        :type: Properties
        """

        self._screens = NamedCollection()
        self.screens = Selector(self._screens)
        """
        A :py:class:`~aiatools.selectors.Selector` over the components of type
        :py:class:`~aiatools.component_types.Screen` in the project.

        For example, if you want to know how many screens are in a project run:

        .. doctest::

            >>> with AIAFile('test_aias/LondonCholeraMap.aia') as aia:
            ...     len(aia.screens)
            1

        :type: aiatools.selectors.Selector[aiatools.component_types.Screen]
        """

        self.components = UnionSelector(self._screens, 'components')
        """
        A :py:class:`~aiatools.selectors.Selector` over the component instances of all screens defined in the project.

        For example, if you want to know how many component instances are in a project run:

        .. doctest::

            >>> with AIAFile('test_aias/LondonCholeraMap.aia') as aia:
            ...     len(aia.components())  # Form, Map, Marker, Button
            4

        :type: aiatools.selectors.Selector[aiatools.common.Component]
        """

        self.blocks = UnionSelector(self._screens, 'blocks')
        """
        A :py:class:`~aiatoools.selectors.Selector` over the blocks of all screen defined in the project.

        For example, if you want to know how many blocks are in a project run:

        .. doctest::

            >>> with AIAFile('test_aias/LondonCholeraMap.aia') as aia:
            ...     len(aia.blocks())
            23

        :type: aiatools.selectors.Selector[aiatools.common.Block]
        """

        if self.zipfile:
            self._process_zip(strict)
        else:
            self._process_dir(strict)

    def close(self):
        if self.zipfile:
            self.zipfile.close()
        self.zipfile = None

    def __enter__(self):
        if self.zipfile:
            self.zipfile.__enter__()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.zipfile:
            self.zipfile.__exit__(exc_type, exc_val, exc_tb)

    def _listfiles(self):
        names = []
        for dirname, dirs, files in os.walk(self.filename):
            names.extend([join(dirname, f) for f in files])

        return names

    def _process_zip(self, strict):
        """
        Processes the contents of an AIA file into Python objects for further operation.
        """
        self.assets = []
        for name in self.zipfile.namelist():
            if name.startswith('assets/'):
                self.assets.append(AIAAsset(self, name))
                # TODO(ewpatton): Need to load extension JSON to extend language model
            elif name.startswith('src/'):
                if name.endswith('.scm'):
                    name = name[:-4]
                    form = self.zipfile.open('%s.scm' % name, 'r')
                    try:
                        blocks = self.zipfile.open('%s.bky' % name, 'r')
                    except KeyError as e:
                        if strict:
                            raise e
                        else:
                            blocks = None  # older aia without a bky file
                    screen = Screen(form=form, blocks=blocks)
                    self._screens[screen.name] = screen
            elif name.endswith('project.properties'):
                with self.zipfile.open(name) as prop_file:
                    self.properties = jprops.load_properties(prop_file)
            else:
                log.warning('Ignoring file in AIA: %s' % name)

    def _process_dir(self, strict):
        """
        Processes the contents of a directory as if it were an AIA file and converts the content into Python objects
        for further operation.
        """
        self.assets = []
        asset_path = join(self.filename, 'assets')
        src_path = join(self.filename, 'src')
        for name in self._listfiles():
            if name.startswith(asset_path):
                self.assets.append(AIAAsset(None, name))
                # TODO(ewpatton): Need to load extension JSON to extend language model
            elif name.startswith(src_path) or name.endswith(
                    '.scm') or name.endswith('.bky'):
                if name.endswith('.scm'):
                    name = name[:-4]
                    if strict and not os.path.exists('%s.bky' % name):
                        raise IOError(
                            'Did not find expected blocks file %s.bky' % name)
                    bky_handle = open('%s.bky' % name) if os.path.exists(
                        '%s.bky' % name) else StringIO('<xml/>')
                    with open('%s.scm' % name,
                              'r') as form, bky_handle as blocks:
                        screen = Screen(form=form, blocks=blocks)
                        self._screens[screen.name] = screen
            elif name.endswith('project.properties'):
                with open(name, 'r') as prop_file:
                    self.properties = jprops.load_properties(prop_file)
            else:
                log.warning('Ignoring file in directory: %s' % name)