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