| | |
| | from __future__ import with_statement |
| | import logging |
| | import os.path |
| | from storage import open_storage |
| | from storage import resolve_path |
| | from storage import makedirs_to_file |
| | from storage import put_file |
| | from storage import copy_file |
| | from storage import iterate_files_recursively |
| | from storage._zipfile import ZipFileStorage |
| | from storage.fs import FileSystemStorage |
| | from manifest import Manifest |
| | from description import Description |
| |
|
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| |
|
| | def is_package(folder): |
| | if 'META-INF' not in folder: |
| | return False |
| | if 'manifest.xml' not in folder['META-INF']: |
| | return False |
| | return True |
| |
|
| |
|
| | MANIFEST_PATH = os.path.join('META-INF', 'manifest.xml') |
| | DESCRIPTION_PATH = 'description.xml' |
| |
|
| |
|
| | def add_file(stg, manifest, path, full_path, media_type): |
| | ''' add a file into the storage and manifest. |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | :param path: path to the file on the filesystem. |
| | :param full_path: ``manifest:full-path`` value of ``manifest:file-entry`` |
| | :param media_type: ``manifest:media-type`` value of ``manifest:file-entry`` |
| | ''' |
| | node = makedirs_to_file(stg, full_path) |
| | put_file(node, path) |
| | manifest[full_path] = media_type |
| | return node |
| |
|
| |
|
| | def add_component_file(stg, manifest, path, full_path, type, platform=None): |
| | ''' add a component file. |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | :param path: path to the file on the filesystem. |
| | :param full_path: ``manifest:full-path`` value of ``manifest:file-entry`` |
| | :param type: ``native``, ``Java``, ``Python`` or None |
| | :param platform: supposed platform to run this component. |
| | |
| | if ``type`` is None, this component is meant to be registered with |
| | `Passive Component Registration |
| | <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/Passive_Component_Registration>`_ |
| | and the file specified with ``path`` should be an XML file, which is |
| | defined in the document above. |
| | |
| | For more informations, see `File Format |
| | <http://wiki.openoffice.org/wiki/Documentation/DevGuide/Extensions/File_Format>`_. |
| | ''' |
| | mimetype = 'application/vnd.sun.star.uno-component' |
| | if type: |
| | mimetype += '; ' + type |
| | if platform: |
| | mimetype += '; ' + platform |
| | return add_file(stg, manifest, path, full_path, mimetype=mimetype) |
| |
|
| |
|
| | def add_type_library(stg, manifest, path, full_path, type): |
| | ''' add a UNO type library. |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | :param type: ``RDB`` or ``Java`` |
| | ''' |
| |
|
| | typelib_extensions = dict(RDB='.rdb', Java='.jar') |
| |
|
| | if type not in typelib_extensions.keys(): |
| | raise ValueError('type: unsupported value of %r' % type) |
| |
|
| | if not full_path.lower().endswith(typelib_extensions[type]): |
| | msg = 'adding %r type library %r with name %r: really intended?' |
| | logger.warning(msg, type, path, full_path) |
| |
|
| | mimetype = 'application/vnd.sun.star.uno-typelibrary' |
| | mimetype += '; type=' + type |
| | return add_file(stg, manifest, path, full_path, mimetype) |
| |
|
| |
|
| | def add_basic_library(stg, manifest, path, full_path): |
| | ''' add a basic library |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | ''' |
| | mimetype = 'application/vnd.sun.star.basic-library' |
| | return add_file(stg, manifest, path, full_path, mimetype=mimetype) |
| |
|
| |
|
| | def add_dialog_library(stg, manifest, path, full_path): |
| | ''' add a dialog library |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | ''' |
| | mimetype = 'application/vnd.sun.star.dialog-library' |
| | return add_file(stg, manifest, path, full_path, mimetype=mimetype) |
| |
|
| |
|
| | def add_configuration_data_file(stg, manifest, path, full_path): |
| | ''' add a configuration data file. |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | ''' |
| | mimetype = 'application/vnd.sun.star.configuration-data' |
| | return add_file(stg, manifest, path, full_path, mimetype=mimetype) |
| |
|
| |
|
| | def add_configuration_schema_file(stg, manifest, path, full_path): |
| | ''' add a configuration schema file. |
| | |
| | :param stg: a storage |
| | :param manifest: an instance of Manifest |
| | ''' |
| | mimetype = 'application/vnd.sun.star.configuration-schema' |
| | return add_file(stg, manifest, path, full_path, mimetype=mimetype) |
| |
|
| |
|
| | def build(package_path, manifest, description, files=dict(), |
| | storage_factory=ZipFileStorage): |
| | ''' Build a OXT Package. |
| | |
| | :param package_path: path to an .oxt package to be built |
| | :param manifest: an instance of Manifest |
| | :param description: an instance of Description |
| | :param files: package files, in a form of (path, node) dict |
| | :param storage_factory: storage factory for the package. |
| | Default to ZipFileStorage |
| | ''' |
| |
|
| | assert not any(node is None for node in files.values()) |
| | assert all(path in files for path in manifest) |
| | assert all(path in files for path in description.required_files()) |
| |
|
| | logger.info('creating %s', package_path) |
| | with storage_factory(package_path, 'w') as stg: |
| | logger.info('writing %s', MANIFEST_PATH) |
| | manifest_node = makedirs_to_file(stg, MANIFEST_PATH) |
| | with manifest_node.open('w') as f: |
| | manifest.dump(f) |
| |
|
| | logger.info('writing %s', DESCRIPTION_PATH) |
| | desc_node = makedirs_to_file(stg, DESCRIPTION_PATH) |
| | with desc_node.open('w') as f: |
| | description.write(f) |
| |
|
| | for path in sorted(files): |
| | node = files[path] |
| | logger.info('writing %s', path) |
| | dest = makedirs_to_file(stg, path) |
| | copy_file(node, dest) |
| |
|
| |
|
| | def build_from(package_path, |
| | src_folder, |
| | manifest_path=None, |
| | description_path=None, |
| | files=[], |
| | excludes=[], |
| | storage_factory=ZipFileStorage): |
| |
|
| | if manifest_path: |
| | with file(manifest_path) as f: |
| | manifest = Manifest() |
| | manifest.load(f) |
| | else: |
| | node = resolve_path(src_folder, MANIFEST_PATH) |
| | if node: |
| | with node.open() as f: |
| | manifest = Manifest() |
| | manifest.load(f) |
| | else: |
| | logger.error('%s: not found' % MANIFEST_PATH) |
| | raise IOError('%s: not found' % MANIFEST_PATH) |
| |
|
| | if description_path: |
| | with file(description_path) as f: |
| | description = Description.parse(f) |
| | else: |
| | node = resolve_path(src_folder, DESCRIPTION_PATH) |
| | if node: |
| | with node.open() as f: |
| | description = Description.parse(f) |
| | else: |
| | raise IOError('%s: not found' % DESCRIPTION_PATH) |
| |
|
| | package_path = make_output_path(package_path, description) |
| | package_files = dict() |
| |
|
| | from itertools import chain |
| | required_files = chain(manifest, description.required_files()) |
| | for path in required_files: |
| | node = resolve_path(src_folder, path) |
| | if node is None: |
| | raise IOError('%s: not found' % path) |
| | package_files[path] = node |
| |
|
| | files = ((path, resolve_path(src_folder, path)) for path in files) |
| | files = expand_folders(files) |
| | files = exclude_files(excludes, files) |
| | package_files.update(files) |
| |
|
| | return build(package_path, manifest, description, package_files, |
| | storage_factory=storage_factory) |
| |
|
| |
|
| | def make_output_path(path, desc=None): |
| | if os.path.isdir(path): |
| | dirname = path |
| | name = '' |
| | else: |
| | dirname, name = os.path.split(path) |
| |
|
| | |
| | if name == '': |
| | if desc is None: |
| | raise ValueError('%s: invalid path' % path) |
| | name = package_name_from_desc(desc) |
| |
|
| | return os.path.join(dirname, name) |
| |
|
| |
|
| | def package_name_from_desc(desc): |
| | id = desc.identifier |
| | version = desc.version |
| | if version: |
| | return '-'.join([id, version]) + '.oxt' |
| | else: |
| | return id + '.oxt' |
| |
|
| |
|
| | def expand_folders(resolved_nodes): |
| | for path, node in resolved_nodes: |
| | if hasattr(node, '__iter__'): |
| | for path, node in iterate_files_recursively(node, path): |
| | yield path, node |
| | else: |
| | yield path, node |
| |
|
| |
|
| | def exclude_files(patterns, resolved_nodes): |
| | from fnmatch import fnmatch |
| | for path, node in resolved_nodes: |
| | excluded = False |
| | for pat in patterns: |
| | if fnmatch(path, pat): |
| | logger.info('exclude %s (by %s)', path, pat) |
| | excluded = True |
| | if not excluded: |
| | yield path, node |
| |
|
| |
|
| | def init_main(): |
| | doc = '''Usage: oxt-pkg-init [options] <package-path> |
| | |
| | --help Print this screen. |
| | ''' |
| |
|
| | from docopt import docopt |
| | args = docopt(doc) |
| | logging.basicConfig(level=logging.INFO) |
| |
|
| | package_path = args['<package-path>'] |
| |
|
| | manifest = Manifest() |
| | description = Description() |
| |
|
| | with open_storage(package_path, 'w') as stg: |
| | with makedirs_to_file(stg, MANIFEST_PATH).open('w') as f: |
| | manifest.dump(f) |
| | with makedirs_to_file(stg, DESCRIPTION_PATH).open('w') as f: |
| | description.write(f) |
| |
|
| |
|
| | def show_main(): |
| | doc = '''Usage: oxt-pkg-show [options] <package-path> |
| | |
| | --help Print this screen. |
| | ''' |
| |
|
| | from docopt import docopt |
| | args = docopt(doc) |
| | logging.basicConfig(level=logging.INFO) |
| |
|
| | package_path = args['<package-path>'] |
| | with open_storage(package_path) as pkg: |
| | with resolve_path(pkg, MANIFEST_PATH).open() as f: |
| | manifest = Manifest() |
| | manifest.load(f) |
| |
|
| | with resolve_path(pkg, DESCRIPTION_PATH).open() as f: |
| | description = Description.parse(f) |
| |
|
| | from description import print_human_readable |
| | print_human_readable(description, pkg) |
| |
|
| | for path in manifest: |
| | item = manifest[path] |
| | print path, item['media-type'], |
| | node = resolve_path(pkg, path) |
| | if node: |
| | print '-- OK' |
| | else: |
| | print '-- MISSING' |
| |
|
| |
|
| | def build_main(): |
| | doc = '''Usage: oxt-pkg-build [options] <src-folder> <add-files>... |
| | |
| | -o OUTPUT-PATH Output path |
| | -m MANIFEST META-INF/manifest.xml |
| | -d DESCRIPT description.xml |
| | -E EXCLUDE, --exclude=EXCLUDE exclude patterns; separated by %r. |
| | --help Print this screen. |
| | |
| | <src-folder> root folder containing package files |
| | <add-files> additional files (relative to <src-folder>) |
| | ''' % os.pathsep |
| |
|
| | from docopt import docopt |
| | args = docopt(doc) |
| | logging.basicConfig(level=logging.INFO) |
| |
|
| | src_folder_path = args['<src-folder>'] |
| | add_files = args['<add-files>'] |
| | output_path = args['-o'] or '.' |
| | manifest_path = args['-m'] |
| | description_path = args['-d'] |
| | excludes = args['--exclude'] or '' |
| | excludes = excludes.strip().split(os.pathsep) |
| |
|
| | with FileSystemStorage(src_folder_path) as src_folder: |
| | build_from(output_path, |
| | src_folder, |
| | manifest_path=manifest_path, |
| | description_path=description_path, |
| | files=add_files, |
| | excludes=excludes) |
| |
|
| |
|
| | def check_main(): |
| | doc = '''Usage: oxt-pkg-show [options] <package-path> |
| | |
| | --help Print this screen. |
| | ''' |
| |
|
| | from docopt import docopt |
| | args = docopt(doc) |
| | logging.basicConfig(level=logging.INFO) |
| |
|
| | package_path = args['<package-path>'] |
| | with open_storage(package_path) as pkg: |
| | with resolve_path(pkg, MANIFEST_PATH).open() as f: |
| | manifest = Manifest() |
| | manifest.load(f) |
| |
|
| | with resolve_path(pkg, DESCRIPTION_PATH).open() as f: |
| | description = Description.parse(f) |
| |
|
| | missing = dict() |
| |
|
| | for path in manifest: |
| | node = resolve_path(pkg, path) |
| | if node is None: |
| | missing[path] = MANIFEST_PATH |
| |
|
| | for path in description.required_files(): |
| | node = resolve_path(pkg, path) |
| | if node is None: |
| | missing[path] = DESCRIPTION_PATH |
| |
|
| | if missing: |
| | for path in sorted(missing): |
| | referer = missing[path] |
| | logger.error('%s: MISSING (refered in %s)', |
| | path, referer) |
| | raise SystemExit(1) |
| | else: |
| | logger.info('%s: OK, identifier=%s, version=%s', package_path, |
| | description.identifier, description.version) |
| |
|