easymacro/source/easymacro.py

2067 lines
58 KiB
Python
Raw Normal View History

2022-02-21 23:43:58 -06:00
#!/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/>.
2022-03-02 14:44:58 -06:00
import csv
2022-02-23 22:49:43 -06:00
import datetime
2022-02-21 23:43:58 -06:00
import getpass
2022-02-26 22:22:11 -06:00
import hashlib
2022-02-26 00:11:41 -06:00
import json
2022-02-21 23:43:58 -06:00
import logging
import os
import platform
2022-03-02 14:44:58 -06:00
import re
2022-02-26 22:22:11 -06:00
import shlex
import shutil
2022-02-21 23:43:58 -06:00
import socket
2022-02-27 23:28:06 -06:00
import ssl
2022-02-21 23:43:58 -06:00
import subprocess
import sys
2022-02-26 22:22:11 -06:00
import tempfile
2022-02-26 00:11:41 -06:00
import threading
2022-02-21 23:43:58 -06:00
import time
2022-02-23 22:49:43 -06:00
import traceback
2022-02-21 23:43:58 -06:00
2022-02-23 22:49:43 -06:00
from functools import wraps
2022-02-26 22:22:11 -06:00
from pathlib import Path
2022-02-23 22:49:43 -06:00
from pprint import pprint
2022-02-27 23:28:06 -06:00
from string import Template
from typing import Any, Union
from socket import timeout
from urllib import parse
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
2022-02-21 23:43:58 -06:00
2022-03-02 14:13:47 -06:00
import mailbox
import smtplib
from smtplib import SMTPException, SMTPAuthenticationError
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.utils import formatdate
from email import encoders
2022-02-21 23:43:58 -06:00
import uno
2022-02-23 22:49:43 -06:00
from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS
from com.sun.star.awt.MessageBoxResults import YES
2022-02-26 00:11:41 -06:00
from com.sun.star.beans import PropertyValue, NamedValue
2022-02-28 19:02:42 -06:00
from com.sun.star.ui.dialogs import TemplateDescription
2022-02-21 23:43:58 -06:00
2022-02-24 23:59:04 -06:00
2022-02-21 23:43:58 -06:00
# 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'
2022-02-23 22:49:43 -06:00
2022-02-27 23:28:06 -06:00
LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
LOG_DATE = '%d/%m/%Y %H:%M:%S'
if IS_WIN:
logging.addLevelName(logging.ERROR, 'ERROR')
logging.addLevelName(logging.DEBUG, 'DEBUG')
logging.addLevelName(logging.INFO, 'INFO')
else:
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__)
2022-02-21 23:43:58 -06:00
2022-02-27 23:28:06 -06:00
_info_debug = f"Python: {sys.version}\n\n{platform.platform()}\n\n" + '\n'.join(sys.path)
TIMEOUT = 10
2022-02-21 23:43:58 -06:00
SALT = b'00a1bfb05353bb3fd8e7aa7fe5efdccc'
2022-02-26 22:22:11 -06:00
_EVENTS = {}
PYTHON = 'python'
if IS_WIN:
PYTHON = 'python.exe'
2022-02-27 23:28:06 -06:00
FILES = {
'CONFIG': 'zaz-{}.json',
}
DIRS = {}
2022-02-28 19:02:42 -06:00
MESSAGES = {
'es': {
'OK': 'Aceptar',
'Cancel': 'Cancelar',
'Select path': 'Seleccionar ruta',
'Select directory': 'Seleccionar directorio',
'Select file': 'Seleccionar archivo',
'Incorrect user or password': 'Nombre de usuario o contraseƱa invƔlidos',
'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail',
}
}
2022-02-21 23:43:58 -06:00
CTX = uno.getComponentContext()
SM = CTX.getServiceManager()
2022-02-24 23:59:04 -06:00
# UNO Enum
class MessageBoxType():
2022-02-25 22:43:37 -06:00
"""Class for import enum
`See Api MessageBoxType <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt.html#ad249d76933bdf54c35f4eaf51a5b7965>`_
"""
2022-02-24 23:59:04 -06:00
from com.sun.star.awt.MessageBoxType \
import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
MBT = MessageBoxType
2022-02-23 22:49:43 -06:00
2022-02-24 23:59:04 -06:00
def create_instance(name: str, with_context: bool=False, argument: Any=None) -> Any:
"""Create a service instance
:param name: Name of service
:type name: str
:param with_context: If used context
:type with_context: bool
:param argument: If needed some argument
:type argument: Any
:return: PyUno instance
:rtype: PyUno Object
"""
2022-02-21 23:43:58 -06:00
if with_context:
instance = SM.createInstanceWithContext(name, CTX)
2022-02-24 23:59:04 -06:00
elif argument:
instance = SM.createInstanceWithArguments(name, (argument,))
2022-02-21 23:43:58 -06:00
else:
instance = SM.createInstance(name)
2022-02-24 23:59:04 -06:00
2022-02-21 23:43:58 -06:00
return instance
2022-02-24 23:59:04 -06:00
def get_app_config(node_name: str, key: str='') -> Any:
"""Get any key from any node from LibreOffice configuration.
:param node_name: Name of node
:type name: str
:param key: Name of key
:type key: str
:return: Any value
:rtype: Any
`See Api ConfigurationProvider <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1configuration_1_1ConfigurationProvider.html>`_
"""
2022-02-21 23:43:58 -06:00
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
2022-02-24 23:59:04 -06:00
value = ''
try:
value = cp.createInstanceWithArguments(service, (node,))
if value and value.hasByName(key):
value = value.getPropertyValue(key)
except Exception as e:
error(e)
value = ''
return value
2022-02-26 22:22:11 -06:00
# Get info LibO
NAME = TITLE = 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}"
# Get start date from Calc configuration
node = '/org.openoffice.Office.Calc/Calculate/Other/Date'
year = get_app_config(node, 'YY')
month = get_app_config(node, 'MM')
day = get_app_config(node, 'DD')
DATE_OFFSET = datetime.date(year, month, day).toordinal()
2022-02-28 19:02:42 -06:00
def _(msg):
if LANG == 'en':
return msg
if not LANG in MESSAGES:
return msg
return MESSAGES[LANG][msg]
2022-02-24 23:59:04 -06:00
def set_app_config(node_name: str, key: str, new_value: Any) -> Any:
2022-02-25 22:43:37 -06:00
"""Update value for key in node name.
:param node_name: Name of node
:type name: str
:param key: Name of key
:type key: str
:return: True if update sucesfully
:rtype: bool
`See Api ConfigurationUpdateAccess <https://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1configuration_1_1ConfigurationUpdateAccess.html>`_
"""
2022-02-24 23:59:04 -06:00
result = True
current_value = ''
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationUpdateAccess'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
update = cp.createInstanceWithArguments(service, (node,))
2022-02-21 23:43:58 -06:00
try:
2022-02-24 23:59:04 -06:00
current_value = update.getPropertyValue(key)
update.setPropertyValue(key, new_value)
update.commitChanges()
2022-02-21 23:43:58 -06:00
except Exception as e:
error(e)
2022-02-24 23:59:04 -06:00
if update.hasByName(key) and current_value:
update.setPropertyValue(key, current_value)
update.commitChanges()
result = False
return result
2022-02-26 22:22:11 -06:00
def debug(*messages) -> None:
"""Show messages debug
:param messages: List of messages to debug
:type messages: list[Any]
"""
data = [str(m) for m in messages]
log.debug('\t'.join(data))
return
def error(message: Any) -> None:
"""Show message error
:param message: The message error
:type message: Any
"""
log.error(message)
return
def info(*messages) -> None:
"""Show messages info
:param messages: List of messages to debug
:type messages: list[Any]
"""
data = [str(m) for m in messages]
log.info('\t'.join(data))
return
def save_log(path: str, data: Any) -> None:
"""Save data in file, data append to end and automatic add current time.
:param path: Path to save log
:type path: str
:param data: Data to save in file log
:type data: Any
"""
with open(path, 'a') as f:
f.write(f'{str(now())[:19]} - ')
pprint(data, stream=f)
return
2022-02-24 23:59:04 -06:00
def _set_app_command(command: str, disable: bool):
NEW_NODE_NAME = f'zaz_disable_command_{command.lower()}'
name = 'com.sun.star.configuration.ConfigurationProvider'
service = 'com.sun.star.configuration.ConfigurationUpdateAccess'
node_name = '/org.openoffice.Office.Commands/Execute/Disabled'
cp = create_instance(name, True)
node = PropertyValue(Name='nodepath', Value=node_name)
update = cp.createInstanceWithArguments(service, (node,))
2022-02-25 22:43:37 -06:00
result = True
try:
if disable:
new_node = update.createInstanceWithArguments(())
new_node.setPropertyValue('Command', command)
update.insertByName(NEW_NODE_NAME, new_node)
else:
update.removeByName(NEW_NODE_NAME)
update.commitChanges()
except Exception as e:
result = False
2022-02-24 23:59:04 -06:00
2022-02-25 22:43:37 -06:00
return result
2022-02-24 23:59:04 -06:00
class commands():
2022-02-25 22:43:37 -06:00
"""Class for disable and enable commands
:param command: UNO Command for disable or enable
:type command: str
`See DispatchCommands <https://wiki.documentfoundation.org/Development/DispatchCommands>`_
"""
2022-02-24 23:59:04 -06:00
def __init__(self, command):
self._command = command
2022-02-25 22:43:37 -06:00
2022-02-24 23:59:04 -06:00
def disable(self):
2022-02-25 22:43:37 -06:00
"""Disable command
:return: True if correctly disable, False if not.
:rtype: bool
"""
2022-02-24 23:59:04 -06:00
return _set_app_command(self._command, True)
2022-02-25 22:43:37 -06:00
2022-02-24 23:59:04 -06:00
def enabled(self):
2022-02-25 22:43:37 -06:00
"""Enable command
:return: True if correctly enable, False if not.
:rtype: bool
"""
2022-02-24 23:59:04 -06:00
return _set_app_command(self._command, False)
2022-02-21 23:43:58 -06:00
2022-02-24 23:59:04 -06:00
def mri(obj: Any) -> None:
"""Inspect object with MRI Extension
2022-02-23 22:49:43 -06:00
2022-02-24 23:59:04 -06:00
:param obj: Any pyUno object
:type obj: Any
2022-02-23 22:49:43 -06:00
2022-02-24 23:59:04 -06:00
`See MRI <https://github.com/hanya/MRI/releases>`_
2022-02-23 22:49:43 -06:00
"""
2022-02-24 23:59:04 -06:00
mri = create_instance('mytools.Mri')
2022-02-23 22:49:43 -06:00
if m is None:
msg = 'Extension MRI not found'
error(msg)
return
if hasattr(obj, 'obj'):
obj = obj.obj
2022-02-24 23:59:04 -06:00
mri.inspect(obj)
2022-02-23 22:49:43 -06:00
return
def catch_exception(f):
2022-02-24 23:59:04 -06:00
"""Catch exception for any function
:param f: Any Python function
:type f: Function instance
"""
2022-02-23 22:49:43 -06:00
@wraps(f)
def func(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
name = f.__name__
if IS_WIN:
msgbox(traceback.format_exc())
log.error(name, exc_info=True)
return func
2022-02-21 23:43:58 -06:00
2022-02-24 23:59:04 -06:00
def msgbox(message: Any, title: str=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, \
2022-02-25 22:43:37 -06:00
type_message_box=MessageBoxType.INFOBOX) -> int:
2022-02-24 23:59:04 -06:00
"""Create message box
:param message: Any type message, all is converted to string.
:type message: Any
:param title: The title for message box
:type title: str
:param buttons: A combination of `com::sun::star::awt::MessageBoxButtons <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1MessageBoxButtons.html>`_
:type buttons: long
:param type_message_box: The `message box type <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt.html#ad249d76933bdf54c35f4eaf51a5b7965>`_
:type type_message_box: enum
2022-02-25 22:43:37 -06:00
:return: `MessageBoxResult <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1awt_1_1MessageBoxResults.html>`_
:rtype: int
2022-02-24 23:59:04 -06:00
`See Api XMessageBoxFactory <http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html>`_
"""
toolkit = create_instance('com.sun.star.awt.Toolkit')
parent = toolkit.getDesktopWindow()
box = toolkit.createMessageBox(parent, type_message_box, buttons, title, str(message))
return box.execute()
2022-02-25 22:43:37 -06:00
def question(message: str, title: str=TITLE) -> bool:
2022-02-24 23:59:04 -06:00
"""Create message box question, show buttons YES and NO
:param message: Message question
:type message: str
:param title: The title for message box
:type title: str
:return: True if user click YES and False if click NO
:rtype: bool
"""
result = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, MessageBoxType.QUERYBOX)
return result == YES
2022-02-25 22:43:37 -06:00
def warning(message: Any, title: str=TITLE) -> int:
2022-02-24 23:59:04 -06:00
"""Create message box with icon warning
:param message: Any type message, all is converted to string.
:type message: Any
:param title: The title for message box
:type title: str
2022-02-25 22:43:37 -06:00
:return: MessageBoxResult
:rtype: int
2022-02-24 23:59:04 -06:00
"""
return msgbox(message, title, type_message_box=MessageBoxType.WARNINGBOX)
2022-02-25 22:43:37 -06:00
def errorbox(message: Any, title: str=TITLE) -> int:
2022-02-24 23:59:04 -06:00
"""Create message box with icon error
:param message: Any type message, all is converted to string.
:type message: Any
:param title: The title for message box
:type title: str
2022-02-25 22:43:37 -06:00
:return: MessageBoxResult
:rtype: int
2022-02-24 23:59:04 -06:00
"""
return msgbox(message, title, type_message_box=MessageBoxType.ERRORBOX)
2022-02-26 00:11:41 -06:00
def sleep(seconds: int):
2022-02-25 22:43:37 -06:00
"""Sleep
2022-02-24 23:59:04 -06:00
"""
2022-02-25 22:43:37 -06:00
time.sleep(seconds)
return
2022-02-24 23:59:04 -06:00
2022-02-26 00:11:41 -06:00
def run_in_thread(fn):
"""Run any function in thread
:param fn: Any Python function (macro)
:type fn: Function instance
"""
def run(*k, **kw):
t = threading.Thread(target=fn, args=k, kwargs=kw)
t.start()
return t
return run
def dict_to_property(values: dict, uno_any: bool=False):
"""Convert dictionary to array of PropertyValue
:param values: Dictionary of values
:type values: dict
:param uno_any: If return like array uno.Any
:type uno_any: bool
:return: Tuple of PropertyValue or array uno.Any
:rtype: tuples or uno.Any
"""
ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()])
if uno_any:
ps = uno.Any('[]com.sun.star.beans.PropertyValue', ps)
return ps
def _property_to_dict(values):
d = {v.Name: v.Value for v in values}
return d
def data_to_dict(data) -> dict:
"""Convert tuples, list, PropertyValue, NamedValue to dictionary
:param data: Dictionary of values
:type data: array of tuples, list, PropertyValue or NamedValue
:return: Dictionary
:rtype: dict
"""
d = {}
if not isinstance(data, (tuple, list)):
return d
if isinstance(data[0], (tuple, list)):
d = {r[0]: r[1] for r in data}
elif isinstance(data[0], (PropertyValue, NamedValue)):
d = _property_to_dict(data)
return d
2022-02-27 23:28:06 -06:00
def render(template, data):
s = Template(template)
return s.safe_substitute(**data)
# Classes
2022-02-25 22:43:37 -06:00
class _classproperty:
def __init__(self, method=None):
self.fget = method
2022-02-24 23:59:04 -06:00
2022-02-25 22:43:37 -06:00
def __get__(self, instance, cls=None):
return self.fget(cls)
def getter(self, method):
self.fget = method
return self
class Dates(object):
"""Class for datetimes
2022-02-24 23:59:04 -06:00
"""
2022-02-25 22:43:37 -06:00
_start = None
@_classproperty
def now(cls):
"""Current local date and time
:return: Return the current local date and time
:rtype: datetime
"""
2022-02-27 23:28:06 -06:00
return datetime.datetime.now().replace(microsecond=0)
2022-02-25 22:43:37 -06:00
@_classproperty
def today(cls):
"""Current local date
:return: Return the current local date
:rtype: date
"""
return datetime.date.today()
2022-02-26 22:22:11 -06:00
@_classproperty
def time(cls):
"""Current local time
:return: Return the current local time
:rtype: datetime.time
"""
t = cls.now.time().replace(microsecond=0)
return t
2022-02-25 22:43:37 -06:00
@_classproperty
def epoch(cls):
"""Get unix time
:return: Return unix time
:rtype: int
`See Unix Time <https://en.wikipedia.org/wiki/Unix_time>`_
"""
n = cls.now
e = int(time.mktime(n.timetuple()))
return e
@classmethod
2022-02-26 00:11:41 -06:00
def date(cls, year: int, month: int, day: int):
2022-02-25 22:43:37 -06:00
"""Get date from year, month, day
:param year: Year of date
:type year: int
:param month: Month of date
:type month: int
:param day: Day of day
:type day: int
:return: Return the date
:rtype: date
`See Python date <https://docs.python.org/3/library/datetime.html#date-objects>`_
"""
d = datetime.date(year, month, day)
return d
@classmethod
2022-02-26 00:11:41 -06:00
def str_to_date(cls, str_date: str, template: str, to_calc: bool=False):
2022-02-25 22:43:37 -06:00
"""Get date from string
:param str_date: Date in string
:type str_date: str
:param template: Formato of date string
:type template: str
:param to_calc: If date is for used in Calc cell
:type to_calc: bool
:return: Return date or int if used in Calc
:rtype: date or int
`See Python strptime <https://docs.python.org/3/library/datetime.html#datetime.datetime.strptime>`_
"""
d = datetime.datetime.strptime(str_date, template).date()
if to_calc:
d = d.toordinal() - DATE_OFFSET
return d
@classmethod
2022-02-26 00:11:41 -06:00
def calc_to_date(cls, value: float):
2022-02-25 22:43:37 -06:00
"""Get date from calc value
:param value: Float value from cell
:type value: float
:return: Return the current local date
:rtype: date
`See Python fromordinal <https://docs.python.org/3/library/datetime.html#datetime.datetime.fromordinal>`_
"""
d = datetime.date.fromordinal(int(value) + DATE_OFFSET)
return d
@classmethod
def start(cls):
"""Start counter
"""
cls._start = cls.now
info('Start: ', cls._start)
return
@classmethod
def end(cls, get_seconds: bool=True):
"""End counter
:param get_seconds: If return value in total seconds
:type get_seconds: bool
:return: Return the timedelta or total seconds
:rtype: timedelta or int
"""
e = cls.now
td = e - cls._start
result = str(td)
if get_seconds:
result = td.total_seconds()
info('End: ', e)
return result
2022-02-26 00:11:41 -06:00
class Json(object):
"""Class for json data
"""
@classmethod
def dumps(cls, data: Any) -> str:
"""Dumps
:param data: Any data
:type data: Any
:return: Return string json
:rtype: str
"""
return json.dumps(data, indent=4, sort_keys=True)
@classmethod
def loads(cls, data: str) -> Any:
"""Loads
:param data: String data
:type data: str
:return: Return any object
:rtype: Any
"""
return json.loads(data)
class Macro(object):
"""Class for call macro
`See Scripting Framework <https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification>`_
"""
@classmethod
def call(cls, args: dict, in_thread: bool=False):
"""Call any macro
:param args: Dictionary with macro location
:type args: dict
:param in_thread: If execute in thread
:type in_thread: bool
:return: Return None or result of call macro
:rtype: Any
"""
result = None
if in_thread:
t = threading.Thread(target=cls._call, args=(args,))
t.start()
else:
result = cls._call(args)
return result
@classmethod
def _get_url_script(cls, args: dict):
library = args['library']
name = args['name']
language = args.get('language', 'Python')
location = args.get('location', 'user')
module = args.get('module', '.')
if language == 'Python':
module = '.py$'
elif language == 'Basic':
module = f".{module}."
if location == 'user':
location = 'application'
url = 'vnd.sun.star.script'
url = f'{url}:{library}{module}{name}?language={language}&location={location}'
return url
@classmethod
def _call(cls, args: dict):
url = cls._get_url_script(args)
args = args.get('args', ())
service = 'com.sun.star.script.provider.MasterScriptProviderFactory'
factory = create_instance(service)
script = factory.createScriptProvider('').getScript(url)
result = script.invoke(args, None, None)[0]
return result
2022-02-26 22:22:11 -06:00
class Shell(object):
"""Class for subprocess
`See Subprocess <https://docs.python.org/3.7/library/subprocess.html>`_
"""
@classmethod
def run(cls, command, capture=False, split=False):
"""Execute commands
:param command: Command to run
:type command: str
:param capture: If capture result of command
:type capture: bool
:param split: Some commands need split.
:type split: bool
:return: Result of command
:rtype: Any
"""
if split:
cmd = shlex.split(command)
result = subprocess.run(cmd, capture_output=capture, text=True, shell=IS_WIN)
if capture:
result = result.stdout
else:
result = result.returncode
else:
if capture:
result = subprocess.check_output(command, shell=True).decode()
else:
result = subprocess.Popen(command)
return result
@classmethod
def popen(cls, command):
"""Execute commands and return line by line
:param command: Command to run
:type command: str
:return: Result of command
:rtype: Any
"""
try:
proc = subprocess.Popen(shlex.split(command), shell=IS_WIN,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in proc.stdout:
yield line.decode().rstrip()
except Exception as e:
error(e)
yield (e.errno, e.strerror)
class Timer(object):
"""Class for timer thread"""
class TimerThread(threading.Thread):
def __init__(self, event, seconds, macro):
threading.Thread.__init__(self)
self._event = event
self._seconds = seconds
self._macro = macro
def run(self):
while not self._event.wait(self._seconds):
Macro.call(self._macro)
info('\tTimer stopped... ')
return
@classmethod
def exists(cls, name):
"""Validate in timer **name** exists
:param name: Timer name, it must be unique
:type name: str
:return: True if exists timer name
:rtype: bool
"""
global _EVENTS
return name in _EVENTS
@classmethod
def start(cls, name: str, seconds: float, macro: dict):
"""Start timer **name** every **seconds** and execute **macro**
:param name: Timer name, it must be unique
:type name: str
:param seconds: Seconds for wait
:type seconds: float
:param macro: Macro for execute
:type macro: dict
"""
global _EVENTS
_EVENTS[name] = threading.Event()
info(f"Timer '{name}' started, execute macro: '{macro['name']}'")
thread = cls.TimerThread(_EVENTS[name], seconds, macro)
thread.start()
return
@classmethod
def stop(cls, name: str):
"""Stop timer **name**
:param name: Timer name
:type name: str
"""
global _EVENTS
_EVENTS[name].set()
del _EVENTS[name]
return
@classmethod
def once(cls, name: str, seconds: float, macro: dict):
"""Start timer **name** only once in **seconds** and execute **macro**
:param name: Timer name, it must be unique
:type name: str
:param seconds: Seconds for wait before execute macro
:type seconds: float
:param macro: Macro for execute
:type macro: dict
"""
global _EVENTS
_EVENTS[name] = threading.Timer(seconds, Macro.call, (macro,))
_EVENTS[name].start()
info(f'Event: "{name}", started... execute in {seconds} seconds')
return
@classmethod
def cancel(cls, name: str):
"""Cancel timer **name** only once events.
:param name: Timer name, it must be unique
:type name: str
"""
global _EVENTS
if name in _EVENTS:
try:
_EVENTS[name].cancel()
del _EVENTS[name]
info(f'Cancel event: "{name}", ok...')
except Exception as e:
error(e)
else:
debug(f'Cancel event: "{name}", not exists...')
return
class Hash(object):
"""Class for hash
"""
@classmethod
def digest(cls, method: str, data: str, in_hex: bool=True):
"""Get digest from data with method
:param method: Digest method: md5, sha1, sha256, sha512, etc...
:type method: str
:param data: Data for get digest
:type data: str
:param in_hex: If True, get digest in hexadecimal, if False, get bytes
:type in_hex: bool
:return: bytes or hex digest
:rtype: bytes or str
"""
result = ''
obj = getattr(hashlib, method)(data.encode())
if in_hex:
result = obj.hexdigest()
else:
result = obj.digest()
return result
class Paths(object):
"""Class for paths
"""
FILE_PICKER = 'com.sun.star.ui.dialogs.FilePicker'
FOLDER_PICKER = 'com.sun.star.ui.dialogs.FolderPicker'
REMOTE_FILE_PICKER = 'com.sun.star.ui.dialogs.RemoteFilePicker'
OFFICE_FILE_PICKER = 'com.sun.star.ui.dialogs.OfficeFilePicker'
def __init__(self, path=''):
if path.startswith('file://'):
path = str(Path(uno.fileUrlToSystemPath(path)).resolve())
self._path = Path(path)
@property
def path(self):
"""Get base path"""
return str(self._path.parent)
@property
def file_name(self):
"""Get file name"""
return self._path.name
@property
def name(self):
"""Get name"""
return self._path.stem
@property
def ext(self):
"""Get extension"""
return self._path.suffix[1:]
@property
def size(self):
"""Get size"""
return self._path.stat().st_size
@property
def url(self):
"""Get like URL"""
return self._path.as_uri()
@property
def info(self):
"""Get all info like tuple"""
i = (self.path, self.file_name, self.name, self.ext, self.size, self.url)
return i
@property
def dict(self):
"""Get all info like dict"""
data = {
'path': self.path,
'file_name': self.file_name,
'name': self.name,
'ext': self.ext,
'size': self.size,
'url': self.url,
}
return data
@_classproperty
def home(self):
"""Get user home"""
return str(Path.home())
@_classproperty
def documents(self):
"""Get user save documents"""
return self.config()
@_classproperty
def user_profile(self):
"""Get path user profile"""
path = self.config('UserConfig')
path = str(Path(path).parent)
return path
@_classproperty
def user_config(self):
"""Get path config in user profile"""
path = self.config('UserConfig')
return path
2022-02-27 23:28:06 -06:00
@_classproperty
def python(self):
"""Get path executable python"""
if IS_WIN:
path = self.join(self.config('Module'), PYTHON)
elif IS_MAC:
path = self.join(self.config('Module'), '..', 'Resources', PYTHON)
else:
path = sys.executable
return path
2022-02-26 22:22:11 -06:00
@classmethod
def to_system(cls, path:str) -> str:
"""Convert paths in URL to system
:param path: Path to convert
:type path: str
:return: Path system format
:rtype: str
"""
if path.startswith('file://'):
path = str(Path(uno.fileUrlToSystemPath(path)).resolve())
return path
@classmethod
def to_url(cls, path: str) -> str:
"""Convert paths in format system to URL
:param path: Path to convert
:type path: str
:return: Path in URL
:rtype: str
"""
if not path.startswith('file://'):
path = Path(path).as_uri()
return path
@classmethod
2022-02-27 23:28:06 -06:00
def config(cls, name: str='Work') -> Union[str, list]:
2022-02-26 22:22:11 -06:00
"""Return path from config
:param name: Name in service PathSettings, default get path documents
:type name: str
:return: Path in config, if exists.
2022-02-27 23:28:06 -06:00
:rtype: str or list
2022-02-26 22:22:11 -06:00
`See Api XPathSettings <http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1util_1_1XPathSettings.html>`_
"""
path = create_instance('com.sun.star.util.PathSettings')
2022-02-27 23:28:06 -06:00
path = cls.to_system(getattr(path, name)).split(';')
if len(path) == 1:
path = path[0]
2022-02-26 22:22:11 -06:00
return path
@classmethod
def join(cls, *paths: str) -> str:
"""Join paths
:param paths: Paths to join
:type paths: list
:return: New path with joins
:rtype: str
"""
path = str(Path(paths[0]).joinpath(*paths[1:]))
return path
@classmethod
def exists(cls, path: str) -> bool:
"""If exists path
:param path: Path for validate
:type path: str
:return: True if path exists, False if not.
:rtype: bool
"""
path = cls.to_system(path)
result = Path(path).exists()
return result
@classmethod
def exists_app(cls, name_app: str) -> bool:
"""If exists app in system
:param name_app: Name of application
:type name_app: str
:return: True if app exists, False if not.
:rtype: bool
"""
result = bool(shutil.which(name_app))
return result
2022-03-01 20:05:19 -06:00
@classmethod
def is_dir(cls, path: str):
"""Validate if path is directory
:param path: Path for validate
:type path: str
:return: True if path is directory, False if not.
:rtype: bool
"""
return Path(path).is_dir()
@classmethod
def is_file(cls, path: str):
"""Validate if path is a file
:param path: Path for validate
:type path: str
:return: True if path is a file, False if not.
:rtype: bool
"""
return Path(path).is_file()
2022-02-28 19:02:42 -06:00
@classmethod
def temp_file(self):
"""Make temporary file"""
return tempfile.NamedTemporaryFile(mode='w')
@classmethod
def temp_dir(self):
"""Make temporary directory"""
return tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
@classmethod
def get(cls, init_dir: str='', filters: str='') -> str:
"""Get path for save
:param init_dir: Initial default path
:type init_dir: str
:param filters: Filter for show type files: 'xml' or 'txt,xml'
:type filters: str
:return: Selected path
:rtype: str
`See API <https://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html>`_
"""
if not init_dir:
init_dir = cls.documents
init_dir = cls.to_url(init_dir)
file_picker = create_instance(cls.FILE_PICKER)
file_picker.setTitle(_('Select path'))
file_picker.setDisplayDirectory(init_dir)
file_picker.initialize((TemplateDescription.FILEOPEN_SIMPLE,))
if filters:
for f in filters.split(','):
file_picker.appendFilter(f.upper(), f'*.{f.lower()}')
path = ''
if file_picker.execute():
path = cls.to_system(file_picker.getSelectedFiles()[0])
return path
@classmethod
def get_dir(cls, init_dir: str='') -> str:
"""Get path dir
:param init_dir: Initial default path
:type init_dir: str
:return: Selected path
:rtype: str
"""
folder_picker = create_instance(cls.FOLDER_PICKER)
if not init_dir:
init_dir = cls.documents
init_dir = cls.to_url(init_dir)
folder_picker.setTitle(_('Select directory'))
folder_picker.setDisplayDirectory(init_dir)
path = ''
if folder_picker.execute():
path = cls.to_system(folder_picker.getDirectory())
return path
@classmethod
def get_file(cls, init_dir: str='', filters: str='', multiple: bool=False):
2022-03-01 20:05:19 -06:00
"""Get path exists file
2022-02-28 19:02:42 -06:00
2022-03-01 20:05:19 -06:00
:param init_dir: Initial default path
:type init_dir: str
:param filters: Filter for show type files: 'xml' or 'txt,xml'
:type filters: str
:param multiple: If user can selected multiple files
:type multiple: bool
:return: Selected path
:rtype: str
2022-02-28 19:02:42 -06:00
"""
if not init_dir:
init_dir = cls.documents
init_dir = cls.to_url(init_dir)
file_picker = create_instance(cls.FILE_PICKER)
file_picker.setTitle(_('Select file'))
file_picker.setDisplayDirectory(init_dir)
file_picker.setMultiSelectionMode(multiple)
if filters:
2022-03-01 20:05:19 -06:00
for f in filters.split(','):
file_picker.appendFilter(f.upper(), f'*.{f.lower()}')
2022-02-28 19:02:42 -06:00
path = ''
if file_picker.execute():
files = file_picker.getSelectedFiles()
path = [cls.to_system(f) for f in files]
if not multiple:
path = path[0]
return path
2022-03-01 20:05:19 -06:00
@classmethod
def files(cls, path: str, pattern: str='*'):
"""Get all files in path
:param path: Path with files
:type path: str
:param pattern: For filter files, default get all.
:type pattern: str
:return: Files in path
:rtype: list
"""
files = [str(p) for p in Path(path).glob(pattern) if p.is_file()]
return files
@classmethod
def walk(cls, path, filters=''):
"""Get all files in path recursively
:param path: Path with files
:type path: str
:param filters: For filter files, default get all.
:type filters: str
:return: Files in path
:rtype: list
"""
paths = []
for folder, _, files in os.walk(path):
if filters:
pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE)
paths += [cls.join(folder, f) for f in files if pattern.search(f)]
else:
paths += [cls.join(folder, f) for f in files]
return paths
2022-03-02 14:44:58 -06:00
@classmethod
def dirs(cls, path):
"""Get directories in path
:param path: Path to scan
:type path: str
:return: Directories in path
:rtype: list
"""
dirs = [str(p) for p in Path(path).iterdir() if p.is_dir()]
return dirs
2022-03-01 20:05:19 -06:00
@classmethod
def walk_dirs(cls, path, tree=False):
"""Get directories recursively
:param path: Path to scan
:type path: str
:param tree: get info in a tuple (ID_FOLDER, ID_PARENT, NAME)
:type tree: bool
:return: Directories in path
:rtype: list
"""
folders = []
if tree:
i = 0
parents = {path: 0}
for root, dirs, _ in os.walk(path):
for name in dirs:
i += 1
rn = cls.join(root, name)
if not rn in parents:
parents[rn] = i
folders.append((i, parents[root], name))
else:
for root, dirs, _ in os.walk(path):
folders += [cls.join(root, name) for name in dirs]
return folders
@classmethod
def ext(cls, id_ext: str):
"""Get path extension install from id
:param id_ext: ID extension
:type id_ext: str
:return: Path extension
:rtype: str
"""
pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider')
path = Paths.to_system(pip.getPackageLocation(id_ext))
return path
@classmethod
def replace_ext(cls, path: str, new_ext: str):
"""Replace extension in file path
:param path: Path to file
:type path: str
:param new_ext: New extension
:type new_ext: str
:return: Path with new extension
:rtype: str
"""
p = Paths(path)
name = f'{p.name}.{new_ext}'
path = cls.join(p.path, name)
return path
@classmethod
def open(cls, path: str):
"""Open any file with default program in systema
:param path: Path to file
:type path: str
:return: PID file, only Linux
:rtype: int
"""
pid = 0
if IS_WIN:
os.startfile(path)
else:
pid = subprocess.Popen(['xdg-open', path]).pid
return pid
2022-02-27 23:28:06 -06:00
# ~ Save/read data
@classmethod
def save(cls, path: str, data: str, encoding: str='utf-8') -> bool:
"""Save data in path with encoding
:param path: Path to file save
:type path: str
:param data: Data to save
:type data: str
:param encoding: Encoding for save data, default utf-8
:type encoding: str
:return: True, if save corrrectly
:rtype: bool
"""
result = bool(Path(path).write_text(data, encoding=encoding))
return result
@classmethod
def save_bin(cls, path: str, data: bytes) -> bool:
"""Save binary data in path
:param path: Path to file save
:type path: str
:param data: Data to save
:type data: bytes
:return: True, if save corrrectly
:rtype: bool
"""
result = bool(Path(path).write_bytes(data))
return result
@classmethod
def read(cls, path: str, get_lines: bool=False, encoding: str='utf-8') -> Union[str, list]:
"""Read data in path
:param path: Path to file read
:type path: str
:param get_lines: If read file line by line
:type get_lines: bool
:return: File content
:rtype: str or list
"""
if get_lines:
with Path(path).open(encoding=encoding) as f:
data = f.readlines()
else:
data = Path(path).read_text(encoding=encoding)
return data
@classmethod
def read_bin(cls, path: str) -> bytes:
"""Read binary data in path
:param path: Path to file read
:type path: str
:return: File content
:rtype: bytes
"""
data = Path(path).read_bytes()
return data
# ~ Import/export data
@classmethod
def from_json(cls, path: str) -> Any:
"""Read path file and load json data
:param path: Path to file
:type path: str
:return: Any data
:rtype: Any
"""
data = json.loads(cls.read(path))
return data
@classmethod
def to_json(cls, path: str, data: str):
"""Save data in path file like json
:param path: Path to file
:type path: str
:return: True if save correctly
:rtype: bool
"""
data = json.dumps(data, indent=4, ensure_ascii=False, sort_keys=True)
return cls.save(path, data)
2022-03-01 20:05:19 -06:00
@classmethod
def from_csv(cls, path: str, args: dict={}) -> tuple:
"""Read CSV
:param path: Path to file csv
:type path: str
:param args: Any argument support for Python library
:type args: dict
:return: Data csv like tuple
:rtype: tuple
`See CSV Reader <https://docs.python.org/3.8/library/csv.html#csv.reader>`_
"""
with open(path) as f:
rows = tuple(csv.reader(f, **args))
return rows
@classmethod
def to_csv(cls, path: str, data: Any, args: dict={}):
"""Write CSV
:param path: Path to file write csv
:type path: str
:param data: Data to write
:type data: Iterable
:param args: Any argument support for Python library
:type args: dict
`See CSV Writer <https://docs.python.org/3.8/library/csv.html#csv.writer>`_
"""
with open(path, 'w') as f:
writer = csv.writer(f, **args)
writer.writerows(data)
return
@classmethod
def zip(cls, source: Union[str, tuple, list], target='') -> str:
path_zip = target
if not isinstance(source, (tuple, list)):
path, _, name, _ = _P(source).info
start = len(path) + 1
if not target:
path_zip = f'{path}/{name}.zip'
if isinstance(source, (tuple, list)):
files = [(f, f[len(_P(f).path)+1:]) for f in source]
elif _P.is_file(source):
files = ((source, source[start:]),)
else:
files = [(f, f[start:]) for f in _P.walk(source)]
compression = zipfile.ZIP_DEFLATED
with zipfile.ZipFile(path_zip, 'w', compression=compression) as z:
for f in files:
z.write(f[0], f[1])
return path_zip
@classmethod
def unzip(cls, source, target='', members=None, pwd=None):
path = target
if not target:
path = _P(source).path
with zipfile.ZipFile(source) as z:
if not pwd is None:
pwd = pwd.encode()
if isinstance(members, str):
members = (members,)
z.extractall(path, members=members, pwd=pwd)
return
@classmethod
def zip_content(cls, path: str):
with zipfile.ZipFile(path) as z:
names = z.namelist()
return names
@classmethod
def merge_zip(cls, target, zips):
try:
with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t:
for path in zips:
with zipfile.ZipFile(path, compression=zipfile.ZIP_DEFLATED) as s:
for name in s.namelist():
t.writestr(name, s.open(name).read())
except Exception as e:
error(e)
return False
return True
@classmethod
def kill(cls, path: str):
"""Delete path
:param path: Path to file or directory
:type path: str
:return: True if delete correctly
:rtype: bool
"""
p = Path(path)
try:
if p.is_file():
p.unlink()
elif p.is_dir():
shutil.rmtree(path)
result = True
except OSError as e:
log.error(e)
result = False
return result
2022-02-27 23:28:06 -06:00
class Config(object):
"""Class for set and get configurations
"""
@classmethod
def set(cls, prefix: str, value: Any, key: str='') -> bool:
"""Save data config in user config like json
:param prefix: Unique prefix for this data
:type prefix: str
:param value: Value for save
:type value: Any
:param key: Key for value
:type key: str
:return: True if save correctly
:rtype: bool
"""
name_file = FILES['CONFIG'].format(prefix)
path = Paths.join(Paths.user_config, name_file)
data = value
if key:
data = cls.get(prefix)
data[key] = value
result = Paths.to_json(path, data)
return result
@classmethod
def get(cls, prefix: str, key: str='', default: Any={}) -> Any:
"""Get data config from user config like json
:param prefix: Unique prefix for this data
:type prefix: str
:param key: Key for value
:type key: str
:param default: Get if not exists key
:type default: Any
:return: data
:rtype: Any
"""
data = {}
name_file = FILES['CONFIG'].format(prefix)
path = Paths.join(Paths.user_config, name_file)
if not Paths.exists(path):
return data
data = Paths.from_json(path)
if key:
data = data.get(key, default)
return data
class Url(object):
"""Class for simple url open
"""
@classmethod
def _open(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
json: bool=False, timeout: int=TIMEOUT, method: str='GET') -> tuple:
"""URL Open"""
debug(url)
result = None
context = None
rheaders = {}
err = ''
if verify:
if not data is None and isinstance(data, str):
data = data.encode()
else:
context = ssl._create_unverified_context()
try:
req = Request(url, data=data, headers=headers, method=method)
response = urlopen(req, timeout=timeout, context=context)
except HTTPError as e:
error(e)
err = str(e)
except URLError as e:
error(e.reason)
err = str(e.reason)
# ToDo
# ~ except timeout:
# ~ err = 'timeout'
# ~ error(err)
else:
rheaders = dict(response.info())
result = response.read().decode()
if json:
result = Json.loads(result)
return result, rheaders, err
@classmethod
def get(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
json: bool=False, timeout: int=TIMEOUT) -> tuple:
"""Method GET
:param url: Url to open
:type url: str
:return: result, headers and error
:rtype: tuple
"""
return cls._open(url, data, headers, verify, json, timeout)
# ToDo
@classmethod
def _post(cls, url: str, data: Any=None, headers: dict={}, verify: bool=True, \
json: bool=False, timeout: int=TIMEOUT) -> tuple:
"""Method POST
"""
data = parse.urlencode(data).encode('ascii')
return cls._open(url, data, headers, verify, json, timeout, 'POST')
2022-03-01 21:26:27 -06:00
class Email(object):
"""Class for send email
"""
2022-03-02 14:13:47 -06:00
class _SmtpServer(object):
2022-03-01 21:26:27 -06:00
def __init__(self, config):
self._server = None
self._error = ''
self._sender = ''
self._is_connect = self._login(config)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
@property
def is_connect(self):
return self._is_connect
@property
def error(self):
return self._error
def _login(self, config):
name = config['server']
port = config['port']
is_ssl = config['ssl']
2022-03-02 14:13:47 -06:00
starttls = config.get('starttls', False)
2022-03-01 21:26:27 -06:00
self._sender = config['user']
try:
2022-03-02 14:13:47 -06:00
if starttls:
2022-03-01 21:26:27 -06:00
self._server = smtplib.SMTP(name, port, timeout=TIMEOUT)
self._server.ehlo()
self._server.starttls()
self._server.ehlo()
elif is_ssl:
self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT)
self._server.ehlo()
else:
self._server = smtplib.SMTP(name, port, timeout=TIMEOUT)
self._server.login(self._sender, config['password'])
msg = 'Connect to: {}'.format(name)
debug(msg)
return True
except smtplib.SMTPAuthenticationError as e:
if '535' in str(e):
self._error = _('Incorrect user or password')
return False
if '534' in str(e) and 'gmail' in name:
self._error = _('Allow less secure apps in GMail')
return False
except smtplib.SMTPException as e:
self._error = str(e)
return False
except Exception as e:
self._error = str(e)
return False
return False
def _body(self, msg):
body = msg.replace('\n', '<BR>')
return body
def send(self, message):
# ~ file_name = 'attachment; filename={}'
email = MIMEMultipart()
email['From'] = self._sender
email['To'] = message['to']
email['Cc'] = message.get('cc', '')
email['Subject'] = message['subject']
email['Date'] = formatdate(localtime=True)
if message.get('confirm', False):
email['Disposition-Notification-To'] = email['From']
email.attach(MIMEText(self._body(message['body']), 'html'))
paths = message.get('files', ())
if isinstance(paths, str):
paths = (paths,)
for path in paths:
fn = _P(path).file_name
2022-03-02 14:13:47 -06:00
# ~ print('NAME', fn)
2022-03-01 21:26:27 -06:00
part = MIMEBase('application', 'octet-stream')
part.set_payload(_P.read_bin(path))
encoders.encode_base64(part)
part.add_header('Content-Disposition', f'attachment; filename="{fn}"')
email.attach(part)
receivers = (
email['To'].split(',') +
email['CC'].split(',') +
message.get('bcc', '').split(','))
try:
self._server.sendmail(self._sender, receivers, email.as_string())
msg = 'Email sent...'
debug(msg)
if message.get('path', ''):
self.save_message(email, message['path'])
return True
except Exception as e:
self._error = str(e)
return False
return False
def save_message(self, email, path):
mbox = mailbox.mbox(path, create=True)
mbox.lock()
try:
msg = mailbox.mboxMessage(email)
mbox.add(msg)
mbox.flush()
finally:
mbox.unlock()
return
def close(self):
try:
self._server.quit()
msg = 'Close connection...'
debug(msg)
except:
pass
return
@classmethod
def _send_email(cls, server, messages):
2022-03-02 14:13:47 -06:00
with cls._SmtpServer(server) as server:
2022-03-01 21:26:27 -06:00
if server.is_connect:
for msg in messages:
server.send(msg)
else:
error(server.error)
return server.error
@classmethod
def send(cls, server: dict, messages: Union[dict, tuple, list]):
2022-03-02 14:13:47 -06:00
"""Send email with config server, emails send in thread.
:param server: Configuration for send emails
:type server: dict
:param server: Dictionary con message or list of messages
:type server: dict or iterator
"""
2022-03-01 21:26:27 -06:00
if isinstance(messages, dict):
messages = (messages,)
t = threading.Thread(target=cls._send_email, args=(server, messages))
t.start()
return
2022-02-27 23:28:06 -06:00
class Color(object):
"""Class for colors
`See Web Colors <https://en.wikipedia.org/wiki/Web_colors>`_
"""
COLORS = {
'aliceblue': 15792383,
'antiquewhite': 16444375,
'aqua': 65535,
'aquamarine': 8388564,
'azure': 15794175,
'beige': 16119260,
'bisque': 16770244,
'black': 0,
'blanchedalmond': 16772045,
'blue': 255,
'blueviolet': 9055202,
'brown': 10824234,
'burlywood': 14596231,
'cadetblue': 6266528,
'chartreuse': 8388352,
'chocolate': 13789470,
'coral': 16744272,
'cornflowerblue': 6591981,
'cornsilk': 16775388,
'crimson': 14423100,
'cyan': 65535,
'darkblue': 139,
'darkcyan': 35723,
'darkgoldenrod': 12092939,
'darkgray': 11119017,
'darkgreen': 25600,
'darkgrey': 11119017,
'darkkhaki': 12433259,
'darkmagenta': 9109643,
'darkolivegreen': 5597999,
'darkorange': 16747520,
'darkorchid': 10040012,
'darkred': 9109504,
'darksalmon': 15308410,
'darkseagreen': 9419919,
'darkslateblue': 4734347,
'darkslategray': 3100495,
'darkslategrey': 3100495,
'darkturquoise': 52945,
'darkviolet': 9699539,
'deeppink': 16716947,
'deepskyblue': 49151,
'dimgray': 6908265,
'dimgrey': 6908265,
'dodgerblue': 2003199,
'firebrick': 11674146,
'floralwhite': 16775920,
'forestgreen': 2263842,
'fuchsia': 16711935,
'gainsboro': 14474460,
'ghostwhite': 16316671,
'gold': 16766720,
'goldenrod': 14329120,
'gray': 8421504,
'grey': 8421504,
'green': 32768,
'greenyellow': 11403055,
'honeydew': 15794160,
'hotpink': 16738740,
'indianred': 13458524,
'indigo': 4915330,
'ivory': 16777200,
'khaki': 15787660,
'lavender': 15132410,
'lavenderblush': 16773365,
'lawngreen': 8190976,
'lemonchiffon': 16775885,
'lightblue': 11393254,
'lightcoral': 15761536,
'lightcyan': 14745599,
'lightgoldenrodyellow': 16448210,
'lightgray': 13882323,
'lightgreen': 9498256,
'lightgrey': 13882323,
'lightpink': 16758465,
'lightsalmon': 16752762,
'lightseagreen': 2142890,
'lightskyblue': 8900346,
'lightslategray': 7833753,
'lightslategrey': 7833753,
'lightsteelblue': 11584734,
'lightyellow': 16777184,
'lime': 65280,
'limegreen': 3329330,
'linen': 16445670,
'magenta': 16711935,
'maroon': 8388608,
'mediumaquamarine': 6737322,
'mediumblue': 205,
'mediumorchid': 12211667,
'mediumpurple': 9662683,
'mediumseagreen': 3978097,
'mediumslateblue': 8087790,
'mediumspringgreen': 64154,
'mediumturquoise': 4772300,
'mediumvioletred': 13047173,
'midnightblue': 1644912,
'mintcream': 16121850,
'mistyrose': 16770273,
'moccasin': 16770229,
'navajowhite': 16768685,
'navy': 128,
'oldlace': 16643558,
'olive': 8421376,
'olivedrab': 7048739,
'orange': 16753920,
'orangered': 16729344,
'orchid': 14315734,
'palegoldenrod': 15657130,
'palegreen': 10025880,
'paleturquoise': 11529966,
'palevioletred': 14381203,
'papayawhip': 16773077,
'peachpuff': 16767673,
'peru': 13468991,
'pink': 16761035,
'plum': 14524637,
'powderblue': 11591910,
'purple': 8388736,
'red': 16711680,
'rosybrown': 12357519,
'royalblue': 4286945,
'saddlebrown': 9127187,
'salmon': 16416882,
'sandybrown': 16032864,
'seagreen': 3050327,
'seashell': 16774638,
'sienna': 10506797,
'silver': 12632256,
'skyblue': 8900331,
'slateblue': 6970061,
'slategray': 7372944,
'slategrey': 7372944,
'snow': 16775930,
'springgreen': 65407,
'steelblue': 4620980,
'tan': 13808780,
'teal': 32896,
'thistle': 14204888,
'tomato': 16737095,
'turquoise': 4251856,
'violet': 15631086,
'wheat': 16113331,
'white': 16777215,
'whitesmoke': 16119285,
'yellow': 16776960,
'yellowgreen': 10145074,
}
def _get_color(self, index):
if isinstance(index, tuple):
color = (index[0] << 16) + (index[1] << 8) + index[2]
else:
if index[0] == '#':
r, g, b = bytes.fromhex(index[1:])
color = (r << 16) + (g << 8) + b
else:
color = self.COLORS.get(index.lower(), -1)
return color
def __call__(self, index):
return self._get_color(index)
def __getitem__(self, index):
return self._get_color(index)
COLOR_ON_FOCUS = Color()('LightYellow')
2022-02-26 22:22:11 -06:00
2022-02-25 22:43:37 -06:00
def __getattr__(name):
classes = {
2022-02-26 00:11:41 -06:00
'dates': Dates,
'json': Json,
'macro': Macro,
2022-02-26 22:22:11 -06:00
'shell': Shell,
'timer': Timer,
'hash': Hash,
'path': Paths,
2022-02-27 23:28:06 -06:00
'config': Config,
'url': Url,
2022-03-01 21:26:27 -06:00
'email': Email,
2022-02-27 23:28:06 -06:00
'color': Color(),
2022-02-25 22:43:37 -06:00
}
if name in classes:
return classes[name]
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
2022-02-24 23:59:04 -06:00
2022-02-21 23:43:58 -06:00
class LOServer(object):
2022-02-25 22:43:37 -06:00
"""Started LibeOffice like server
"""
2022-02-21 23:43:58 -06:00
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):
2022-02-25 22:43:37 -06:00
"""Stop server
"""
2022-02-21 23:43:58 -06:00
if self._server is None:
print('Search pgrep soffice')
else:
self._server.terminate()
debug('LibreOffice is stop...')
return
2022-02-25 22:43:37 -06:00
def _create_instance(self, name, with_context=True):
2022-02-21 23:43:58 -06:00
if with_context:
instance = self._sm.createInstanceWithContext(name, self._ctx)
else:
instance = self._sm.createInstance(name)
return instance