zaz-barcode/zaz.py

539 lines
15 KiB
Python

#!/usr/bin/env python3
# == Rapid Develop Macros in LibreOffice ==
# ~ 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 argparse
import os
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)
class LiboXML(object):
TYPES = {
'py': 'application/vnd.sun.star.uno-component;type=Python',
'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',
}
NAME_SPACES = {
'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',
}
def __init__(self):
self._manifest = None
self._paths = []
def _save_path(self, attr):
self._paths.append(attr['{{{}}}full-path'.format(self.NAME_SPACES['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.NAME_SPACES['manifest_version'],
'xmlns:manifest': self.NAME_SPACES['manifest'],
'xmlns:loext': self.NAME_SPACES['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.NAME_SPACES['manifest'])
self._manifest = ET.fromstring(data)
data = {'xmlns:loext': self.NAME_SPACES['xmlns:loext']}
self._manifest.attrib.update(**data)
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 _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 = DIRS['files']
if not _exists(path):
_mkdir(path)
path_oxt = _join(path, 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()
if DATA['update']:
path_xml = _join(path, FILES['update'])
_save(path_xml, DATA['update'])
log.info('Extension OXT created sucesfully...')
return
def _install_and_test():
path_oxt = (_join(DIRS['files'], FILES['oxt']),)
call(PATHS['install'] + path_oxt)
log.info('Install extension sucesfully...')
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 sucesfully...')
return
def _update_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.py'
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, DIRS['office'])
_mkdir(path)
path = _join(path_source, DIRS['office'], FILES['shortcut'])
_save(path, DATA['shortcut'])
path = _join(path_source, FILES['addons'])
_save(path, DATA['addons'])
path = _join(path_source, FILES['description'])
_save(path, DATA['description'])
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"
log.info(msg)
for lang in EXTENSION['languages']:
path = _join(path_source, DIRS['locales'], lang, 'LC_MESSAGES')
Path(path).mkdir(parents=True, exist_ok=True)
_compile_idl()
return
def _new():
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/'
EASYMACRO = 'easymacro.'
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
with zipfile.PyZipFile(EASYMACRO + 'zip', mode='w') as zf:
zf.writepy(EASYMACRO + 'py')
xml = LiboXML()
path_easymacro = PATH + EASYMACRO + 'zip'
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(EASYMACRO + 'zip', 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(EASYMACRO + 'zip')
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):
EASYMACRO = 'easymacro.py'
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 main(args):
if args.update:
_update()
return
if args.locales:
_locales(args)
return
if args.embed:
_embed(args)
return
if args.new:
_new()
return
if not _validate_update():
return
_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('-i', '--install', dest='install', action='store_true',
default=False, required=False)
parser.add_argument('-n', '--new', dest='new', 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)
return parser.parse_args()
if __name__ == '__main__':
args = _process_command_line_arguments()
main(args)