diff --git a/README.rst b/README.rst index 6e38585..3256cf4 100644 --- a/README.rst +++ b/README.rst @@ -256,6 +256,13 @@ LIBRARY The name of the Basic library. Default to the same Run he specific macro after synchronization, for convenience. +--user Store the macros in the user macro storage. (default) + +--doc Store the macros in the document macro storage. + +--target TARGET The target storage document if there are more than one + opened documents. A partial path is OK. + -h, --help Show the help message and exit -v, --version Show program’s version number and exit @@ -264,7 +271,7 @@ LIBRARY The name of the Basic library. Default to the same COPYRIGHT --------- - Copyright (c) 2016 imacat. + Copyright (c) 2016-2017 imacat. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/bin/obasync b/bin/obasync index c797d65..566bb19 100755 --- a/bin/obasync +++ b/bin/obasync @@ -3,7 +3,7 @@ # Office Basic macro source synchronizer. # by imacat , 2016-08-31 -# Copyright (c) 2016 imacat. +# Copyright (c) 2016-2017 imacat. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ LIBRARY The name of the Basic library. Default to the same OpenOffice/LibreOffice. The default is 2002. You can change it if port 2002 is already in use. --x, --ext .EXT The file name extension of the source files. The +-x, --ext .EXT The file name extension of the source files. The default is ".vb". This may be used for your convenience of editor syntax highlighting. @@ -86,6 +86,13 @@ LIBRARY The name of the Basic library. Default to the same Run he specific macro after synchronization, for convenience. +--user Store the macros in the user macro storage. (default) + +--doc Store the macros in the document macro storage. + +--target TARGET The target storage document if there are more than one + opened documents. A partial path is OK. + -h, --help Show the help message and exit -v, --version Show program’s version number and exit @@ -133,9 +140,8 @@ def main(): # Downloads the macros from OpenOffice/LibreOffice Basic if args.get: oo = Office(args.port) - libraries = oo.service_manager.createInstance( - "com.sun.star.script.ApplicationScriptLibraryContainer") - modules = read_basic_modules(libraries, args.library) + storage = find_storage(oo, args.storage_type, args.target) + modules = read_basic_modules(storage, args.library) if len(modules) == 0: print("ERROR: Library %s does not exist" % args.library, file=sys.stderr) @@ -153,19 +159,10 @@ def main(): file=sys.stderr) return oo = Office(args.port) - libraries = oo.service_manager.createInstance( - "com.sun.star.script.ApplicationScriptLibraryContainer") - update_basic_modules(libraries, args.library, modules, oo) + storage = find_storage(oo, args.storage_type, args.target) + update_basic_modules(storage, args.library, modules) if args.run is not None: - factory = oo.service_manager.DefaultContext.getByName( - "/singletons/com.sun.star.script.provider." - "theMasterScriptProviderFactory") - provider = factory.createScriptProvider("") - script = provider.getScript( - "vnd.sun.star.script:%s.%s" - "?language=Basic&location=application" % - (args.library, args.run)) - script.invoke((), (), ()) + run_macro(storage, args.library, args.run) print("Done. %02d:%02d elapsed." % (int((time.time() - t_start) / 60), @@ -206,6 +203,18 @@ def parse_args(): parser.add_argument( "-r", "--run", metavar="MODULE.MACRO", help="The macro to run after the upload, if any.") + parser.add_argument( + "--user", dest="storage_type", + action="store_const", const="user", + help="Store the macros in the user macro storage. (default)") + parser.add_argument( + "--doc", dest="storage_type", + action="store_const", const="doc", + help="Store the macros in the document macro storage.") + parser.add_argument( + "--target", metavar="TARGET", + help=("The target storage document if there are more than one" + " opened documents. A partial path is OK.")) parser.add_argument( "-v", "--version", action="version", version="%(prog)s 0.4") args = parser.parse_args() @@ -219,9 +228,117 @@ def parse_args(): if args.ext[0] != ".": args.ext = "." + args.ext + if args.storage_type is None: + args.storage_type = "user" + # Paths are understood locally, despite of the content encoding. + if args.target is not None: + args.target = args.target.decode(locale.getpreferredencoding()) return +def find_storage(oo, type, target): + """Finds the macro storage to store the macros. + + Arguments: + type: The storage type, either "user" or "doc". + target: The file path to locate the storing document if there + are more than one opened documents. A partial path + is OK. + + Returns: + The storage to save the macros, as a Storage object. + """ + if type == "user": + storage = Storage() + storage.type = type + storage.oo = oo + storage.doc = None + storage.libs = oo.service_manager.createInstance( + "com.sun.star.script.ApplicationScriptLibraryContainer") + return storage + elif type == "doc": + storage = Storage() + storage.type = type + storage.oo = oo + storage.doc = find_doc(oo, target) + storage.libs = storage.doc.getPropertyValue("BasicLibraries") + return storage + else: + return None + + +def find_doc(oo, target): + """Find the target opened document by a partial path. + + Arguments: + target: A partial path of the document. + + Returns: + If there is only one opened document, it is returned. If + there are more than one opened document, the document whose + file path matches the "target" is returned. Otherwise, if + no matching document or more than one matching document are + found, the program exists with an error. + """ + # Checks the opened documents + enum = oo.desktop.getComponents().createEnumeration() + opened = [] + while enum.hasMoreElements(): + component = enum.nextElement() + if component.supportsService( + "com.sun.star.document.OfficeDocument"): + opened.append(component) + if len(opened) == 0: + print("ERROR: Found no opened document to store the macros", + file=sys.stderr) + sys.exit(1) + # There are opened documents. + if target is None: + if len(opened) == 1: + return opened[0] + print("ERROR: There are more than one opened documens." + " Please specify the file path.", + file=sys.stderr) + sys.exit(1) + file_content_provider = oo.service_manager.createInstance( + "com.sun.star.ucb.FileContentProvider") + matched = [] + for doc in opened: + if doc.hasLocation(): + path = file_content_provider.getSystemPathFromFileURL( + doc.getLocation()) + else: + path = doc.getTitle() + if path.find(target) >= 0: + matched.append(doc) + if len(matched) == 1: + return matched[0] + elif len(matched) == 0: + print("ERROR: Found no matching document to store the macros.", + file=sys.stderr) + print("Opened documents:", file=sys.stderr) + for doc in opened: + if doc.hasLocation(): + path = file_content_provider.getSystemPathFromFileURL( + doc.getLocation()) + else: + path = doc.getTitle() + print("* %s" % path, file=sys.stderr) + sys.exit(1) + else: + print("ERROR: There are more than one matching documents.", + file=sys.stderr) + print("Matching documents:", file=sys.stderr) + for doc in matched: + if doc.hasLocation(): + path = file_content_provider.getSystemPathFromFileURL( + doc.getLocation()) + else: + path = doc.getTitle() + print("* %s" % path, file=sys.stderr) + sys.exit(1) + + def read_in_source_dir(projdir, ext, encoding): """Read-in the source files. @@ -289,15 +406,13 @@ def update_source_dir(projdir, modules, ext, encoding): return -def read_basic_modules(libraries, libname): +def read_basic_modules(storage, libname): """ Read the OpenOffice/LibreOffice Basic macros from the macro storage. Arguments: - libraries: The Basic library storage, as a - com.sun.star.script.ApplicationScriptLibraryContainer - service object. + storage: The Basic macro storage, as a Storage object. libname: The name of the Basic library. Returns: @@ -306,41 +421,44 @@ def read_basic_modules(libraries, libname): dictionary are the module contents. """ modules = {} - if not libraries.hasByName(libname): + if not storage.libs.hasByName(libname): return modules - libraries.loadLibrary(libname) - library = libraries.getByName(libname) + storage.libs.loadLibrary(libname) + library = storage.libs.getByName(libname) for modname in library.getElementNames(): modules[modname] = library.getByName(modname) return modules -def update_basic_modules(libraries, libname, modules, oo): +def update_basic_modules(storage, libname, modules): """Update the OpenOffice/LibreOffice Basic macro storage. Arguments: - libraries: The Basic library storage, as a - com.sun.star.script.ApplicationScriptLibraryContainer - service object. + storage: The Basic macro storage, as a Storage object. libname: The name of the Basic library. modules: The Basic modules, as a dictionary. The keys of the dictionary are the module names, and the values of the dictionary are the module contents. - oo: The OpenOffice/LibreOffice connection, as an Office - object. """ - if not libraries.hasByName(libname): - libraries.createLibrary(libname) + if not storage.libs.hasByName(libname): + storage.libs.createLibrary(libname) print("Script library %s created." % libname, file=sys.stderr) - create_dialog_library(oo, libname) - library = libraries.getByName(libname) + create_dialog_library(storage, libname) + library = storage.libs.getByName(libname) for modname in sorted(modules.keys()): library.insertByName(modname, modules[modname]) print("Module %s added." % modname, file=sys.stderr) else: - libraries.loadLibrary(libname) - library = libraries.getByName(libname) - curmods = sorted(library.getElementNames()) + storage.libs.loadLibrary(libname) + library = storage.libs.getByName(libname) + # As of OpenOffice 4.1.3, when there is no modules in the + # document storage, it returns a zero-length byte sequence + # instead of an empty string list. + # The byte sequence cannot be sorted directly. + curmods = library.getElementNames() + if len(curmods) == 0: + curmods = [] + curmods = sorted(curmods) for modname in sorted(modules.keys()): if modname not in curmods: library.insertByName(modname, modules[modname]) @@ -352,28 +470,57 @@ def update_basic_modules(libraries, libname, modules, oo): if modname not in modules: library.removeByName(modname) print("Module %s removed." % modname, file=sys.stderr) - if libraries.isModified(): - libraries.storeLibraries() + if storage.libs.isModified(): + storage.libs.storeLibraries() else: print("Everything is in sync.", file=sys.stderr) return -def create_dialog_library(oo, libname): +def create_dialog_library(storage, libname): """Create the dialog library. Arguments: - oo: The OpenOffice/LibreOffice connection, as an Office - object. + storage: The Basic macro storage, as a Storage object. libname: The name of the dialog library. """ - libraries = oo.service_manager.createInstance( - "com.sun.star.script.ApplicationDialogLibraryContainer") + if storage.type is "user": + libraries = storage.oo.service_manager.createInstance( + "com.sun.star.script.ApplicationDialogLibraryContainer") + else: + libraries = storage.doc.getPropertyValue("DialogLibraries") libraries.createLibrary(libname) print("Dialog library %s created." % libname, file=sys.stderr) libraries.storeLibraries() +def run_macro(storage, libname, macro): + """Run a Basic macro. + + Arguments: + storage: The Basic macro storage, as a Storage object. + libname: The name of the dialog library. + macro: The The macro to run. + """ + if storage.type is "user": + factory = storage.oo.service_manager.DefaultContext.getByName( + "/singletons/com.sun.star.script.provider." + "theMasterScriptProviderFactory") + provider = factory.createScriptProvider("") + script = provider.getScript( + "vnd.sun.star.script:%s.%s" + "?language=Basic&location=application" % + (args.library, args.run)) + script.invoke((), (), ()) + else: + provider = storage.doc.getScriptProvider() + script = provider.getScript( + "vnd.sun.star.script:%s.%s" + "?language=Basic&location=document" % + (args.library, args.run)) + script.invoke((), (), ()) + + def read_file(path, encoding): """Read a file, and deals with Python 3 / 2 compatibility. @@ -448,6 +595,17 @@ def update_file(path, content, encoding): return is_updated +class Storage: + """A Basic macro storage. + + Attributes: + oo: The office connection, as an Office() object. + doc: The office document component to store the macros. + libs: The Basic macro storage of the document. + """ + pass + + class Office: """The OpenOffice/LibreOffice connection. @@ -588,7 +746,7 @@ class Office: LibreOffice on POSIX systems accepts "--accept" instead of "-accept" now. - + Returns: True if soffice is LibreOffice, or False otherwise. """ diff --git a/setup.py b/setup.py index c5b2f63..ee292fe 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ # Python setuptools installer for the obasync project. # by imacat , 2016-12-20 -# Copyright (c) 2016 imacat. +# Copyright (c) 2016-2017 imacat. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License.