Compare commits

...

4 Commits

Author SHA1 Message Date
El Mau f8c4e3c3ee Add global variables 2022-02-23 14:48:14 -06:00
Mauricio Baeza 9e4664905e Add tools for debug 2021-06-14 16:20:56 -05:00
Mauricio Baeza 062870937b Initial version 2021-06-14 14:56:30 -05:00
Mauricio Baeza b056b5260e Initial version 2021-06-14 14:54:22 -05:00
19 changed files with 1827 additions and 3 deletions

View File

@ -1,5 +1,16 @@
# zaz-easymacro
ZAZ EasyMacro
EasyMacro can you help to rapid develop macros in LibreOffice Basic, with Python of course.
ZAZ EasyMacro
EasyMacro can you help to rapid develop macros in LibreOffice Basic, with Python of course.
Thanks!
https://git.cuates.net/elmau/zaz
### Software libre, no gratis
This extension have a cost of maintenance of 1 euro every year.

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.0

435
conf.py Normal file
View File

@ -0,0 +1,435 @@
#!/usr/bin/env python3
# ~ This file is part of ZAZ.
# ~ ZAZ is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~ ZAZ is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~ You should have received a copy of the GNU General Public License
# ~ along with ZAZ. If not, see <https://www.gnu.org/licenses/>.
import logging
# ~ Type extension:
# ~ 1 = normal extension
# ~ 2 = new component
# ~ 3 = Calc addin
TYPE_EXTENSION = 2
# ~ Your great extension name, not used spaces
NAME = 'ZAZEasyMacro'
# ~ https://semver.org/
VERSION = '0.1.0'
# ~ Should be unique, used URL inverse
ID = 'net.elmau.zaz.EasyMacro'
# ~ If you extension will be multilanguage set: True
# ~ This feature used gettext, set pythonpath and easymacro in True
# ~ You can used PoEdit for edit PO files and generate MO files.
# ~ https://poedit.net/
USE_LOCALES = True
DOMAIN = 'base'
PATH_LOCALES = 'locales'
PATH_PYGETTEXT = '/usr/lib/python3.9/Tools/i18n/pygettext.py'
# ~ You can use PoEdit for update locales too
PATH_MSGMERGE = 'msgmerge'
# ~ Show in extension manager
URL_GIT = 'https://git.cuates.net/elmau/zaz-easymacro'
PUBLISHER = {
'en': {'text': 'El Mau', 'link': URL_GIT},
'es': {'text': 'El Mau', 'link': URL_GIT},
}
# ~ Name in this folder for copy
ICON = 'images/logo.png'
# ~ Name inside extensions
ICON_EXT = f'{NAME.lower()}.png'
# ~ Change for you favorite license
LICENSE_EN = f"""This file is part of {NAME}.
{NAME} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
{NAME} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with {NAME}. If not, see <https://www.gnu.org/licenses/>.
"""
LICENSE_ES = LICENSE_EN
INFO = {
'en': {
'display_name': 'Easy Macro',
'description': 'Rapid develop macros',
'license': LICENSE_EN,
},
'es': {
'display_name': 'Macro Fácil',
'description': 'Desarrollo rápido de macros',
'license': LICENSE_ES,
},
}
# ~ Menus, only for TYPE_EXTENSION = 1
# ~ Parent can be: AddonMenu or OfficeMenuBar
# ~ For icons con name: NAME_16.bmp, used only NAME
# ~ PARENT = ''
# ~ MENU_MAIN = {}
# ~ Shortcut: Key + "Modifier Keys"
# ~ Important: Not used any shortcuts used for LibreOffice
# ~ SHIFT is mapped to Shift on all platforms.
# ~ MOD1 is mapped to Ctrl on Windows/Linux, while it is mapped to Cmd on Mac.
# ~ MOD2 is mapped to Alt on all platforms.
# ~ For example: Shift+Ctrl+Alt+T -> T_SHIFT_MOD1_MOD2
PARENT = ''
MENU_MAIN = {}
MENUS = (
{
'title': {'en': 'Option 1', 'es': 'Opción 1'},
'argument': 'option1',
'context': 'calc,writer',
'icon': 'icon',
'toolbar': True,
'shortcut': 'T_SHIFT_MOD1_MOD2',
},
{
'title': {'en': 'Submenu', 'es': 'Submenu'},
'context': 'calc,writer',
'submenu': (
{
'title': {'en': 'Option 2', 'es': 'Opción 2'},
'argument': 'option2',
'context': 'calc,writer',
'icon': 'icon',
'toolbar': True,
'shortcut': '',
},
),
},
)
# ~ Functions, only for TYPE_EXTENSION = 3
FUNCTIONS = {
'test': {
'displayname': {'en': 'test', 'es': 'prueba'},
'description': {'en': 'My test', 'es': 'Mi prueba'},
'parameters': {
'value': {
'displayname': {'en': 'value', 'es': 'valor'},
'description': {'en': 'The value', 'es': 'El valor'},
},
},
},
}
# ~ FUNCTIONS = {}
EXTENSION = {
'version': VERSION,
'name': NAME,
'id': ID,
'icon': (ICON, ICON_EXT),
'languages': tuple(INFO.keys())
}
# ~ If used more libraries set python path in True and copy inside
# ~ If used easymacro pythonpath always is True, recommended
DIRS = {
'meta': 'META-INF',
'source': 'source',
'description': 'description',
'images': 'images',
'registration': 'registration',
'files': 'files',
'office': 'Office',
'locales': PATH_LOCALES,
'pythonpath': True,
}
FILES = {
'oxt': f'{NAME}_v{VERSION}.oxt',
'py': f'{NAME}.py',
'ext_desc': 'desc_{}.txt',
'manifest': 'manifest.xml',
'description': 'description.xml',
'idl': f'X{NAME}.idl',
'addons': 'Addons.xcu',
'urd': f'X{NAME}.urd',
'rdb': f'X{NAME}.rdb',
'update': f'{NAME.lower()}.update.xml',
'addin': 'CalcAddIn.xcu',
'shortcut': 'Accelerators.xcu',
'easymacro': True,
}
# ~ URLs for update for example
# ~ URL_XML_UPDATE = 'https://gitlab.com/USER/PROYECT/raw/BRANCH/FOLDERs/FILE_NAME'
URL_XML_UPDATE = ''
URL_OXT = ''
# ~ Default program for test: --calc, --writer, --draw
PROGRAM = '--calc'
# ~ Path to file for test
FILE_TEST = ''
PATHS = {
'idlc': '/usr/lib/libreoffice/sdk/bin/idlc',
'include': '/usr/share/idl/libreoffice',
'regmerge': '/usr/lib/libreoffice/program/regmerge',
'soffice': ('soffice', PROGRAM, FILE_TEST),
'install': ('unopkg', 'add', '-v', '-f', '-s'),
'profile': '/home/mau/.config/libreoffice/4/user',
'gettext': PATH_PYGETTEXT,
'msgmerge': PATH_MSGMERGE,
}
SERVICES = {
'job': "('com.sun.star.task.Job',)",
'addin': "('com.sun.star.sheet.AddIn',)",
}
FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
DATE = '%d/%m/%Y %H:%M:%S'
LEVEL_ERROR = logging.getLevelName(logging.ERROR)
LEVEL_INFO = logging.getLevelName(logging.INFO)
logging.addLevelName(logging.ERROR, f'\033[1;41m{LEVEL_ERROR}\033[1;0m')
logging.addLevelName(logging.INFO, f'\x1b[32m{LEVEL_INFO}\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=FORMAT, datefmt=DATE)
log = logging.getLogger(NAME)
def _methods():
template = """ def {0}(self, {1}):
print({1})
return 'ok'\n"""
functions = ''
for k, v in FUNCTIONS.items():
args = ','.join(v['parameters'].keys())
functions += template.format(k, args)
return functions
SRV = SERVICES['job']
XSRV = 'XJobExecutor'
SRV_IMPORT = f'from com.sun.star.task import {XSRV}'
METHODS = """ def trigger(self, args='pyUNO'):
print('Hello World', args)
return\n"""
if TYPE_EXTENSION > 1:
MENUS = ()
XSRV = f'X{NAME}'
SRV_IMPORT = f'from {ID} import {XSRV}'
if TYPE_EXTENSION == 2:
SRV = f"('{ID}',)"
METHODS = """ def test(self, args='pyUNO'):
print('Hello World', args)
return\n"""
elif TYPE_EXTENSION == 3:
SRV = SERVICES['addin']
METHODS = _methods()
DATA_PY = f"""import uno
import unohelper
{SRV_IMPORT}
ID_EXTENSION = '{ID}'
SERVICE = {SRV}
class {NAME}(unohelper.Base, {XSRV}):
def __init__(self, ctx):
self.ctx = ctx
{METHODS}
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation({NAME}, ID_EXTENSION, SERVICE)
"""
def _functions():
a = '[in] any {}'
t = ' any {}({});'
f = ''
for k, v in FUNCTIONS.items():
args = ','.join([a.format(k) for k, v in v['parameters'].items()])
f += t.format(k, args)
return f
FILE_IDL = ''
if TYPE_EXTENSION > 1:
id_ext = ID.replace('.', '_')
interface = f'X{NAME}'
module = ''
for i, P in enumerate(ID.split('.')):
module += f'module {P} {{ '
close_module = '}; ' * (i + 1)
functions = ' void test([in] any argument);'
if TYPE_EXTENSION == 3:
functions = _functions()
FILE_IDL = f"""#ifndef __{id_ext}_idl__
#define __{id_ext}_idl__
#include <com/sun/star/uno/XInterface.idl>
{module}
interface {interface} : com::sun::star::uno::XInterface
{{
{functions}
}};
service {P} {{
interface {interface};
}};
{close_module}
#endif
"""
def _parameters(args):
NODE = """ <node oor:name="{name}" oor:op="replace">
<prop oor:name="DisplayName">
{displayname}
</prop>
<prop oor:name="Description">
{description}
</prop>
</node>"""
line = '{}<value xml:lang="{}">{}</value>'
node = ''
for k, v in args.items():
displayname = '\n'.join(
[line.format(' ' * 16, k, v) for k, v in v['displayname'].items()])
description = '\n'.join(
[line.format(' ' * 16, k, v) for k, v in v['description'].items()])
values = {
'name': k,
'displayname': displayname,
'description': description,
}
node += NODE.format(**values)
return node
NODE_FUNCTIONS = ''
if TYPE_EXTENSION == 3:
tmp = '{}<value xml:lang="{}">{}</value>'
NODE_FUNCTION = """ <node oor:name="{name}" oor:op="replace">
<prop oor:name="DisplayName">
{displayname}
</prop>
<prop oor:name="Description">
{description}
</prop>
<prop oor:name="Category">
<value>Add-In</value>
</prop>
<prop oor:name="CompatibilityName">
<value xml:lang="en">AutoAddIn.{name}</value>
</prop>
<node oor:name="Parameters">
{parameters}
</node>
</node>"""
for k, v in FUNCTIONS.items():
displayname = '\n'.join(
[tmp.format(' ' * 12, k, v) for k, v in v['displayname'].items()])
description = '\n'.join(
[tmp.format(' ' * 12, k, v) for k, v in v['description'].items()])
parameters = _parameters(v['parameters'])
values = {
'name': k,
'displayname': displayname,
'description': description,
'parameters': parameters,
}
NODE_FUNCTIONS += NODE_FUNCTION.format(**values)
FILE_ADDIN = f"""<?xml version="1.0" encoding="UTF-8"?>
<oor:component-data xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" oor:name="CalcAddIns" oor:package="org.openoffice.Office">
<node oor:name="AddInInfo">
<node oor:name="{ID}" oor:op="replace">
<node oor:name="AddInFunctions">
{NODE_FUNCTIONS}
</node>
</node>
</node>
</oor:component-data>"""
DATA_MANIFEST = [FILES['py'], f"Office/{FILES['shortcut']}", 'Addons.xcu']
if TYPE_EXTENSION > 1:
DATA_MANIFEST.append(FILES['rdb'])
if TYPE_EXTENSION == 3:
DATA_MANIFEST.append('CalcAddIn.xcu')
DATA_DESCRIPTION = {
'identifier': {'value': ID},
'version': {'value': VERSION},
'display-name': {k: v['display_name'] for k, v in INFO.items()},
'icon': ICON_EXT,
'publisher': PUBLISHER,
'update': URL_XML_UPDATE,
}
DATA_ADDONS = {
'parent': PARENT,
'images': DIRS['images'],
'main': MENU_MAIN,
'menus': MENUS,
}
DATA = {
'py': DATA_PY,
'manifest': DATA_MANIFEST,
'description': DATA_DESCRIPTION,
'addons': DATA_ADDONS,
'update': URL_OXT,
'idl': FILE_IDL,
'addin': FILE_ADDIN,
}
with open('VERSION', 'w') as f:
f.write(VERSION)
# ~ LICENSE_ACCEPT_BY = 'user' # or admin
# ~ LICENSE_SUPPRESS_ON_UPDATE = True

196
easymacro.py Normal file
View File

@ -0,0 +1,196 @@
#!/usr/bin/env python3
# == Rapid Develop Macros in LibreOffice ==
# ~ https://git.cuates.net/elmau/easymacro
# ~ easymacro is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~ easymacro is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~ You should have received a copy of the GNU General Public License
# ~ along with easymacro. If not, see <https://www.gnu.org/licenses/>.
import getpass
import logging
import os
import platform
import socket
import subprocess
import sys
import time
from typing import Any
import uno
from com.sun.star.beans import PropertyValue
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
# Global variables
OS = platform.system()
DESKTOP = os.environ.get('DESKTOP_SESSION', '')
PC = platform.node()
USER = getpass.getuser()
IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin'
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc'
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
def debug(*args):
"""
Show messages debug
Parameters:
args (list): iterable messages for show
"""
data = [str(a) for a in args]
log.debug('\t'.join(data))
return
def error(message: Any):
"""
Show message error
Parameters:
message (Any): message show error
"""
log.error(message)
return
def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any:
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
elif args:
instance = SM.createInstanceWithArguments(name, (args,))
else:
instance = SM.createInstance(name)
return instance
def get_app_config(node_name: str, key: str='', update: bool=False):
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
if update:
service = 'com.sun.star.configuration.ConfigurationUpdateAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
try:
ca = cp.createInstanceWithArguments(service, (node,))
if ca and not key:
return ca
if ca and ca.hasByName(key):
return ca.getPropertyValue(key)
except Exception as e:
error(e)
return ''
# Get info LibO
NAME = get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion')
LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale')
LANG = LANGUAGE.split('-')[0]
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}"
class LOServer(object):
HOST = 'localhost'
PORT = '8100'
ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext'
CMD = ['soffice',
'-env:SingleAppInstance=false',
'-env:UserInstallation=file:///tmp/LO_Process8100',
'--headless', '--norestore', '--invisible',
f'--accept={ARG}']
def __init__(self):
self._server = None
self._ctx = None
self._sm = None
self._start_server()
self._init_values()
def _init_values(self):
global CTX
global SM
if not self.is_running:
return
ctx = uno.getComponentContext()
service = 'com.sun.star.bridge.UnoUrlResolver'
resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx)
self._ctx = resolver.resolve('uno:{}'.format(self.ARG))
self._sm = self._ctx.getServiceManager()
CTX = self._ctx
SM = self._sm
return
@property
def is_running(self):
try:
s = socket.create_connection((self.HOST, self.PORT), 5.0)
s.close()
debug('LibreOffice is running...')
return True
except ConnectionRefusedError:
return False
def _start_server(self):
if self.is_running:
return
for i in range(3):
self._server = subprocess.Popen(self.CMD,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(3)
if self.is_running:
break
return
def stop(self):
if self._server is None:
print('Search pgrep soffice')
else:
self._server.terminate()
debug('LibreOffice is stop...')
return
def create_instance(self, name, with_context=True):
if with_context:
instance = self._sm.createInstanceWithContext(name, self._ctx)
else:
instance = self._sm.createInstance(name)
return instance

Binary file not shown.

BIN
images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" manifest:version="1.2">
<manifest:file-entry manifest:full-path="ZAZEasyMacro.py" manifest:media-type="application/vnd.sun.star.uno-component;type=Python"/>
<manifest:file-entry manifest:full-path="Office/Accelerators.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
<manifest:file-entry manifest:full-path="Addons.xcu" manifest:media-type="application/vnd.sun.star.configuration-data"/>
<manifest:file-entry manifest:full-path="XZAZEasyMacro.rdb" manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB"/>
</manifest:manifest>

43
source/XZAZEasyMacro.idl Normal file
View File

@ -0,0 +1,43 @@
#ifndef __net_elmau_zaz_EasyMacro_idl__
#define __net_elmau_zaz_EasyMacro_idl__
#include <com/sun/star/uno/XInterface.idl>
/*
any, boolean, byte, char, double, float, hyper, long, short, string, void,
unsigned hyper, unsigned long, unsigned short
*/
module net { module elmau { module zaz { module EasyMacro {
interface XZAZEasyMacro : com::sun::star::uno::XInterface
{
[attribute, readonly] string OS;
[attribute, readonly] string DESKTOP;
[attribute, readonly] string PC;
[attribute, readonly] string USER;
[attribute, readonly] boolean IS_WIN;
[attribute, readonly] boolean IS_MAC;
[attribute, readonly] string NAME;
[attribute, readonly] string VERSION;
[attribute, readonly] string LANGUAGE;
[attribute, readonly] string LANG;
[attribute, readonly] string INFO_DEBUG;
};
interface XZAZDebug : com::sun::star::uno::XInterface {
void inspect([in] any obj);
void info([in] any data);
void debug([in] any data);
void error([in] any data);
};
service EasyMacro {
interface XZAZEasyMacro;
interface XZAZDebug;
};
}; }; }; };
#endif

BIN
source/XZAZEasyMacro.rdb Normal file

Binary file not shown.

20
source/ZAZEasyMacro.py Normal file
View File

@ -0,0 +1,20 @@
import uno
import unohelper
from net.elmau.zaz.EasyMacro import XZAZEasyMacro
from libo import LIBO
ID_EXTENSION = 'net.elmau.zaz.EasyMacro'
SERVICE = ('net.elmau.zaz.EasyMacro',)
class ZAZEasyMacro(unohelper.Base, XZAZEasyMacro, LIBO):
def __init__(self, ctx):
self.ctx = ctx
super().__init__()
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(ZAZEasyMacro, ID_EXTENSION, SERVICE)

26
source/description.xml Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:d="http://openoffice.org/extensions/description/2006">
<identifier value="net.elmau.zaz.EasyMacro"/>
<version value="0.1.0"/>
<display-name>
<name lang="en">Easy Macro</name>
<name lang="es">Macro Fácil</name>
</display-name>
<extension-description>
<src lang="en" xlink:href="description/desc_en.txt"/>
<src lang="es" xlink:href="description/desc_es.txt"/>
</extension-description>
<icon>
<default xlink:href="images/zazeasymacro.png"/>
</icon>
<publisher>
<name xlink:href="https://git.cuates.net/elmau/zaz-easymacro" lang="en">El Mau</name>
<name xlink:href="https://git.cuates.net/elmau/zaz-easymacro" lang="es">El Mau</name>
</publisher>
<registration>
<simple-license accept-by="user" suppress-on-update="true">
<license-text xlink:href="registration/license_en.txt" lang="en"/>
<license-text xlink:href="registration/license_es.txt" lang="es"/>
</simple-license>
</registration>
</description>

View File

@ -0,0 +1 @@
Rapid develop macros

View File

@ -0,0 +1 @@
Desarrollo rápido de macros

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,196 @@
#!/usr/bin/env python3
# == Rapid Develop Macros in LibreOffice ==
# ~ https://git.cuates.net/elmau/easymacro
# ~ easymacro is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~ easymacro is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~ You should have received a copy of the GNU General Public License
# ~ along with easymacro. If not, see <https://www.gnu.org/licenses/>.
import getpass
import logging
import os
import platform
import socket
import subprocess
import sys
import time
from typing import Any
import uno
from com.sun.star.beans import PropertyValue
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m')
logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m')
logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m')
logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE)
log = logging.getLogger(__name__)
# Global variables
OS = platform.system()
DESKTOP = os.environ.get('DESKTOP_SESSION', '')
PC = platform.node()
USER = getpass.getuser()
IS_WIN = OS == 'Windows'
IS_MAC = OS == 'Darwin'
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc'
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
def debug(*args):
"""
Show messages debug
Parameters:
args (list): iterable messages for show
"""
data = [str(a) for a in args]
log.debug('\t'.join(data))
return
def error(message: Any):
"""
Show message error
Parameters:
message (Any): message show error
"""
log.error(message)
return
def create_instance(name: str, with_context: bool=False, args: Any=None) -> Any:
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
elif args:
instance = SM.createInstanceWithArguments(name, (args,))
else:
instance = SM.createInstance(name)
return instance
def get_app_config(node_name: str, key: str='', update: bool=False):
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
if update:
service = 'com.sun.star.configuration.ConfigurationUpdateAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
try:
ca = cp.createInstanceWithArguments(service, (node,))
if ca and not key:
return ca
if ca and ca.hasByName(key):
return ca.getPropertyValue(key)
except Exception as e:
error(e)
return ''
# Get info LibO
NAME = get_app_config('org.openoffice.Setup/Product', 'ooName')
VERSION = get_app_config('org.openoffice.Setup/Product','ooSetupVersion')
LANGUAGE = get_app_config('org.openoffice.Setup/L10N/', 'ooLocale')
LANG = LANGUAGE.split('-')[0]
INFO_DEBUG = f"{NAME} v{VERSION} {LANGUAGE}\n\n{_info_debug}"
class LOServer(object):
HOST = 'localhost'
PORT = '8100'
ARG = f'socket,host={HOST},port={PORT};urp;StarOffice.ComponentContext'
CMD = ['soffice',
'-env:SingleAppInstance=false',
'-env:UserInstallation=file:///tmp/LO_Process8100',
'--headless', '--norestore', '--invisible',
f'--accept={ARG}']
def __init__(self):
self._server = None
self._ctx = None
self._sm = None
self._start_server()
self._init_values()
def _init_values(self):
global CTX
global SM
if not self.is_running:
return
ctx = uno.getComponentContext()
service = 'com.sun.star.bridge.UnoUrlResolver'
resolver = ctx.ServiceManager.createInstanceWithContext(service, ctx)
self._ctx = resolver.resolve('uno:{}'.format(self.ARG))
self._sm = self._ctx.getServiceManager()
CTX = self._ctx
SM = self._sm
return
@property
def is_running(self):
try:
s = socket.create_connection((self.HOST, self.PORT), 5.0)
s.close()
debug('LibreOffice is running...')
return True
except ConnectionRefusedError:
return False
def _start_server(self):
if self.is_running:
return
for i in range(3):
self._server = subprocess.Popen(self.CMD,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
time.sleep(3)
if self.is_running:
break
return
def stop(self):
if self._server is None:
print('Search pgrep soffice')
else:
self._server.terminate()
debug('LibreOffice is stop...')
return
def create_instance(self, name, with_context=True):
if with_context:
instance = self._sm.createInstanceWithContext(name, self._ctx)
else:
instance = self._sm.createInstance(name)
return instance

37
source/pythonpath/libo.py Normal file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env python3
from net.elmau.zaz.EasyMacro import XZAZDebug
import easymacro as app
class Debug(XZAZDebug):
def info(self, message):
app.info(message)
return
def debug(self, message):
app.debug(message)
return
def error(self, message):
app.error(message)
return
def inspect(self, obj):
app.inspect(obj)
return
class LIBO(Debug):
OS = app.OS
DESKTOP = app.DESKTOP
PC = app.PC
USER = app.USER
IS_WIN = app.IS_WIN
IS_MAC = app.IS_MAC
NAME = app.NAME
VERSION = app.VERSION
LANGUAGE = app.LANGUAGE
LANG = app.LANG
INFO_DEBUG = app.INFO_DEBUG

View File

@ -0,0 +1,14 @@
This file is part of ZAZEasyMacro.
ZAZEasyMacro is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ZAZEasyMacro is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ZAZEasyMacro. If not, see <https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,14 @@
This file is part of ZAZEasyMacro.
ZAZEasyMacro is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ZAZEasyMacro is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ZAZEasyMacro. If not, see <https://www.gnu.org/licenses/>.

822
zaz.py Executable file
View File

@ -0,0 +1,822 @@
#!/usr/bin/env python3
# == Rapid Develop Macros in LibreOffice ==
# ~ This file is part of ZAZ.
# ~ https://git.elmau.net/elmau/zaz
# ~ ZAZ is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~ ZAZ is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~ You should have received a copy of the GNU General Public License
# ~ along with ZAZ. If not, see <https://www.gnu.org/licenses/>.
import argparse
import os
import py_compile
import re
import sys
import zipfile
from datetime import datetime
from pathlib import Path
from shutil import copyfile
from subprocess import call
from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString
from conf import (
DATA,
DIRS,
DOMAIN,
EXTENSION,
FILES,
INFO,
PATHS,
TYPE_EXTENSION,
USE_LOCALES,
log)
EASYMACRO = 'easymacro.py'
class LiboXML(object):
CONTEXT = {
'calc': 'com.sun.star.sheet.SpreadsheetDocument',
'writer': 'com.sun.star.text.TextDocument',
'impress': 'com.sun.star.presentation.PresentationDocument',
'draw': 'com.sun.star.drawing.DrawingDocument',
'base': 'com.sun.star.sdb.OfficeDatabaseDocument',
'math': 'com.sun.star.formula.FormulaProperties',
'basic': 'com.sun.star.script.BasicIDE',
}
TYPES = {
'py': 'application/vnd.sun.star.uno-component;type=Python',
'pyc': 'application/binary',
'zip': 'application/binary',
'xcu': 'application/vnd.sun.star.configuration-data',
'rdb': 'application/vnd.sun.star.uno-typelibrary;type=RDB',
'xcs': 'application/vnd.sun.star.configuration-schema',
'help': 'application/vnd.sun.star.help',
'component': 'application/vnd.sun.star.uno-components',
}
NS_MANIFEST = {
'manifest_version': '1.2',
'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
'xmlns:loext': 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0',
}
NS_DESCRIPTION = {
'xmlns': 'http://openoffice.org/extensions/description/2006',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
'xmlns:d': 'http://openoffice.org/extensions/description/2006',
}
NS_ADDONS = {
'xmlns:xs': 'http://www.w3.org/2001/XMLSchema',
'xmlns:oor': 'http://openoffice.org/2001/registry',
}
NS_UPDATE = {
'xmlns': 'http://openoffice.org/extensions/update/2006',
'xmlns:d': 'http://openoffice.org/extensions/description/2006',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
}
def __init__(self):
self._manifest = None
self._paths = []
self._path_images = ''
self._toolbars = []
def _save_path(self, attr):
self._paths.append(attr['{{{}}}full-path'.format(self.NS_MANIFEST['manifest'])])
return
def _clean(self, name, nodes):
has_words = re.compile('\\w')
if not re.search(has_words, str(nodes.tail)):
nodes.tail = ''
if not re.search(has_words, str(nodes.text)):
nodes.text = ''
for node in nodes:
if name == 'manifest':
self._save_path(node.attrib)
if not re.search(has_words, str(node.tail)):
node.tail = ''
if not re.search(has_words, str(node.text)):
node.text = ''
return
def new_manifest(self, data):
attr = {
'manifest:version': self.NS_MANIFEST['manifest_version'],
'xmlns:manifest': self.NS_MANIFEST['manifest'],
'xmlns:loext': self.NS_MANIFEST['xmlns:loext'],
}
self._manifest = ET.Element('manifest:manifest', attr)
return self.add_data_manifest(data)
def parse_manifest(self, data):
ET.register_namespace('manifest', self.NS_MANIFEST['manifest'])
self._manifest = ET.fromstring(data)
attr = {'xmlns:loext': self.NS_MANIFEST['xmlns:loext']}
self._manifest.attrib.update(**attr)
self._clean('manifest', self._manifest)
return
def add_data_manifest(self, data):
node_name = 'manifest:file-entry'
attr = {
'manifest:full-path': '',
'manifest:media-type': '',
}
for path in data:
if path in self._paths:
continue
ext = path.split('.')[-1]
attr['manifest:full-path'] = path
attr['manifest:media-type'] = self.TYPES.get(ext, '')
ET.SubElement(self._manifest, node_name, attr)
return self._get_xml(self._manifest)
def new_description(self, data):
doc = ET.Element('description', self.NS_DESCRIPTION)
key = 'identifier'
ET.SubElement(doc, key, data[key])
key = 'version'
ET.SubElement(doc, key, data[key])
key = 'display-name'
node = ET.SubElement(doc, key)
for k, v in data[key].items():
sn = ET.SubElement(node, 'name', {'lang': k})
sn.text = v
node = ET.SubElement(doc, 'extension-description')
for k in data[key].keys():
attr = {
'lang': k,
'xlink:href': f'description/desc_{k}.txt',
}
ET.SubElement(node, 'src', attr)
key = 'icon'
node = ET.SubElement(doc, key)
attr = {'xlink:href': f"images/{data[key]}"}
ET.SubElement(node, 'default', attr)
key = 'publisher'
node = ET.SubElement(doc, key)
for k, v in data[key].items():
attr = {
'xlink:href': v['link'],
'lang': k,
}
sn = ET.SubElement(node, 'name', attr)
sn.text = v['text']
key = 'display-name'
node = ET.SubElement(doc, 'registration')
attr = {
'accept-by': 'user',
'suppress-on-update': 'true',
}
node = ET.SubElement(node, 'simple-license', attr)
for k in data[key].keys():
attr = {
'xlink:href': f"{DIRS['registration']}/license_{k}.txt",
'lang': k
}
ET.SubElement(node, 'license-text', attr)
if data['update']:
node = ET.SubElement(doc, 'update-information')
ET.SubElement(node, 'src', {'xlink:href': data['update']})
return self._get_xml(doc)
def _get_context(self, args):
if not args:
return ''
context = ','.join([self.CONTEXT[v] for v in args.split(',')])
return context
def _add_node_value(self, node, name, value='_self'):
attr = {'oor:name': name, 'oor:type': 'xs:string'}
sn = ET.SubElement(node, 'prop', attr)
sn = ET.SubElement(sn, 'value')
sn.text = value
return
def _add_menu(self, id_extension, node, index, menu, in_menu_bar=True):
if in_menu_bar:
attr = {
'oor:name': index,
'oor:op': 'replace',
}
subnode = ET.SubElement(node, 'node', attr)
else:
subnode = node
attr = {'oor:name': 'Title', 'oor:type': 'xs:string'}
sn1 = ET.SubElement(subnode, 'prop', attr)
for k, v in menu['title'].items():
sn2 = ET.SubElement(sn1, 'value', {'xml:lang': k})
sn2.text = v
value = self._get_context(menu['context'])
self._add_node_value(subnode, 'Context', value)
if 'submenu' in menu:
sn = ET.SubElement(subnode, 'node', {'oor:name': 'Submenu'})
for i, m in enumerate(menu['submenu']):
self._add_menu(id_extension, sn, f'{index}.s{i}', m)
if m.get('toolbar', False):
self._toolbars.append(m)
return
value = f"service:{id_extension}?{menu['argument']}"
self._add_node_value(subnode, 'URL', value)
self._add_node_value(subnode, 'Target')
value = f"%origin%/{self._path_images}/{menu['icon']}"
self._add_node_value(subnode, 'ImageIdentifier', value)
return
def new_addons(self, id_extension, data):
in_menu_bar = data['parent'] == 'OfficeMenuBar'
self._path_images = data['images']
attr = {
'oor:name': 'Addons',
'oor:package': 'org.openoffice.Office',
}
attr.update(self.NS_ADDONS)
doc = ET.Element('oor:component-data', attr)
parent = ET.SubElement(doc, 'node', {'oor:name': 'AddonUI'})
node = ET.SubElement(parent, 'node', {'oor:name': data['parent']})
op = 'fuse'
if in_menu_bar:
op = 'replace'
attr = {'oor:name': id_extension, 'oor:op': op}
node = ET.SubElement(node, 'node', attr)
if in_menu_bar:
attr = {'oor:name': 'Title', 'oor:type': 'xs:string'}
subnode = ET.SubElement(node, 'prop', attr)
for k, v in data['main'].items():
sn = ET.SubElement(subnode, 'value', {'xml:lang': k})
sn.text = v
self._add_node_value(node, 'Target')
node = ET.SubElement(node, 'node', {'oor:name': 'Submenu'})
for i, menu in enumerate(data['menus']):
self._add_menu(id_extension, node, f'm{i}', menu, in_menu_bar)
if menu.get('toolbar', False):
self._toolbars.append(menu)
if self._toolbars:
attr = {'oor:name': 'OfficeToolBar'}
toolbar = ET.SubElement(parent, 'node', attr)
attr = {'oor:name': id_extension, 'oor:op': 'replace'}
toolbar = ET.SubElement(toolbar, 'node', attr)
for t, menu in enumerate(self._toolbars):
self._add_menu(id_extension, toolbar, f't{t}', menu)
return self._get_xml(doc)
def _add_shortcut(self, node, key, id_extension, arg):
attr = {'oor:name': key, 'oor:op': 'fuse'}
subnode = ET.SubElement(node, 'node', attr)
subnode = ET.SubElement(subnode, 'prop', {'oor:name': 'Command'})
subnode = ET.SubElement(subnode, 'value', {'xml:lang': 'en-US'})
subnode.text = f"service:{id_extension}?{arg}"
return
def _get_acceleartors(self, menu):
if 'submenu' in menu:
for m in menu['submenu']:
return self._get_acceleartors(m)
if not menu.get('shortcut', ''):
return ''
return menu
def new_accelerators(self, id_extension, menus):
attr = {
'oor:name': 'Accelerators',
'oor:package': 'org.openoffice.Office',
}
attr.update(self.NS_ADDONS)
doc = ET.Element('oor:component-data', attr)
parent = ET.SubElement(doc, 'node', {'oor:name': 'PrimaryKeys'})
data = []
for m in menus:
info = self._get_acceleartors(m)
if info:
data.append(info)
node_global = None
node_modules = None
for m in data:
if m['context']:
if node_modules is None:
node_modules = ET.SubElement(
parent, 'node', {'oor:name': 'Modules'})
for app in m['context'].split(','):
node = ET.SubElement(
node_modules, 'node', {'oor:name': self.CONTEXT[app]})
self._add_shortcut(
node, m['shortcut'], id_extension, m['argument'])
else:
if node_global is None:
node_global = ET.SubElement(
parent, 'node', {'oor:name': 'Global'})
self._add_shortcut(
node_global, m['shortcut'], id_extension, m['argument'])
return self._get_xml(doc)
def new_update(self, extension, url_oxt):
doc = ET.Element('description', self.NS_UPDATE)
ET.SubElement(doc, 'identifier', {'value': extension['id']})
ET.SubElement(doc, 'version', {'value': extension['version']})
node = ET.SubElement(doc, 'update-download')
ET.SubElement(node, 'src', {'xlink:href': url_oxt})
node = ET.SubElement(doc, 'release-notes')
return self._get_xml(doc)
def _get_xml(self, doc):
xml = parseString(ET.tostring(doc, encoding='utf-8'))
return xml.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8')
def _exists(path):
return os.path.exists(path)
def _join(*paths):
return os.path.join(*paths)
def _mkdir(path):
return Path(path).mkdir(parents=True, exist_ok=True)
def _save(path, data):
with open(path, 'w') as f:
f.write(data)
return
def _get_files(path, filters=''):
paths = []
if filters in ('*', '*.*'):
filters = ''
for folder, _, files in os.walk(path):
if filters:
pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE)
paths += [_join(folder, f) for f in files if pattern.search(f)]
else:
paths += files
return paths
def _compress_oxt():
log.info('Compress OXT extension...')
path_oxt = _join(DIRS['files'], FILES['oxt'])
z = zipfile.ZipFile(path_oxt, 'w', compression=zipfile.ZIP_DEFLATED)
root_len = len(os.path.abspath(DIRS['source']))
for root, dirs, files in os.walk(DIRS['source']):
relative = os.path.abspath(root)[root_len:]
for f in files:
fullpath = _join(root, f)
file_name = _join(relative, f)
if file_name == FILES['idl']:
continue
z.write(fullpath, file_name, zipfile.ZIP_DEFLATED)
z.close()
log.info('Extension OXT created successfully...')
return
def _install_and_test():
path_oxt = (_join(DIRS['files'], FILES['oxt']),)
call(PATHS['install'] + path_oxt)
log.info('Install extension successfully...')
log.info('Start LibreOffice...')
call(PATHS['soffice'])
return
def _validate_new():
path_source = DIRS['source']
if not _exists(path_source):
return True
msg = f'Path: {path_source}, exists, delete first'
log.error(msg)
return False
def _create_new_directories():
path_source = DIRS['source']
_mkdir(path_source)
path = _join(path_source, DIRS['meta'])
_mkdir(path)
path = _join(path_source, DIRS['description'])
_mkdir(path)
path = _join(path_source, DIRS['images'])
_mkdir(path)
path = _join(path_source, DIRS['registration'])
_mkdir(path)
path = _join(path_source, DIRS['office'])
_mkdir(path)
if FILES['easymacro'] or DIRS['pythonpath']:
path = _join(path_source, 'pythonpath')
_mkdir(path)
path = DIRS['files']
if not _exists(path):
_mkdir(path)
msg = 'Created directories...'
log.info(msg)
return
def _create_new_files():
path_source = DIRS['source']
for k, v in INFO.items():
file_name = f'license_{k}.txt'
path = _join(path_source, DIRS['registration'], file_name)
_save(path, v['license'])
if TYPE_EXTENSION > 1:
path = _join(path_source, FILES['idl'])
_save(path, DATA['idl'])
path = _join(path_source, FILES['py'])
_save(path, DATA['py'])
msg = 'Created files...'
log.info(msg)
return
def _validate_update():
if TYPE_EXTENSION == 1:
return True
if not _exists(PATHS['idlc']):
msg = 'Binary: "idlc" not found'
log.error(msg)
return False
if not _exists(PATHS['include']):
msg = 'Directory: "include" not found'
log.error(msg)
return False
if not _exists(PATHS['regmerge']):
msg = 'Binary: "regmerge" not found'
log.error(msg)
return False
path = _join(DIRS['source'], FILES['idl'])
if not _exists(path):
msg = f'File: "{FILES["idl"]}" not found'
log.error(msg)
return False
return True
def _compile_idl():
if TYPE_EXTENSION == 1:
return
log.info('Compilate IDL...')
path_rdb = _join(DIRS['source'], FILES['rdb'])
path_urd = _join(DIRS['source'], FILES['urd'])
path = _join(DIRS['source'], FILES['idl'])
call([PATHS['idlc'], '-I', PATHS['include'], path])
call([PATHS['regmerge'], path_rdb, '/UCR', path_urd])
os.remove(path_urd)
log.info('Compilate IDL successfully...')
return
def _update_files():
path_files = DIRS['files']
if not _exists(path_files):
_mkdir(path_files)
path_source = DIRS['source']
for k, v in INFO.items():
file_name = FILES['ext_desc'].format(k)
path = _join(path_source, DIRS['description'], file_name)
_save(path, v['description'])
path_logo = EXTENSION['icon'][0]
if _exists(path_logo):
file_name = EXTENSION['icon'][1]
path = _join(path_source, DIRS['images'], file_name)
copyfile(path_logo, path)
files = os.listdir(DIRS['images'])
for f in files:
if f[-3:].lower() == 'bmp':
source = _join(DIRS['images'], f)
target = _join(path_source, DIRS['images'], f)
copyfile(source, target)
if FILES['easymacro']:
source = EASYMACRO
target = _join(path_source, 'pythonpath', source)
copyfile(source, target)
xml = LiboXML()
path = _join(path_source, DIRS['meta'], FILES['manifest'])
data = xml.new_manifest(DATA['manifest'])
_save(path, data)
path = _join(path_source, FILES['description'])
data = xml.new_description(DATA['description'])
_save(path, data)
if TYPE_EXTENSION == 1:
path = _join(path_source, FILES['addons'])
data = xml.new_addons(EXTENSION['id'], DATA['addons'])
_save(path, data)
path = _join(path_source, DIRS['office'])
_mkdir(path)
path = _join(path_source, DIRS['office'], FILES['shortcut'])
data = xml.new_accelerators(EXTENSION['id'], DATA['addons']['menus'])
_save(path, data)
if TYPE_EXTENSION == 3:
path = _join(path_source, FILES['addin'])
_save(path, DATA['addin'])
if USE_LOCALES:
msg = "Don't forget generate DOMAIN.pot for locales"
for lang in EXTENSION['languages']:
path = _join(path_source, DIRS['locales'], lang, 'LC_MESSAGES')
Path(path).mkdir(parents=True, exist_ok=True)
log.info(msg)
if DATA['update']:
path_xml = _join(path_files, FILES['update'])
data = xml.new_update(EXTENSION, DATA['update'])
_save(path_xml, data)
_compile_idl()
return
def _create():
if not _validate_new():
return
_create_new_directories()
_create_new_files()
_update_files()
msg = f"New extension: {EXTENSION['name']} make sucesfully...\n"
msg += '\tNow, you can install and test: zaz.py -i'
log.info(msg)
return
def _get_info_path(path):
path, filename = os.path.split(path)
name, extension = os.path.splitext(filename)
return (path, filename, name, extension)
def _zip_embed(source, files):
PATH = 'Scripts/python/'
FILE_PYC = 'easymacro.pyc'
p, f, name, e = _get_info_path(source)
now = datetime.now().strftime('_%Y%m%d_%H%M%S')
path_source = _join(p, name + now + e)
copyfile(source, path_source)
target = source
py_compile.compile(EASYMACRO, FILE_PYC)
xml = LiboXML()
path_easymacro = PATH + FILE_PYC
names = [f[1] for f in files] + [path_easymacro]
nodes = []
with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as zt:
with zipfile.ZipFile(path_source, compression=zipfile.ZIP_DEFLATED) as zs:
for name in zs.namelist():
if FILES['manifest'] in name:
path_manifest = name
xml_manifest = zs.open(name).read()
elif name in names:
continue
else:
zt.writestr(name, zs.open(name).read())
data = []
for path, name in files:
data.append(name)
zt.write(path, name)
zt.write(FILE_PYC, path_easymacro)
data.append(path_easymacro)
xml.parse_manifest(xml_manifest)
xml_manifest = xml.add_data_manifest(data)
zt.writestr(path_manifest, xml_manifest)
os.unlink(FILE_PYC)
return
def _embed(args):
PATH = 'Scripts/python'
PYTHONPATH = 'pythonpath'
doc = args.document
if not doc:
msg = '-d/--document Path file to embed is mandatory'
log.error(msg)
return
if not _exists(doc):
msg = 'Path file not exists'
log.error(msg)
return
files = []
if args.files:
files = args.files.split(',')
source = _join(PATHS['profile'], PATH)
content = os.listdir(source)
if PYTHONPATH in content:
content.remove(PYTHONPATH)
if files:
files = [(_join(source, f), _join(PATH, f)) for f in files if f in content]
else:
files = [(_join(source, f), _join(PATH, f)) for f in content]
_zip_embed(doc, files)
log.info('Embedded macros successfully...')
return
def _locales(args):
if args.files:
files = args.files.split(',')
else:
files = _get_files(DIRS['source'], 'py')
paths = ' '.join([f for f in files if not EASYMACRO in f])
path_pot = _join(DIRS['source'], DIRS['locales'], '{}.pot'.format(DOMAIN))
call([PATHS['gettext'], '-o', path_pot, paths])
log.info('POT generate successfully...')
return
def _update():
path_locales = _join(DIRS['source'], DIRS['locales'])
path_pot = _join(DIRS['source'], DIRS['locales'], '{}.pot'.format(DOMAIN))
if not _exists(path_pot):
log.error('Not exists file POT...')
return
files = _get_files(path_locales, 'po')
if not files:
log.error('First, generate files PO...')
return
for f in files:
call([PATHS['msgmerge'], '-U', f, path_pot])
log.info('\tUpdate: {}'.format(f))
log.info('Locales update successfully...')
return
def _new(args):
if not args.target:
msg = 'Add argument target: -t PATH_TARGET'
log.error(msg)
return
if not args.name:
msg = 'Add argument name: -n name-new-extension'
log.error(msg)
return
path = _join(args.target, args.name)
_mkdir(path)
_mkdir(_join(path, 'files'))
_mkdir(_join(path, 'images'))
path_logo = 'images/pymacros.png'
copyfile(path_logo, _join(path, 'images/logo.png'))
copyfile('zaz.py', _join(path, 'zaz.py'))
copyfile(EASYMACRO, _join(path, 'easymacro.py'))
copyfile('conf.py.example', _join(path, 'conf.py'))
msg = 'Folders and files copy successfully for new extension.'
log.info(msg)
msg = f'Change to folder: {path}'
log.info(msg)
return
def main(args):
if args.new:
_new(args)
return
if args.update:
_update()
return
if args.locales:
_locales(args)
return
if args.embed:
_embed(args)
return
if args.create:
_create()
return
if not _validate_update():
return
if not args.only_compress:
_update_files()
_compress_oxt()
if args.install:
_install_and_test()
log.info('Extension make successfully...')
return
def _process_command_line_arguments():
parser = argparse.ArgumentParser(
description='Make LibreOffice extensions')
parser.add_argument('-new', '--new', dest='new', action='store_true',
default=False, required=False)
parser.add_argument('-t', '--target', dest='target', default='')
parser.add_argument('-n', '--name', dest='name', default='', required=False)
parser.add_argument('-c', '--create', dest='create', action='store_true',
default=False, required=False)
parser.add_argument('-i', '--install', dest='install', action='store_true',
default=False, required=False)
parser.add_argument('-e', '--embed', dest='embed', action='store_true',
default=False, required=False)
parser.add_argument('-d', '--document', dest='document', default='')
parser.add_argument('-f', '--files', dest='files', default='')
parser.add_argument('-l', '--locales', dest='locales', action='store_true',
default=False, required=False)
parser.add_argument('-u', '--update', dest='update', action='store_true',
default=False, required=False)
parser.add_argument('-oc', '--only_compress', dest='only_compress',
action='store_true', default=False, required=False)
return parser.parse_args()
if __name__ == '__main__':
args = _process_command_line_arguments()
main(args)