| |
| 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) |
|
|