diff --git a/README.rst b/README.rst index 879f558..6e38585 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ macros: If the Basic library ``MyApp`` does not exist, it will be created. Missing modules will be added, and excess modules will be removed. -And vice versa. Given the following Basic macros: +On the other hand, given the following Basic macros: * Library: ``MyApp`` * Modules: ``MyMacros`` ``Utils`` ``Registry`` ``Data`` @@ -41,42 +41,180 @@ deleted. INSTALL ------- -You can install ``obasync`` with ``pip``. Uses the ``python`` -executable that comes with your OpenOffice/LibreOffice installation -when possible. +You can either: -* OpenOffice 4 on Linux:: +1. Install ``obasync`` with ``pip`` (recommended), or - /opt/openoffice4/program/python `which pip` install obasync +2. Download the ``obasync`` script manually, and run it with the + Python that come with your OpenOffice/LibreOffice installation. -* LibreOffice 5.x on Linux:: +We will explain them in detail. - /opt/libreoffice5.*/program/python `which pip` install obasync -* Linux vendor OpenOffice/LibreOffice installation:: +OpenOffice/LibreOffice That Comes with Your Linux +################################################# + +Install with ``pip`` (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Your system may already have ``pip`` installed. If not, install the +``python-pip`` package from the system package manager. Then, run:: pip install obasync -* OpenOffice on Mac OS X relies on the system ``python`` - installation:: +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + python obasync + +Or, you can edit the script and change the first line (shebang) to:: + + #! /usr/bin/python + +and save this script somewhere in your path, say, ``/usr/local/bin``. +Then you can run ``obasync``. + + +OpenOffice 4 on Linux +##################### + +Install with ``pip`` (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install ``pip`` for your OpenOffice installation, and then install +``obasync`` with this ``pip``:: + + wget https://bootstrap.pypa.io/get-pip.py + sudo /opt/openoffice4/program/python get-pip.py + /opt/openoffice4/program/python-core-2.7.6/bin/pip install obasync + +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + /opt/openoffice4/program/python obasync + +Or, you can edit the script and change the first line (shebang) to:: + + #! /opt/openoffice4/program/python + +and save this script somewhere in your path, say, ``/usr/local/bin``. +Then you can run ``obasync``. + + +LibreOffice on Linux +#################### + +Python from LibreOffice on Linux does not install ``pip`` properly. +However, you can still download and install ``obasync`` manually. + +Install with ``pip`` (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + /opt/libreoffice5.2/program/python obasync + +Or, you can edit the script and change the first line (shebang) to:: + + #! /opt/libreoffice5.2/program/python + +and save this script somewhere in your path, say, ``/usr/local/bin``. +Then you can run ``obasync``. + + +OpenOffice on MS-Windows +######################## + +You can install ``obasync`` with ``pip``, but the result is messy. +The recommended way is to download and install ``obasync`` manually. + +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + "C:\Program Files (x86)\OpenOffice 4\program\python.exe" obasync + + +LibreOffice on MS-Windows +######################### + +You can install ``obasync`` with ``pip``, but the result is messy. +The recommended way is to download and install ``obasync`` manually. + +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + "C:\Program Files\LibreOffice 5\program\python.exe" obasync + + +OpenOffice on Mac OS X +###################### + +Install with ``pip`` (Recommended) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install ``pip`` first, and then install ``obasync`` with ``pip``:: + + wget https://bootstrap.pypa.io/get-pip.py + sudo python get-pip.py sudo pip install obasync -* LibreOffice on Mac OS X: There is no simple ``pip`` way to install - ``obasync``. However, you can still download the script and run - it with the ``python`` executable that comes with your LibreOffice - installation. See below. +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* OpenOffice/LibreOffice on MS-Windows: You can still install - ``obasync`` with ``pip`` from your OpenOffice/LibreOffice - installation. However, it would be much easier to just download the - script and run it with the ``python`` executable that comes with - your OpenOffice/LibreOffice installation. See below. +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: -To download the package or the source script: + python obasync -* Python package: https://pypi.python.org/pypi/obasync -* Source directory: https://github.com/imacat/obasync +Or, you can edit the script and change the first line (shebang) to:: + + #! /usr/bin/python + +and save this script somewhere in your path, say, ``/usr/local/bin``. +Then you can run ``obasync``. + + + +LibreOffice on Mac OS X +####################### + +Python from LibreOffice on Mac OS X does not install ``pip`` properly. +However, you can still download and install ``obasync`` manually. + +Download and Install Manually +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Download ``obasync`` from either `PyPI +`_ or `GitHub +`_. Then, run ``obasync`` as:: + + /Applications/LibreOffice.app/Contents/Resources/python obasync + +Or, you can edit the script and change the first line (shebang) to:: + + #! /Applications/LibreOffice.app/Contents/Resources/python + +and save this script somewhere in your path, say, ``/usr/local/bin``. +Then you can run ``obasync``. OPTIONS @@ -120,7 +258,7 @@ LIBRARY The name of the Basic library. Default to the same -h, --help Show the help message and exit --v, --version Show program's version number and exit +-v, --version Show program’s version number and exit COPYRIGHT diff --git a/bin/obasync b/bin/obasync index f5f513d..0d867c0 100755 --- a/bin/obasync +++ b/bin/obasync @@ -3,6 +3,94 @@ # Office Basic macro source synchronizer. # by imacat , 2016-08-31 +# Copyright (c) 2016 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Synchronize Office Basic macro source. + +obasync is an OpenOffice/LibreOffice Basic macro source synchronizer. +It synchronizes your Basic macros with your local project files. + +Given the following source files: + +* Directory: MyApp +* Files: MyMacros.vb Utils.vb Registry.vb Data.vb + +Running "obasync" will synchronize them with the following Basic +macros: + +* Library: MyApp +* Modules: MyMacros Utils Registry Data + +If the Basic library MyApp does not exist, it will be created. +Missing modules will be added, and excess modules will be removed. + +On the other hand, given the following Basic macros: + +* Library: MyApp +* Modules: MyMacros Utils Registry Data + +Running "obasync --get" will synchronize them with the following +source files: + +* Directory: MyApp +* Files: MyMacros.vb Utils.vb Registry.vb Data.vb + +Missing source files will be added, and excess source files will be +deleted. + +SYNOPSIS + + obasync [options] [DIRECTORY [LIBRARY]] + +DIRECTORY The project source directory. Default to the current + working directory. + +LIBRARY The name of the Basic library. Default to the same + name as the project source directory. + +--get Download (check out) the macros from the + OpenOffice/LibreOffice Basic storage to the source + files, instead of upload (check in). By default it + uploads the source files onto the + OpenOffice/LibreOffice Basic storage. + +-p, --port N The TCP port to communicate with + 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 + default is ".vb". This may be used for your + convenience of editor syntax highlighting. + +-e, --encoding CS + The encoding of the source files. The default is + system-dependent. For example, on Traditional Chinese + MS-Windows, this will be CP950 (Big5). You can change + this to UTF-8 for convenience if you + obtain/synchronize your source code from other + sources. + +-r, --run MODULE.MACRO + Run he specific macro after synchronization, for + convenience. + +-h, --help Show the help message and exit + +-v, --version Show program’s version number and exit +""" + from __future__ import print_function import argparse import os @@ -12,8 +100,7 @@ import locale def append_uno_path(): - """ Appends the path of the uno module to the import path. """ - + """Append 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 @@ -36,18 +123,49 @@ from com.sun.star.connection import NoConnectException def main(): - """ The main program. """ + """The main program.""" t_start = time.time() global args # Parses the arguments parse_args() - # Connects to the OpenOffice/LibreOffice - oo = Office(args.port) + # 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) + if len(modules) == 0: + print("ERROR: Library %s does not exist" % args.library, + file=sys.stderr) + return + update_source_dir( + args.projdir, modules, args.ext, args.encoding) - # Synchronize the Basic macros. - sync_macros(oo, args.ext, args.encoding) + # Uploads the macros onto OpenOffice/LibreOffice Basic + else: + modules = read_in_source_dir( + args.projdir, args.ext, args.encoding) + if len(modules) == 0: + print("ERROR: Found no source macros in %s" % + args.projdir, + 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) + 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((), (), ()) print("Done. %02d:%02d elapsed." % (int((time.time() - t_start) / 60), @@ -55,7 +173,7 @@ def main(): def parse_args(): - """ Parses the arguments. """ + """Parse the arguments.""" global args parser = argparse.ArgumentParser( @@ -89,7 +207,7 @@ def parse_args(): "-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.2") + "-v", "--version", action="version", version="%(prog)s 0.3") args = parser.parse_args() # Obtains the absolute path @@ -104,45 +222,20 @@ def parse_args(): return -def sync_macros(oo, ext, encoding): - """ 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, encoding) - - # Uploads the macros onto OpenOffice/LibreOffice Basic - else: - modules = read_in_source_dir(args.projdir, ext, encoding) - 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, encoding): - """ Reads-in the source macros. """ + """Read-in the source files. + + Arguments: + projdir: The project source directory. + ext: The file name extension of the source files, beginning + with a single dot ".". + encoding: The encoding of the source file. + + Returns: + The Basic modules that read, as a dictionary. The keys of the + dictionary are the module names, and the values of the + dictionary are the module contents. + """ modules = {} for entry in os.listdir(projdir): path = os.path.join(projdir, entry) @@ -154,7 +247,17 @@ def read_in_source_dir(projdir, ext, encoding): def update_source_dir(projdir, modules, ext, encoding): - """ Updates the source macros. """ + """Update the source files. + + Arguments: + projdir: The project source directory. + 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. + ext: The file name extension of the source files, beginning + with a single dot ".". + encoding: The encoding of the source file. + """ curmods = {} is_in_sync = True for entry in os.listdir(projdir): @@ -187,8 +290,21 @@ def update_source_dir(projdir, modules, ext, encoding): def read_basic_modules(libraries, libname): - """ Reads the OpenOffice/LibreOffice Basic macros from the macros - storage. """ + """ + 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. + libname: The name of the Basic library. + + Returns: + The Basic modules that read, as a dictionary. The keys of the + dictionary are the module names, and the values of the + dictionary are the module contents. + """ modules = {} if not libraries.hasByName(libname): return modules @@ -200,7 +316,19 @@ def read_basic_modules(libraries, libname): def update_basic_modules(libraries, libname, modules, oo): - """ Updates the OpenOffice/LibreOffice Basic macro storage. """ + """Update the OpenOffice/LibreOffice Basic macro storage. + + Arguments: + libraries: The Basic library storage, as a + com.sun.star.script.ApplicationScriptLibraryContainer + service 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) print("Script library %s created." % libname, file=sys.stderr) @@ -232,7 +360,13 @@ def update_basic_modules(libraries, libname, modules, oo): def create_dialog_library(oo, libname): - """ Creates the dialog library. """ + """Create the dialog library. + + Arguments: + oo: The OpenOffice/LibreOffice connection, as an Office + object. + libname: The name of the dialog library. + """ libraries = oo.service_manager.createInstance( "com.sun.star.script.ApplicationDialogLibraryContainer") libraries.createLibrary(libname) @@ -241,7 +375,15 @@ def create_dialog_library(oo, libname): def read_file(path, encoding): - """ Reads a file, and deals with Python 3 / 2 compatibility. """ + """Read a file, and deals with Python 3 / 2 compatibility. + + Arguments: + path: The full path of the file. + encoding: The encoding of the file. + + Returns: + The content of the file. + """ if sys.version_info.major == 2: f = open(path) content = f.read().decode(encoding).replace("\r\n", "\n") @@ -254,8 +396,13 @@ def read_file(path, encoding): def write_file(path, content, encoding): - """ Writes to a file, and deals with Python 3 / 2 - compatibility. """ + """Write to a file, and deals with Python 3 / 2 compatibility. + + Arguments: + path: The full path of the file. + content: The content of the file. + encoding: The encoding of the file. + """ if sys.version_info.major == 2: f = open(path, "w") f.write(content.encode(encoding)) @@ -268,8 +415,19 @@ def write_file(path, content, encoding): def update_file(path, content, encoding): - """ Updates a file, and deals with Python 3 / 2 - compatibility. """ + """Update a file, and deals with Python 3 / 2 compatibility. + + The file will only be update if its content is different from + the supplied content. + + Arguments: + path: The full path of the file. + content: The new content of the file. + encoding: The encoding of the file. + + Returns: + True if the file is updated, or False otherwise. + """ is_updated = False if sys.version_info.major == 2: f = open(path, "r+") @@ -291,10 +449,30 @@ def update_file(path, content, encoding): class Office: - """ The OpenOffice/LibreOffice connection. """ + """The OpenOffice/LibreOffice connection. + + Attributes: + port: The TCP port that is used to communicate with the + OpenOffice/LibreOffice process. OpenOffice/LibreOffice + will listen to this port. + bootstrap_context: The bootstrap context of the + OpenOffice/LibreOffice desktop. + service_manager: The service manager of the + OpenOffice/LibreOffce desktop. + desktop: The OpenOffice/LibreOffice desktop object. + """ def __init__(self, port=2002): - """ Initializes the object.""" + """Initialize the OpenOffice/LibreOffice connection. + + Arguments: + port: The TCP port to communicate with + OpenOffice/LibreOffice with. OpenOffice/LibreOffice + will start to listen on this port if it is not + listening on this port not yet. The default is + port 2002. Change it to another port if port 2002 is + already in use. + """ self.port = port self.bootstrap_context = None self.service_manager = None @@ -302,7 +480,11 @@ class Office: self.connect() def connect(self): - """ Connects to the running OpenOffice/LibreOffice process.""" + """Connect to the running OpenOffice/LibreOffice process. + + Run OpenOffice/LibreOffice in server listening mode if it is + not running yet. + """ # Obtains the local context local_context = uno.getComponentContext() # Obtains the local service manager @@ -327,15 +509,14 @@ class Office: "com.sun.star.frame.Desktop", self.bootstrap_context) def start_oo(self): - """ Starts the OpenOffice/LibreOffice in server listening - mode. """ + """Start 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( + soffice = os.path.join( os.path.dirname(uno.__file__), "soffice.exe") DETACHED_PROCESS = 0x00000008 - Popen([ooexec, + Popen([soffice, "-accept=socket,host=localhost,port=%d;urp;" % self.port], close_fds=True, creationflags=DETACHED_PROCESS) @@ -352,8 +533,8 @@ class Office: time.sleep(2) return os.setsid() - ooexec = self.find_posix_ooexec() - if ooexec is None: + soffice = self.find_posix_soffice() + if soffice is None: print("Failed to find the " "OpenOffice/LibreOffice installation.", file=sys.stderr) @@ -362,56 +543,64 @@ class Office: self.port # LibreOffice on POSIX systems uses --accept instead of # -accept now. - if self.is_ooexec_lo(ooexec): + if self.is_soffice_lo(soffice): param = "-" + param try: - os.execl(ooexec, ooexec, param) + os.execl(soffice, soffice, param) except OSError: print("%s: Failed to run the" - " OpenOffice/LibreOffice server." % ooexec, + " OpenOffice/LibreOffice server." % soffice, file=sys.stderr) sys.exit(1) - def find_posix_ooexec(self): - """ Finds the OpenOffice/LibreOffice executable on POSIX - systems (Linux or MacOSX). """ + def find_posix_soffice(self): + """Find soffice on POSIX systems (Linux or MacOSX). + + Returns: + The found soffice executable, or None if not found. + """ # Checkes soffice in the same directory of uno.py # This works for Linux OpenOffice/LibreOffice local # installation, and OpenOffice on MacOSX. - ooexec = os.path.join( + soffice = os.path.join( os.path.dirname(uno.__file__), "soffice") - if os.path.exists(ooexec): - return ooexec - + if os.path.exists(soffice): + return soffice + # Now we have LibreOffice on MacOSX and Linux # OpenOffice/LibreOffice vender installation. - + # LibreOffice on MacOSX. - ooexec = "/Applications/LibreOffice.app/Contents/MacOS/soffice" - if os.path.exists(ooexec): - return ooexec - + soffice = "/Applications/LibreOffice.app/Contents/MacOS/soffice" + if os.path.exists(soffice): + return soffice + # Linux OpenOffice/LibreOffice vender installation. - ooexec = "/usr/bin/soffice" - if os.path.exists(ooexec): - return ooexec - + soffice = "/usr/bin/soffice" + if os.path.exists(soffice): + return soffice + # Not found return None - def is_ooexec_lo(self, ooexec): - """ Checks whether the soffice executable is LibreOffice. - LibreOffice on POSIX systems uses --accept instead of - -accept now. """ + def is_soffice_lo(self, soffice): + """Check whether the soffice executable is LibreOffice. + + LibreOffice on POSIX systems accepts "--accept" instead of + "-accept" now. + + Returns: + True if soffice is LibreOffice, or False otherwise. + """ # This works for most cases. - if ooexec.lower().find("libreoffice") != -1: + if soffice.lower().find("libreoffice") != -1: return True - + # Checks the symbolic link at /usr/bin/soffice - if ooexec == "/usr/bin/soffice" and os.path.islink(ooexec): - if os.readlink(ooexec).lower().find("libreoffice") != -1: + if soffice == "/usr/bin/soffice" and os.path.islink(soffice): + if os.readlink(soffice).lower().find("libreoffice") != -1: return True - + # Not found return False diff --git a/setup.py b/setup.py index 6c0e97f..d77051c 100755 --- a/setup.py +++ b/setup.py @@ -1,18 +1,35 @@ +#! /opt/openoffice4/program/python +# -*- coding: utf-8 -*- +# Python setuptools installer for the obasync project. +# by imacat , 2016-12-20 + +# Copyright (c) 2016 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import os import sys from setuptools import setup -# For Python shipped with OpenOffice, python is a wrapper -# for python.bin that fixes the import and library path. We should -# call python instead of python.bin. """ -import os -import sys +# For Python shipped with OpenOffice, "python" is a shell script +# wrapper for the real executable "python.bin" that sets the import +# and library path. We should call "python" instead of "python.bin". if os.path.basename(sys.executable) == "python.bin": sys.executable = os.path.join( os.path.dirname(sys.executable), "python") setup(name="obasync", - version="0.2", + version="0.3", description="Office Basic macro source synchronizer", url="https://github.com/imacat/obasync", author="imacat",