diff options
author | Jean-Pierre Ledure <jp@ledure.be> | 2021-03-14 13:03:16 +0100 |
---|---|---|
committer | Jean-Pierre Ledure <jp@ledure.be> | 2021-03-14 14:43:03 +0100 |
commit | 082d4741fe981504bafef5ebe302e8263ef3719d (patch) | |
tree | e671ddb974a83da58555f6122dd3671770f9f2fb /wizards | |
parent | 351edb44eb0548f7e56464de42c1758a1f5e4ab4 (diff) |
ScriptForge - (scriptforge.py) enhance property and method naming
To make the access to services more close to Python habits
and to better attract Basic users to Python, the convention
for attributes naming has been enhanced:
- property names may be ProperCased, camelCased or lowercased
- method names may be ProperCased, camelCased or lowercased
Their arguments are always lowercased
In documentation examples, the camelCased notation will be privileged
Synonyms for properties are managed with generic functions.
The generic getter and setter has been reviewed accordingly.
Method synonyms are hardcoded.
The management of synonyms for servicenames have also been
reviewed to be defined in each class i.o. centrally and hardcoded
in CreateScriptService()
Change-Id: Id8d3181118b1e61937e2412109bd0d1eedd6f5e6
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/112470
Tested-by: Jean-Pierre Ledure <jp@ledure.be>
Tested-by: Jenkins
Reviewed-by: Jean-Pierre Ledure <jp@ledure.be>
Diffstat (limited to 'wizards')
-rw-r--r-- | wizards/source/scriptforge/python/scriptforge.py | 221 |
1 files changed, 159 insertions, 62 deletions
diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py index 2941c0db1e8b..93335098bf8b 100644 --- a/wizards/source/scriptforge/python/scriptforge.py +++ b/wizards/source/scriptforge/python/scriptforge.py @@ -304,42 +304,49 @@ class SFServices(object): A service instance is created by the CreateScriptService method It can have a mirror in the Basic world or be totally defined in Python - Every subclass must initialize 2 class properties: - servicename (e.g. ScriptForge.FileSystem, ScriptForge.Basic) + Every subclass must initialize 3 class properties: + servicename (e.g. 'ScriptForge.FileSystem', 'ScriptForge.Basic') + servicesynonyms (e.g. 'FileSystem', 'Basic') serviceimplementation: either 'python' or 'basic' - This is sufficient to register the set of services in the Python world + This is sufficient to register the service in the Python world The communication with Basic is managed by 2 ScriptForge() methods: InvokeSimpleScript(): low level invocation of a Basic script. This script must be located in a usual Basic module. The result is passed as-is - InvokeSBasicService(): the result comes back encapsulated with additional info + InvokeBasicService(): the result comes back encapsulated with additional info The result is interpreted in the method - The invoked script can be a property or a method of a Basic class module + The invoked script can be a property or a method of a Basic class or usual module It is up to every service method to determine which method to use For Basic services only: Each instance is identified by its - object reference: the real Basic object embedded as a UNO wrapper object - - objecttype ('SF_String', 'DICTIONARY', ...) + - object type ('SF_String', 'DICTIONARY', ...) + - class module: 1 for usual modules, 2 for class modules - name (form, control, ... name) - may be blank The role of the Service() superclass is mainly to propose a generic properties management Properties are got and set following next strategy: - 1. Property names are controlled strictly ('Value' and not 'value') + 1. Property names are controlled strictly ('Value' or 'value', not 'VALUE') 2. Getting a property value for the first time is always done via a Basic call 3. Next occurrences are fetched from the Python dictionary of the instance if the property - is read-only, otherwise via a Basic call + is read-only, otherwise via a Basic call 4. Read-only properties may be modified or deleted exceptionally by the class when self.internal == True. The latter must immediately be reset after use Each subclass must define its interface with the user scripts: 1. The properties - a dictionary named 'serviceProperties' with keys = (camel-cased) property names and value = boolean + Property names are proper-cased + Conventionally, camel-cased and lower-cased synonyms are supported where relevant + a dictionary named 'serviceproperties' with keys = (proper-cased) property names and value = boolean True = editable, False = read-only - a list named 'localProperties' reserved to properties for internal use + a list named 'localProperties' reserved to properties for internal use e.g. oDlg.Controls() is a method that uses '_Controls' to hold the list of available controls - serviceProperties are buffered in Python after their 1st get request to Basic - Only if there is a need to go to Basic at each get, then declare the property explicitly: + When + forceGetProperty = False # Standard bahaviour + read-only serviceproperties are buffered in Python after their 1st get request to Basic + Otherwise set it to True to force a recomputation at each property getter invocation + If there is a need to handle a specific property in a specific manner: @property def myProperty(self): return self.GetProperty('myProperty') @@ -347,7 +354,8 @@ class SFServices(object): a usual def: statement def myMethod(self, arg1, arg2 = ''): return self.Execute(self.vbMethod, 'myMethod', arg1, arg2) - Method names are camel-cased, arguments are lower-cased + Method names are proper-cased, arguments are lower-cased + Conventionally, camel-cased and lower-cased homonyms are supported where relevant All arguments must be present and initialized before the call to Basic, if any """ # Python-Basic protocol constants and flags @@ -359,6 +367,10 @@ class SFServices(object): # Basic class type moduleClass, moduleStandard = 2, 1 # + # Define the default behaviour for read-only properties: buffer their values in Python + forceGetProperty = False + # Empty dictionary for lower/camelcased homonyms or properties + propertysynonyms = {} # To operate dynamic property getting/setting it is necessary to # enumerate all types of properties and adapt __getattr__() and __setattr__() according to their type internal_attributes = ('objectreference', 'objecttype', 'name', 'internal', 'servicename', @@ -383,30 +395,46 @@ class SFServices(object): """ Executed for EVERY property reference if name not yet in the instance dict At the 1st get, the property value is always got from Basic + Due to the use of lower/camelcase synonyms, it is called for each variant of the same property + The method manages itself the buffering in __dict__ based on the official ProperCase property name """ + if name in self.propertysynonyms: # Reset real name if argument provided in lower or camel case + name = self.propertysynonyms[name] if self.serviceimplementation == 'basic': - if name in ('serviceProperties', 'localProperties', 'internal_attributes'): + if name in ('serviceproperties', 'localProperties', 'internal_attributes', 'propertysynonyms', + 'forceGetProperty'): pass - elif name in self.serviceProperties: - # Get Property from Basic - return self.GetProperty(name) + elif name in self.serviceproperties: + if self.forceGetProperty is False and self.serviceproperties[name] is False: # False = read-only + if name in self.__dict__: + return self.__dict__[name] + else: + # Get Property from Basic + prop = self.GetProperty(name) + self.__dict__[name] = prop + return prop + else: + return self.GetProperty(name) # Execute the usual attributes getter return super(SFServices, self).__getattribute__(name) def __setattr__(self, name, value): """ Executed for EVERY property assignment, including in __init__() !! - Setting a property requires for serviceProperties() to be executed in Basic + Setting a property requires for serviceproperties() to be executed in Basic + Management of __dict__ is automatically done in the final usual object.__setattr__ method """ if self.serviceimplementation == 'basic': - if name in ('serviceProperties', 'localProperties', 'internal_attributes'): + if name in ('serviceproperties', 'localProperties', 'internal_attributes', 'propertysynonyms'): pass elif name[0:2] == '__' or name in self.internal_attributes or name in self.localProperties: pass - elif name in self.serviceProperties: + elif name in self.serviceproperties or name in self.propertysynonyms: + if name in self.propertysynonyms: # Reset real name if argument provided in lower or camel case + name = self.propertysynonyms[name] if self.internal: # internal = True forces property local setting even if property is read-only pass - elif self.serviceProperties[name] is True: # True == Editable + elif self.serviceproperties[name] is True: # True == Editable self.SetProperty(name, value) else: raise AttributeError( @@ -420,11 +448,30 @@ class SFServices(object): return self.serviceimplementation + '/' + self.servicename + '/' + str(self.objectreference) + '/' + \ super(SFServices, self).__repr__() + @staticmethod + def _getAttributeSynonyms(dico): + """ + Returns a dictionary with key = name in lower case and in camelCase, value = real ProperCased name + Example: + d = dict(ConfigFolder = False, InstallFolder = False) + dh = _getHomonyms(d) + # dh == dict(configfolder = 'ConfigFolder', installfolder = 'InstallFolder', + configFolder = 'ConfigFolder', installFolder = 'InstallFolder') + """ + def camelCase(key): + return key[0].lower() + key[1:] + + lc = dict(zip(map(str.casefold, dico.keys()), dico.keys())) + cc = dict(zip(map(camelCase, dico.keys()), dico.keys())) + lc.update(cc) + return lc + def Dispose(self): if self.serviceimplementation == 'basic': - if self.objectreference >= 0: + if self.objectreference >= len(ScriptForge.servicesmodules): # Do not dispose predefined module objects self.Execute(self.vbMethod, 'Dispose') self.objectreference = -1 + dispose = Dispose def Execute(self, flags = 0, methodname = '', *args): if flags == 0: @@ -436,16 +483,21 @@ class SFServices(object): """ Get the given property from the Basic world """ - return self.EXEC(self.objectreference, self.vbGet, propertyname) + if self.serviceimplementation == 'basic': + return self.EXEC(self.objectreference, self.vbGet, propertyname) + getProperty, getproperty = GetProperty, GetProperty def Properties(self): - return list(self.serviceProperties) + return list(self.serviceproperties) + properties = Properties def SetProperty(self, propertyname, value): """ Set the given property to a new value in the Basic world """ - return self.EXEC(self.objectreference, self.vbLet, propertyname, value) + if self.serviceimplementation == 'basic': + return self.EXEC(self.objectreference, self.vbLet, propertyname, value) + setProperty, setproperty = SetProperty, SetProperty # ##################################################################################################################### @@ -465,6 +517,7 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'python' servicename = 'ScriptForge.Basic' + servicesynonyms = ('basic', 'scriptforge.basic') # Basic helper functions invocation module = 'SF_PythonHelper' # Message box constants @@ -475,18 +528,22 @@ class SFScriptForge: def ConvertFromUrl(self, filename): return self.SIMPLEEXEC(self.module + '.PyConvertFromUrl', filename) + convertFromUrl, convertfromurl = ConvertFromUrl, ConvertFromUrl def ConvertToUrl(self, filename): return self.SIMPLEEXEC(self.module + '.PyConvertToUrl', filename) + convertToUrl, converttourl = ConvertToUrl, ConvertToUrl def CreateUnoService(self, unoservice): return self.SIMPLEEXEC(self.module + '.PyCreateUnoService', unoservice) + createUnoService, createunoservice = CreateUnoService, CreateUnoService def DateAdd(self, add, count, datearg): if isinstance(datearg, datetime.datetime): datearg = datearg.isoformat() dateadd = self.SIMPLEEXEC(self.module + '.PyDateAdd', add, count, datearg) return datetime.datetime.fromisoformat(dateadd) + dateAdd, dateadd = DateAdd, DateAdd def DateDiff(self, add, date1, date2, weekstart = 1, yearstart = 1): if isinstance(date1, datetime.datetime): @@ -494,36 +551,44 @@ class SFScriptForge: if isinstance(date2, datetime.datetime): date2 = date2.isoformat() return self.SIMPLEEXEC(self.module + '.PyDateDiff', add, date1, date2, weekstart, yearstart) + dateDiff, datediff = DateDiff, DateDiff def DatePart(self, add, datearg, weekstart = 1, yearstart = 1): if isinstance(datearg, datetime.datetime): datearg = datearg.isoformat() return self.SIMPLEEXEC(self.module + '.PyDatePart', add, datearg, weekstart, yearstart) + datePart, datepart = DatePart, DatePart def DateValue(self, datearg): if isinstance(datearg, datetime.datetime): datearg = datearg.isoformat() datevalue = self.SIMPLEEXEC(self.module + '.PyDateValue', datearg) return datetime.datetime.fromisoformat(datevalue) + dateValue, datevalue = DateValue, DateValue def Format(self, value, pattern = ''): if isinstance(value, datetime.datetime): value = value.isoformat() return self.SIMPLEEXEC(self.module + '.PyFormat', value, pattern) + format = Format @staticmethod def GetDefaultContext(): return ScriptForge.componentcontext + getDefaultContext, getdefaultcontext = GetDefaultContext, GetDefaultContext def GetGuiType(self): return self.SIMPLEEXEC(self.module + '.PyGetGuiType') + getGuiType, getguitype = GetGuiType, GetGuiType def GetSystemTicks(self): return self.SIMPLEEXEC(self.module + '.PyGetSystemTicks') + getSystemTicks, getsystemticks = GetSystemTicks, GetSystemTicks @staticmethod def GetPathSeparator(): return os.sep + getPathSeparator, getpathseparator = GetPathSeparator, GetPathSeparator class GlobalScope(object, metaclass = _Singleton): @classmethod # Mandatory because the GlobalScope class is normally not instantiated @@ -538,17 +603,21 @@ class SFScriptForge: if xpos < 0 or ypos < 0: return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, title, default) return self.SIMPLEEXEC(self.module + '.PyInputBox', msg, title, default, xpos, ypos) + inputBox, inputbox = InputBox, InputBox def MsgBox(self, text, dialogtype = 0, dialogtitle = ''): return self.SIMPLEEXEC(self.module + '.PyMsgBox', text, dialogtype, dialogtitle) + msgBox, msgbox = MsgBox, MsgBox @staticmethod def Now(): return datetime.datetime.now() + now = Now @staticmethod def RGB(red, green, blue): return int('%02x%02x%02x' % (red, green, blue), 16) + rgb = RGB @staticmethod def StarDesktop(): @@ -559,9 +628,11 @@ class SFScriptForge: DESK = 'com.sun.star.frame.Desktop' desktop = smgr.createInstanceWithContext(DESK, ctx) return desktop + starDesktop, stardesktop = StarDesktop, StarDesktop def Xray(self, unoobject = None): return self.SIMPLEEXEC('XrayTool._main.xray', unoobject) + xray = Xray # ######################################################################### # SF_String CLASS @@ -575,6 +646,7 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.String' + servicesynonyms = () # ######################################################################### # SF_FileSystem CLASS @@ -586,16 +658,16 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.FileSystem' - serviceProperties = dict(FileNaming = True, ConfigFolder = False, ExtensionsFolder = False, HomeFolder = False, + servicesynonyms = ('filesystem', 'scriptforge.filesystem') + serviceproperties = dict(FileNaming = True, ConfigFolder = False, ExtensionsFolder = False, HomeFolder = False, InstallFolder = False, TemplatesFolder = False, TemporaryFolder = False, UserTemplatesFolder = False) + propertysynonyms = SFServices._getAttributeSynonyms(serviceproperties) + # Force for each property to get its value from Basic - due to FileNaming updatability + forceGetProperty = True # Open TextStream constants ForReading, ForWriting, ForAppending = 1, 2, 8 - @property - def ConfigFolder(self): - return self.GetProperty('ConfigFolder') - def BuildPath(self, foldername, name): return self.Execute(self.vbMethod, 'BuildPath', foldername, name) @@ -628,6 +700,7 @@ class SFScriptForge: def FileExists(self, filename): return self.Execute(self.vbMethod, 'FileExists', filename) + fileexists, fileExists = FileExists, FileExists def Files(self, foldername, filter = ''): return self.Execute(self.vbMethod + self.flgArrayRet, 'Files', foldername, filter) @@ -705,18 +778,21 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.L10N' - serviceProperties = dict(Folder = False, Languages = False, Locale = False) + servicesynonyms = () + serviceproperties = dict(Folder = False, Languages = False, Locale = False) + propertysynonyms = SFServices._getAttributeSynonyms(serviceproperties) def AddText(self, context = '', msgid = '', comment = ''): return self.Execute(self.vbMethod, 'AddText', context, msgid, comment) + addText, addtext = AddText, AddText def ExportToPOTFile(self, filename, header = '', encoding= 'UTF-8'): return self.Execute(self.vbMethod, 'ExportToPOTFile', filename, header, encoding) + exportToPOTFile, exporttopotfile = ExportToPOTFile, ExportToPOTFile def GetText(self, msgid, *args): return self.Execute(self.vbMethod, 'GetText', msgid, *args) - - _ = GetText + _, gettext, getText = GetText, GetText, GetText # ######################################################################### # SF_Platform CLASS @@ -735,51 +811,63 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.Platform' - serviceProperties = dict(Architecture = False, ComputerName = False, CPUCount = False, CurrentUser = False, + servicesynonyms = () + serviceproperties = dict(Architecture = False, ComputerName = False, CPUCount = False, CurrentUser = False, Locale = False, Machine = False, OfficeVersion = False, OSName = False, OSPlatform = False, OSRelease = False, OSVersion = False, Processor = False) + propertysynonyms = SFServices._getAttributeSynonyms(serviceproperties) # Python helper functions py = ScriptForge.pythonhelpermodule + '$' + '_SF_Platform' @property def Architecture(self): return self.SIMPLEEXEC(self.py, 'Architecture') + architecture = Architecture @property def ComputerName(self): return self.SIMPLEEXEC(self.py, 'ComputerName') + computerName, computername = ComputerName, ComputerName @property def CPUCount(self): return self.SIMPLEEXEC(self.py, 'CPUCount') + cpuCount, cpucount = CPUCount, CPUCount @property def CurrentUser(self): return self.SIMPLEEXEC(self.py, 'CurrentUser') + currentUser, currentuser = CurrentUser, CurrentUser @property def Machine(self): return self.SIMPLEEXEC(self.py, 'Machine') + machine = Machine @property def OSName(self): return self.SIMPLEEXEC(self.py, 'OSName') + osName, osname = OSName, OSName @property def OSPlatform(self): return self.SIMPLEEXEC(self.py, 'OSPlatform') + osPlatform, osplatform = OSPlatform, OSPlatform @property def OSRelease(self): return self.SIMPLEEXEC(self.py, 'OSRelease') + osRelease, osrelease = OSRelease, OSRelease @property def OSVersion(self): return self.SIMPLEEXEC(self.py, 'OSVersion') + osVersion, osversion = OSVersion, OSVersion @property def Processor(self): return self.SIMPLEEXEC(self.py, 'Processor') + processor = Processor # ######################################################################### # SF_TextStream CLASS @@ -792,34 +880,44 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.TextStream' - serviceProperties = dict(AtEndOfStream = False, Encoding = False, FileName = False, - IOMode = False, Line = False, NewLine = True) + servicesynonyms = () + serviceproperties = dict(AtEndOfStream = False, Encoding = False, FileName = False, IOMode = False, + Line = False, NewLine = True) + propertysynonyms = SFServices._getAttributeSynonyms(serviceproperties) @property def AtEndOfStream(self): return self.GetProperty('AtEndOfStream') + atEndOfStream, atendofstream = AtEndOfStream, AtEndOfStream @property def Line(self): return self.GetProperty('Line') + line = Line def CloseFile(self): return self.Execute(self.vbMethod, 'CloseFile') + closeFile, closefile = CloseFile, CloseFile def ReadAll(self): return self.Execute(self.vbMethod, 'ReadAll') + readAll, readall = ReadAll, ReadAll def ReadLine(self): return self.Execute(self.vbMethod, 'ReadLine') + readLine, readline = ReadLine, ReadLine def SkipLine(self): return self.Execute(self.vbMethod, 'SkipLine') + skipLine, skipline = SkipLine, SkipLine def WriteBlankLines(self, lines): return self.Execute(self.vbMethod, 'WriteBlankLines', lines) + writeBlankLines, writeblanklines = WriteBlankLines, WriteBlankLines def WriteLine(self, line): return self.Execute(self.vbMethod, 'WriteLine', line) + writeLine, writeline = WriteLine, WriteLine # ######################################################################### # SF_Timer CLASS @@ -831,39 +929,31 @@ class SFScriptForge: # Mandatory class properties for service registration serviceimplementation = 'basic' servicename = 'ScriptForge.Timer' - serviceProperties = dict(Duration = False, IsStarted = False, IsSuspended = False, + servicesynonyms = () + serviceproperties = dict(Duration = False, IsStarted = False, IsSuspended = False, SuspendDuration = False, TotalDuration = False) - - @property - def Duration(self): - return self.GetProperty('Duration') - - @property - def IsStarted(self): - return self.GetProperty('IsStarted') - - @property - def SuspendDuration(self): - return self.GetProperty('SuspendDuration') - - @property - def TotalDuration(self): - return self.GetProperty('TotalDuration') + propertysynonyms = SFServices._getAttributeSynonyms(serviceproperties) + # Force for each property to get its value from Basic + forceGetProperty = True def Continue(self): return self.Execute(self.vbMethod, 'Continue') def Restart(self): return self.Execute(self.vbMethod, 'Restart') + restart = Restart def Start(self): return self.Execute(self.vbMethod, 'Start') + start = Start def Suspend(self): return self.Execute(self.vbMethod, 'Suspend') + suspend = Suspend def Terminate(self): return self.Execute(self.vbMethod, 'Terminate') + terminate = Terminate # ##############################################False####################################################################### @@ -872,11 +962,17 @@ class SFScriptForge: def CreateScriptService(service, *args): """ A service being the name of a collection of properties and methods, - this method returns the Python object mirror of the Basic object implementing - the requested service - As an exception to above, 'Basic' is accepted as a shortcut to the Basic service - which is implemented in Python + this method returns either + - the Python object mirror of the Basic object implementing the requested service + - the Python object implementing the service itself + + A service may be designated by its official name, stored in its class.servicename + or by one of its synonyms stored in its class.servicesynonyms list + If the service is not identified, the service creation is delegated to Basic, that might raise an error + if still not identified there + :param service: the name of the service as a string 'library.service' - cased exactly + or one of its synonyms :param args: the arguments to pass to the service constructor :return: the service as a Python object """ @@ -889,12 +985,11 @@ def CreateScriptService(service, *args): """ Synonyms within service names implemented in Python or predefined are resolved here :param servicename: The short name of the service - :return: The official service name + :return: The official service name if found, the argument otherwise """ - if servicename.lower() in ('basic', 'scriptforge.basic'): - return 'ScriptForge.Basic' - if servicename.lower() in ('filesystem', 'scriptforge.filesystem'): - return 'ScriptForge.FileSystem' + for cls in SFServices.__subclasses__(): + if servicename.lower() in cls.servicesynonyms: + return cls.servicename return servicename # @@ -915,6 +1010,8 @@ def CreateScriptService(service, *args): serv = ScriptForge.InvokeBasicService('SF_Services', SFServices.vbMethod, 'CreateScriptService', service, *args) return serv +createScriptSerive, createscriptservive = CreateScriptService, CreateScriptService + # ##################################################################################################################### # Services shortcuts ### |