363 lines
12 KiB
Python
Executable File
363 lines
12 KiB
Python
Executable File
#! /opt/openoffice4/program/python
|
|
# -*- coding: utf-8 -*-
|
|
# Office Basic macro source synchronizer.
|
|
# by imacat <imacat@mail.imacat.idv.tw>, 2016-08-31
|
|
|
|
from __future__ import print_function
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
|
|
def append_uno_path():
|
|
""" Appends the path of the uno module to the import path. """
|
|
|
|
for p in sys.path:
|
|
if os.path.exists(os.path.join(p, "uno.py")):
|
|
return
|
|
# For uno.py on MacOS
|
|
cand = "/Applications/OpenOffice.app/Contents/MacOS"
|
|
if os.path.exists(os.path.join(cand, "uno.py")):
|
|
sys.path.append(cand)
|
|
return
|
|
# Finds uno.py for MS-Windows
|
|
cand = sys.executable
|
|
while cand != os.path.dirname(cand):
|
|
cand = os.path.dirname(cand)
|
|
if os.path.exists(os.path.join(cand, "uno.py")):
|
|
sys.path.append(cand)
|
|
return
|
|
|
|
append_uno_path()
|
|
import uno
|
|
from com.sun.star.connection import NoConnectException
|
|
|
|
|
|
def main():
|
|
""" The main program. """
|
|
t_start = time.time()
|
|
global args
|
|
|
|
# Parses the arguments
|
|
parse_args()
|
|
|
|
# Connects to the OpenOffice/LibreOffice
|
|
oo = Office(args.port)
|
|
|
|
# Synchronize the Basic macros.
|
|
sync_macros(oo, args.ext)
|
|
|
|
print("Done. %02d:%02d elapsed." %
|
|
(int((time.time() - t_start) / 60),
|
|
(time.time() - t_start) % 60), file=sys.stderr)
|
|
|
|
|
|
def parse_args():
|
|
""" Parses the arguments. """
|
|
global args
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description=("Synchronize the local Basic scripts"
|
|
" with OpenOffice/LibreOffice Basic."))
|
|
parser.add_argument(
|
|
"projdir", metavar="dir", nargs="?", default=os.getcwd(),
|
|
help=("The project source directory"
|
|
" (default to the current directory)."))
|
|
parser.add_argument(
|
|
"library", metavar="Library", nargs="?",
|
|
help=("The Library to upload/download the macros"
|
|
" (default to the name of the directory)."))
|
|
parser.add_argument(
|
|
"--get", action="store_true",
|
|
help="Downloads the macros instead of upload.")
|
|
parser.add_argument(
|
|
"-p", "--port", metavar="N", type=int, default=2002,
|
|
help=("The TCP port to communicate with "
|
|
"OpenOffice/LibreOffice with (default: %(default)s)"))
|
|
parser.add_argument(
|
|
"-e", "--ext", metavar=".EXT", default=".vb",
|
|
help=("The file name extension of the source files. "
|
|
"(default: %(default)s)"))
|
|
parser.add_argument(
|
|
"-r", "--run", metavar="Module.Macro",
|
|
help="The macro to run after the upload, if any.")
|
|
parser.add_argument(
|
|
"-v", "--version", action="version", version="%(prog)s 0.1")
|
|
args = parser.parse_args()
|
|
|
|
# Obtains the absolute path
|
|
args.projdir = os.path.abspath(args.projdir)
|
|
# Obtains the library name from the path
|
|
if args.library is None:
|
|
args.library = os.path.basename(args.projdir)
|
|
# Adjusts the file name extension.
|
|
if args.ext[0] != ".":
|
|
args.ext = "." + args.ext
|
|
|
|
return
|
|
|
|
|
|
def sync_macros(oo, ext):
|
|
""" Synchronize the Basic macros. """
|
|
global args
|
|
|
|
libraries = oo.service_manager.createInstance(
|
|
"com.sun.star.script.ApplicationScriptLibraryContainer")
|
|
# Downloads the macros from OpenOffice/LibreOffice Basic
|
|
if args.get:
|
|
modules = read_basic_modules(libraries, args.library)
|
|
if len(modules) == 0:
|
|
print("ERROR: Library %s does not exist" % args.library,
|
|
file=sys.stderr)
|
|
return
|
|
update_source_dir(args.projdir, modules, ext)
|
|
|
|
# Uploads the macros onto OpenOffice/LibreOffice Basic
|
|
else:
|
|
modules = read_in_source_dir(args.projdir, ext)
|
|
if len(modules) == 0:
|
|
print("ERROR: Found no source macros in %s" %
|
|
args.projdir,
|
|
file=sys.stderr)
|
|
return
|
|
update_basic_modules(libraries, args.library, modules, oo)
|
|
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((), (), ())
|
|
return
|
|
|
|
|
|
def read_in_source_dir(projdir, ext):
|
|
""" Reads-in the source macros. """
|
|
modules = {}
|
|
for entry in os.listdir(projdir):
|
|
path = os.path.join(projdir, entry)
|
|
if os.path.isfile(path) \
|
|
and entry.lower().endswith(ext.lower()):
|
|
modname = entry[0:-3]
|
|
modules[modname] = read_file(path)
|
|
return modules
|
|
|
|
|
|
def update_source_dir(projdir, modules, ext):
|
|
""" Updates the source macros. """
|
|
curmods = {}
|
|
is_in_sync = True
|
|
for entry in os.listdir(projdir):
|
|
path = os.path.join(projdir, entry)
|
|
if os.path.isfile(path) \
|
|
and entry.lower().endswith(ext.lower()):
|
|
modname = entry[0:-len(ext)]
|
|
curmods[modname] = entry
|
|
for modname in sorted(modules.keys()):
|
|
if modname not in curmods:
|
|
path = os.path.join(projdir, modname + ext)
|
|
write_file(path, modules[modname])
|
|
print("%s added." % (modname + ext), file=sys.stderr)
|
|
is_in_sync = False
|
|
else:
|
|
path = os.path.join(projdir, curmods[modname])
|
|
if update_file(path, modules[modname]):
|
|
print("%s updated." % curmods[modname],
|
|
file=sys.stderr)
|
|
is_in_sync = False
|
|
for modname in sorted(curmods.keys()):
|
|
if modname not in modules:
|
|
path = os.path.join(projdir, curmods[modname])
|
|
os.remove(path)
|
|
print("%s removed." % curmods[modname], file=sys.stderr)
|
|
is_in_sync = False
|
|
if is_in_sync:
|
|
print("Everything is in sync.", file=sys.stderr)
|
|
return
|
|
|
|
|
|
def read_basic_modules(libraries, libname):
|
|
""" Reads the OpenOffice/LibreOffice Basic macros from the macros
|
|
storage. """
|
|
modules = {}
|
|
if not libraries.hasByName(libname):
|
|
return modules
|
|
libraries.loadLibrary(libname)
|
|
library = libraries.getByName(libname)
|
|
for modname in library.getElementNames():
|
|
modules[modname] = library.getByName(modname)
|
|
return modules
|
|
|
|
|
|
def update_basic_modules(libraries, libname, modules, oo):
|
|
""" Updates the OpenOffice/LibreOffice Basic macro storage. """
|
|
if not libraries.hasByName(libname):
|
|
libraries.createLibrary(libname)
|
|
print("Script library %s created." % libname, file=sys.stderr)
|
|
create_dialog_library(oo, libname)
|
|
library = libraries.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())
|
|
for modname in sorted(modules.keys()):
|
|
if modname not in curmods:
|
|
library.insertByName(modname, modules[modname])
|
|
print("Module %s added." % modname, file=sys.stderr)
|
|
elif modules[modname] != library.getByName(modname):
|
|
library.replaceByName(modname, modules[modname])
|
|
print("Module %s updated." % modname, file=sys.stderr)
|
|
for modname in curmods:
|
|
if modname not in modules:
|
|
library.removeByName(modname)
|
|
print("Module %s removed." % modname, file=sys.stderr)
|
|
if libraries.isModified():
|
|
libraries.storeLibraries()
|
|
else:
|
|
print("Everything is in sync.", file=sys.stderr)
|
|
return
|
|
|
|
|
|
def create_dialog_library(oo, libname):
|
|
""" Creates the dialog library. """
|
|
libraries = oo.service_manager.createInstance(
|
|
"com.sun.star.script.ApplicationDialogLibraryContainer")
|
|
libraries.createLibrary(libname)
|
|
print("Dialog library %s created." % libname, file=sys.stderr)
|
|
libraries.storeLibraries()
|
|
|
|
|
|
def read_file(path):
|
|
""" Reads a file, and deals with Python 3 / 2 compatibility. """
|
|
if sys.version_info.major == 2:
|
|
f = open(path)
|
|
content = f.read().decode("utf-8").replace("\r\n", "\n")
|
|
f.close()
|
|
else:
|
|
f = open(path, encoding="utf-8")
|
|
content = f.read().replace("\r\n", "\n")
|
|
f.close()
|
|
return content
|
|
|
|
|
|
def write_file(path, content):
|
|
""" Writes to a file, and deals with Python 3 / 2
|
|
compatibility. """
|
|
if sys.version_info.major == 2:
|
|
f = open(path, "w")
|
|
f.write(content.encode("utf-8"))
|
|
f.close()
|
|
else:
|
|
f = open(path, "w", encoding="utf-8")
|
|
f.write(content)
|
|
f.close()
|
|
return
|
|
|
|
|
|
def update_file(path, content):
|
|
""" Updates a file, and deals with Python 3 / 2
|
|
compatibility. """
|
|
is_updated = False
|
|
if sys.version_info.major == 2:
|
|
f = open(path, "r+")
|
|
if content != f.read().decode("utf-8").replace("\r\n", "\n"):
|
|
f.seek(0)
|
|
f.truncate(0)
|
|
f.write(content.encode("utf-8"))
|
|
is_updated = True
|
|
f.close()
|
|
else:
|
|
f = open(path, "r+", encoding="utf-8")
|
|
if content != f.read().replace("\r\n", "\n"):
|
|
f.seek(0)
|
|
f.truncate(0)
|
|
f.write(content)
|
|
is_updated = True
|
|
f.close()
|
|
return is_updated
|
|
|
|
|
|
class Office:
|
|
""" The OpenOffice/LibreOffice connection. """
|
|
|
|
def __init__(self, port=2002):
|
|
""" Initializes the object."""
|
|
self.port = port
|
|
self.bootstrap_context = None
|
|
self.service_manager = None
|
|
self.desktop = None
|
|
self.connect()
|
|
|
|
def connect(self):
|
|
""" Connects to the running OpenOffice/LibreOffice process."""
|
|
# Obtains the local context
|
|
local_context = uno.getComponentContext()
|
|
# Obtains the local service manager
|
|
local_service_manager = local_context.getServiceManager()
|
|
# Obtains the URL resolver
|
|
url_resolver = local_service_manager.createInstanceWithContext(
|
|
"com.sun.star.bridge.UnoUrlResolver", local_context)
|
|
# Obtains the context
|
|
url = ("uno:socket,host=localhost,port=%d;"
|
|
"urp;StarOffice.ComponentContext") % self.port
|
|
while True:
|
|
try:
|
|
self.bootstrap_context = url_resolver.resolve(url)
|
|
except NoConnectException:
|
|
self.start_oo()
|
|
else:
|
|
break
|
|
# Obtains the service manager
|
|
self.service_manager = self.bootstrap_context.getServiceManager()
|
|
# Obtains the desktop service
|
|
self.desktop = self.service_manager.createInstanceWithContext(
|
|
"com.sun.star.frame.Desktop", self.bootstrap_context)
|
|
|
|
def start_oo(self):
|
|
""" Starts the OpenOffice/LibreOffice in server listening
|
|
mode. """
|
|
# For MS-Windows, which does not have fork()
|
|
if os.name == "nt":
|
|
from subprocess import Popen
|
|
ooexec = os.path.join(
|
|
os.path.dirname(uno.__file__), "soffice.exe")
|
|
DETACHED_PROCESS = 0x00000008
|
|
Popen([ooexec,
|
|
"-accept=socket,host=localhost,port=%d;urp;" %
|
|
self.port],
|
|
close_fds=True, creationflags=DETACHED_PROCESS)
|
|
time.sleep(2)
|
|
return
|
|
|
|
# For POSIX systems, including Linux and MacOSX
|
|
try:
|
|
pid = os.fork()
|
|
except OSError:
|
|
print("Failed to fork().", file=sys.stderr)
|
|
sys.exit(1)
|
|
if pid != 0:
|
|
time.sleep(2)
|
|
return
|
|
os.setsid()
|
|
ooexec = os.path.join(
|
|
os.path.dirname(uno.__file__), "soffice")
|
|
try:
|
|
os.execl(ooexec, ooexec,
|
|
"-accept=socket,host=localhost,port=%d;urp;" %
|
|
self.port)
|
|
except OSError:
|
|
print("%s: Failed to run the"
|
|
" OpenOffice/LibreOffice server." % ooexec,
|
|
file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|