diff --git a/wizards/source/scriptforge/SF_FileSystem.xba b/wizards/source/scriptforge/SF_FileSystem.xba index fd166ed524dc..1d7eda971a25 100644 --- a/wizards/source/scriptforge/SF_FileSystem.xba +++ b/wizards/source/scriptforge/SF_FileSystem.xba @@ -54,6 +54,14 @@ Option Explicit ''' The file manifest.xml is managed automatically. ''' The FileNaming setting is ignored. ''' +''' The FileSystem service provides also simple methods to extract files from or load files +''' into archive (zip) files. +''' .odf and .oxt files being compressed files, direct vreation or update of such files (with care !) is made easy. +''' Note that multipart and encrypted zip files are not supported. +''' +''' The service provides symbolic names for a number of system folders (home, LibreOffice install, ...) +''' that contribute to improving the portability of user scripts. +''' ''' Service invocation example: ''' Dim FSO As Variant ''' Set FSO = CreateScriptService("FileSystem") @@ -377,6 +385,96 @@ CatchNotSupported: GoTo Finally End Function ' ScriptForge.SF_FileSystem.CompareFiles +REM ----------------------------------------------------------------------------- +Public Function CompressToZip(Optional ByVal FileName As Variant _ + , Optional ByVal FolderName As Variant _ + , Optional ByVal RootFolder As Variant _ + ) As Variant +''' Stores the content of Folder, including its subfolders, into a zip archive. +''' Encrypted archives are not supported. +''' The compressed files are stored optionally under a RootFolder. +''' Files existing already in the archive are not overwritten. +''' The method is not supported for document's internal file systems. +''' Args: +''' FileName: A string representing the zip file. +''' If it does not exist, the archive is created, oncluding its parent folders. +''' All files present in the archive are kept untouched. +''' FolderName: The folder to compress, as a string. +''' All files and subfolders are included. The Folder itself is not. +''' RootFolder: When absent, files and folders are loaded in the archive from the root ("/"). +''' When a RootFolder is given, as a string without path separator, they are stored below that folder. +''' Returns: +''' True when successful. +''' When False, an error message might be found in the console. +''' Exceptions: +''' UNKNOWNFOLDERERROR Source folder does not exist +''' NOTAFOLDERERROR The folder is a file, not a folder +''' FILESYSTEMERROR The method is not applicable on document's file systems +''' Example: +''' a = SF_FileSystem.CompressToZip("C:\archive.zip", Folder := "C:\Downloads") + +Dim bCompress As Boolean ' Return value +Dim vCompress As Variant ' Returned from Python (True/False, error message) +Dim sFile As String ' Alias of FileName +Dim sMode As String ' a = append, file exists - x = exclusive read/write +Dim sParentFolder As String ' Parent folder of FileName +Dim vRootFolder As Variant ' Alias of RootFolder +Const cstPyHelper = "$" & "_SF_FileSystem__CompressToZip" +Const cstThisSub = "FileSystem.CompressToZip" +Const cstSubArgs = "FileName, FolderName, [RootFolder=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bCompress = False + +Check: + If IsMissing(RootFolder) Or IsEmpty(RootFolder) Then RootFolder = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._ValidateFile(FolderName, "FolderName") Then GoTo Finally + If Not SF_Utils._Validate(RootFolder, "RootFolder", V_STRING) Then GoTo Finally + End If + If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported + + If SF_FileSystem.FileExists(FolderName) Then GoTo CatchFileNotFolder + If Not SF_FileSystem.FolderExists(FolderName) Then GoTo CatchFolderNotExists + +Try: + With ScriptForge.SF_FileSystem + ' Append (a) or exclusive create (x) mode ? Eventually, create parent folder + If .FileExists(FileName) Then + sMode = "a" + Else + sParentFolder = .GetParentFolderName(FileName) + If Not .FolderExists(sParentFolder) Then .CreateFolder(sParentFolder) + sMode = "x" + End If + If Len(RootFolder) > 0 Then vRootFolder = RootFolder Else vRootFolder = Null + With ScriptForge.SF_Session + ' Python requires OS notation for files and folders + vCompress = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName), sMode, ConvertFromUrl(FolderName), vRootFolder) + End With + bCompress = vCompress(0) + If Not bCompress Then SF_Exception.DebugPrint("CompressToZip", vCompress(1)) + End With + +Finally: + CompressToZip = bCompress + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchFolderNotExists: + SF_Exception.RaiseFatal(UNKNOWNFOLDERERROR, "FolderName", FolderName) + GoTo Finally +CatchFileNotFolder: + SF_Exception.RaiseFatal(NOTAFOLDERERROR, "FolderName", FolderName) + GoTo Finally +CatchNotSupported: + SF_Exception.RaiseFatal(FILESYSTEMERROR, "FileName", Split(cstThisSub, ".")(1), FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.CompressToZip + REM ----------------------------------------------------------------------------- Public Function CopyFile(Optional ByVal Source As Variant _ , Optional ByVal Destination As Variant _ @@ -725,6 +823,85 @@ Catch: GoTo Finally End Function ' ScriptForge.SF_FileSystem.ExtensionFolder +REM ----------------------------------------------------------------------------- +Public Function ExtractFromZip(Optional ByVal FileName As Variant _ + , Optional ByVal Destination As Variant _ + , Optional ByVal Source As Variant _ + ) As Variant +''' Extracts a list of files contained in a zip container and stores them in a folder. +''' The process ends at the first error. Existing files are overwritten without warning. +''' The method is not supported for document's internal file systems. +''' Args: +''' FileName: A string representing a zip file +''' Destination: The folder where to extract the compressed files to. +''' Existing files with the same names are overwritten. Other files stay untouched. +''' When the folder does not exist, it is created, as are subfolders. +''' Source: A single string or an array of strings with valid names of files contained in the zip file +''' When Source is omitted, is an empty array or an empty string, all the files and folders are extracted. +''' Returns: +''' True when successful. +''' When False, an error message might be found in the console. +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist or is a folder +''' FILESYSTEMERROR The method is not applicable on document's file systems +''' Example: +''' a = SF_FileSystem.ExtractFromZip("C:\writerdoc.odt", Source := Array("content.xml", "meta.xml"), Desctination := "C:\TEMP") + +Dim bExtract As Boolean ' Return value +Dim vExtract As Variant ' Returned from Python (True/False, error message) +Dim vSource As Variant ' Alias of Source +Const cstPyHelper = "$" & "_SF_FileSystem__ExtractFromZip" +Const cstThisSub = "FileSystem.ExtractFromZip" +Const cstSubArgs = "FileName, Destination, [Source=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bExtract = False + +Check: + If IsMissing(Source) Or IsEmpty(Source) Then Source = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._ValidateFile(Destination, "Destination") Then GoTo Finally + If IsArray(Source) Then + If Not SF_Utils._ValidateArray(Source, "Source", 1, V_STRING) Then GoTo Finally + Else + If Not SF_Utils._Validate(Source, "Source", V_STRING) Then GoTo Finally + End If + End If + If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported + +Try: + If SF_FileSystem.FileExists(FileName) Then + With ScriptForge.SF_Session + ' Be sure to pass an array to the Python script + If Not IsArray(Source) Then + If Len(Source) = 0 Then vSource = Array() Else vSource = Array(Source) + Else + vSource = Source + End If + vExtract = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName), Destination, vSource) + bExtract = vExtract(0) + If Not bExtract Then SF_Exception.DebugPrint("ExtractFromZip", vExtract(1)) + End With + Else + GoTo CatchNotExists + End If + +Finally: + ExtractFromZip = bExtract + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +CatchNotSupported: + SF_Exception.RaiseFatal(FILESYSTEMERROR, "FileName", Split(cstThisSub, ".")(1), FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.ExtractFromZip + REM ----------------------------------------------------------------------------- Public Function FileExists(Optional ByVal FileName As Variant) As Boolean ''' Return True if the given file exists @@ -829,6 +1006,74 @@ CatchFolder: GoTo Finally End Function ' ScriptForge.SF_FileSystem.Files +REM ----------------------------------------------------------------------------- +Public Function FilesInZip(Optional ByVal FileName As Variant _ + , Optional ByVal Pattern As Variant _ + ) As Variant +''' Returns The list of the files contained in a zip container. +''' The list of names may optionally be filtered. +''' The method is not supported for document's internal file systems. +''' Args: +''' FileName: A string representing a zip file +''' Pattern: A pattern as a not case-sensitive string containing 0 or more wildcards +''' Admitted wildcard are: the "?" represents any single character +''' the "*" represents zero, one, or multiple characters +''' Returns: +''' An unordered array of file names without a heading slash. +''' The path separator is always the slash ("/"), also on Windows. +''' The list may be filtered with a name pattern. Without pattern, all files are returned. +''' The resulting array may be empty. +''' An empty array is returned when the file is not a valid zip file. +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist or is a folder +''' FILESYSTEMERROR The method is not applicable on document's file systems +''' Example: +''' a = SF_FileSystem.FilesInZip("C:\writerdoc.odt", Pattern := "*.xml") + +Dim vFiles As Variant ' Return value +Const cstPyHelper = "$" & "_SF_FileSystem__FilesInZip" +Const cstThisSub = "FileSystem.FilesInZip" +Const cstSubArgs = "FileName, [Pattern=""""]" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + vFiles = Array() + +Check: + If IsMissing(Pattern) Or IsEmpty(Pattern) Then Pattern = "" + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + If Not SF_Utils._Validate(Pattern, "Pattern", V_STRING) Then GoTo Finally + End If + If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported + +Try: + If SF_FileSystem.FileExists(FileName) Then + With ScriptForge.SF_Session + vFiles = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName)) + End With + ' Filter files based on pattern + If Len(Pattern) > 0 And UBound(vFiles) > -1 Then + vFiles = ScriptForge.SF_Array.Filter(ScriptForge.SF_String, "IsLike", vFiles, Pattern, False) + End If + Else + GoTo CatchNotExists + End If + +Finally: + FilesInZip = vFiles + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +CatchNotSupported: + SF_Exception.RaiseFatal(FILESYSTEMERROR, "FileName", Split(cstThisSub, ".")(1), FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.FilesInZip + REM ----------------------------------------------------------------------------- Public Function FolderExists(Optional ByVal FolderName As Variant) As Boolean ''' Return True if the given folder name exists @@ -1293,12 +1538,65 @@ CatchNotSupported: GoTo Finally End Function ' ScriptForge.SF_FileSystem.HashFile +REM ----------------------------------------------------------------------------- +Public Function IsZipFile(Optional ByVal FileName As Variant) As Boolean +''' Returns True if filename is a valid ZIP file based on its magic number. +''' The method is not supported for document's internal file systems. +''' Args: +''' FileName: a string representing a file +''' Returns: +''' True when FileName is a valid zip file. Otherwise False. +''' Exceptions: +''' UNKNOWNFILEERROR The file does not exist or is a folder +''' FILESYSTEMERROR The method is not applicable on document's file systems +''' Example: +''' MsgBox SF_FileSystem.IsZipFile("C:\writerdoc.odt") + +Dim bIsZip As Boolean ' Return value +Const cstPyHelper = "$" & "_SF_FileSystem__IsZipFile" +Const cstThisSub = "FileSystem.IsZipFile" +Const cstSubArgs = "FileName" + + If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch + bIsZip = False + +Check: + If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then + If Not SF_Utils._ValidateFile(FileName, "FileName") Then GoTo Finally + End If + If SF_FileSystem._IsDocFileSystem(FileName) Then GoTo CatchNotSupported + +Try: + If SF_FileSystem.FileExists(FileName) Then + With ScriptForge.SF_Session + bIsZip = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper & cstPyHelper _ + , _ConvertFromUrl(FileName)) + End With + Else + GoTo CatchNotExists + End If + +Finally: + IsZipFile = bIsZip + SF_Utils._ExitFunction(cstThisSub) + Exit Function +Catch: + GoTo Finally +CatchNotExists: + SF_Exception.RaiseFatal(UNKNOWNFILEERROR, "FileName", FileName) + GoTo Finally +CatchNotSupported: + SF_Exception.RaiseFatal(FILESYSTEMERROR, "FileName", Split(cstThisSub, ".")(1), FileName) + GoTo Finally +End Function ' ScriptForge.SF_FileSystem.IsZipFile + REM ----------------------------------------------------------------------------- Public Function Methods() As Variant ''' Return the list or methods of the FileSystem service as an array Methods = Array("BuildPath" _ , "CompareFiles" _ + , "CompressToZip" _ , "CopyFile" _ , "CopyFolder" _ , "CreateFolder" _ @@ -1306,8 +1604,10 @@ Public Function Methods() As Variant , "DeleteFile" _ , "DeleteFolder" _ , "ExtensionFolder" _ + , "ExtractFromZip" _ , "FileExists" _ , "Files" _ + , "FilesInZip" _ , "FolderExists" _ , "GetBaseName" _ , "GetExtension" _ @@ -1317,6 +1617,7 @@ Public Function Methods() As Variant , "GetParentFolderName" _ , "GetTempName" _ , "HashFile" _ + , "IsZipFile" _ , "MoveFile" _ , "MoveFolder" _ , "Normalize" _ diff --git a/wizards/source/scriptforge/python/ScriptForgeHelper.py b/wizards/source/scriptforge/python/ScriptForgeHelper.py index f2fe8db8aebc..5604df51bf5f 100644 --- a/wizards/source/scriptforge/python/ScriptForgeHelper.py +++ b/wizards/source/scriptforge/python/ScriptForgeHelper.py @@ -22,26 +22,28 @@ # distributed with this file, see http://www.gnu.org/licenses/ . """ -Collection of Python helper functions called from the ScriptForge Basic libraries -to execute specific services that are not or not easily available from Basic directly. -When relevant, the methods present in the ScriptForge Python module (scriptforge.py) might call the -functions below for compatibility reasons. -""" + Collection of Python helper functions called from the ScriptForge Basic libraries + to execute specific services that are not or not easily available from Basic directly. + When relevant, the methods present in the ScriptForge Python module (scriptforge.py) may call the + functions below directly to bypass the Python-Basic bridge. + """ import getpass import os import platform import hashlib import filecmp +import pathlib +import zipfile import webbrowser import json class _Singleton(type): """ - A Singleton design pattern - Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly - """ + A Singleton design pattern + Credits: « Python in a Nutshell » by Alex Martelli, O'Reilly + """ instances = {} def __call__(cls, *args, **kwargs): @@ -57,8 +59,8 @@ class _Singleton(type): def _SF_Dictionary__ConvertToJson(propval, indent = None) -> str: # used by Dictionary.ConvertToJson() Basic method """ - Given an array of PropertyValues as argument, convert it to a JSON string - """ + Given an array of PropertyValues as argument, convert it to a JSON string + """ # Array of property values => Dict(ionary) => JSON pvDict = {} for pv in propval: @@ -66,11 +68,12 @@ def _SF_Dictionary__ConvertToJson(propval, indent = None) -> str: return json.dumps(pvDict, indent=indent, skipkeys=True) -def _SF_Dictionary__ImportFromJson(jsonstr: str): # used by Dictionary.ImportFromJson() Basic method - """ - Given a JSON string as argument, convert it to a list of tuples (name, value) - The value must not be a (sub)dict. This doesn't pass the python-basic bridge. +def _SF_Dictionary__ImportFromJson(jsonstr: str) -> list: + # used by Dictionary.ImportFromJson() Basic method """ + Given a JSON string as argument, convert it to a list of tuples (name, value) + The value must not be a (sub)dict. This doesn't pass the python-basic bridge. + """ # JSON => Dictionary => Array of tuples/lists dico = json.loads(jsonstr) result = [] @@ -94,9 +97,9 @@ def _SF_Dictionary__ImportFromJson(jsonstr: str): # used by Dictionary.ImportFr def _SF_Exception__PythonPrint(string: str) -> bool: # used by SF_Exception.PythonPrint() Basic method """ - Write the argument to stdout. - If the APSO shell console is active, the argument will be displayed in the console window - """ + Write the argument to stdout. + If the APSO shell console is active, the argument will be displayed in the console window + """ print(string) return True @@ -108,27 +111,84 @@ def _SF_Exception__PythonPrint(string: str) -> bool: def _SF_FileSystem__CompareFiles(filename1: str, filename2: str, comparecontents=True) -> bool: # used by SF_FileSystem.CompareFiles() Basic method """ - Compare the 2 files, returning True if they seem equal, False otherwise. - By default, only their signatures (modification time, ...) are compared. - When comparecontents == True, their contents are compared. - """ + Compare the 2 files, returning True if they seem equal, False otherwise. + By default, only their signatures (modification time, ...) are compared. + When comparecontents == True, their contents are compared. + """ try: return filecmp.cmp(filename1, filename2, not comparecontents) except Exception: return False -def _SF_FileSystem__GetFilelen(systemfilepath: str) -> str: # used by SF_FileSystem.GetFilelen() Basic method +def _SF_FileSystem__CompressToZip(filename: str, mode: str, folder: str, rootfolder: str = None) -> tuple[bool, str]: + # used by SF_FileSystem.CompressToZip() Basic method + """ + Stores the content of folder, including its subfolders, into a zip archive. + The mode argument determines if the zip file is created (e'x'clusive create) or not ('a'ppend). + The compressed files are stored optionally under a root folder. + """ + try: + if mode == 'a': # existence of filename is checked in the calling routine + if not zipfile.is_zipfile(filename): + return False, "File is not a zip archive: '%s'" % filename + directory = pathlib.Path(folder) + # Write compressed files under root or under the given root folder. Normal slashes are required, also in Windows.. + topfolder = (None if rootfolder is None else pathlib.PurePosixPath(rootfolder + '/')) + with zipfile.ZipFile(filename, mode, strict_timestamps = False) as archive: + for filepath in directory.rglob('*'): + name = (filepath.relative_to(directory) if topfolder is None + else topfolder.joinpath(filepath.relative_to(directory))) + archive.write(filepath, arcname = name, compress_type = zipfile.ZIP_DEFLATED) + return True, '' + except Exception as e: + return False, str(e) + + +def _SF_FileSystem__ExtractFromZip(filename: str, destination: str, source: tuple = ()) -> tuple[bool, str]: + # used by SF_FileSystem.ExtractFromZip() Basic method + """ + Stores the source file(s) found in the given ZIP file into a destination folder. + """ + try: + if not zipfile.is_zipfile(filename): + return False, "File is not a zip archive: '%s'" % filename + with zipfile.ZipFile(filename, mode = 'r') as archive: + archive.extractall(destination, members = (None if len(source) == 0 else source)) + return True, '' + except Exception as e: + return False, str(e) + + +def _SF_FileSystem__FilesInZip(filename: str) -> list: + # used by SF_FileSystem.FilesInZip() Basic method + """ + Returns a list of archive members by name. The objects are in the same order as their entries + in the given ZIP file. + """ + try: + if not zipfile.is_zipfile(filename): + return [] + with zipfile.ZipFile(filename, mode = 'r') as archive: + namelist = archive.namelist() + return namelist + except Exception: + return [] + + +def _SF_FileSystem__GetFilelen(systemfilepath: str) -> str: + # used by SF_FileSystem.GetFilelen() Basic method return str(os.path.getsize(systemfilepath)) -def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: # used by SF_FileSystem.HashFile() Basic method - """ - Hash a given file with the given hashing algorithm - cfr. https://www.pythoncentral.io/hashing-files-with-python/ - Example - hash = _SF_FileSystem__HashFile('myfile.txt','MD5') +def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: + # used by SF_FileSystem.HashFile() Basic method """ + Hash a given file with the given hashing algorithm + cfr. https://www.pythoncentral.io/hashing-files-with-python/ + Example + hash = _SF_FileSystem__HashFile('myfile.txt','MD5') + """ algo = algorithm.lower() try: if algo in hashlib.algorithms_guaranteed: @@ -159,13 +219,21 @@ def _SF_FileSystem__HashFile(filename: str, algorithm: str) -> str: # used by S return '' +def _SF_FileSystem__IsZipFile(filename: str) -> bool: + # used by SF_FileSystem.IsZipFile() Basic method + """ + Returns True if filename is a valid ZIP file based on its magic number, otherwise returns False. + """ + return zipfile.is_zipfile(filename) + + def _SF_FileSystem__Normalize(systemfilepath: str) -> str: # used by SF_FileSystem.Normalize() Basic method """ - Normalize a pathname by collapsing redundant separators and up-level references so that - A//B, A/B/, A/./B and A/foo/../B all become A/B. - On Windows, it converts forward slashes to backward slashes. - """ + Normalize a pathname by collapsing redundant separators and up-level references so that + A//B, A/B/, A/./B and A/foo/../B all become A/B. + On Windows, it converts forward slashes to backward slashes. + """ return os.path.normpath(systemfilepath) @@ -175,8 +243,8 @@ def _SF_FileSystem__Normalize(systemfilepath: str) -> str: def _SF_Platform(propertyname: str): # used by SF_Platform Basic module """ - Switch between SF_Platform properties (read the documentation about the ScriptForge.Platform service) - """ + Switch between SF_Platform properties (read the documentation about the ScriptForge.Platform service) + """ pf = Platform() if propertyname == 'Architecture': return pf.Architecture @@ -249,8 +317,8 @@ class Platform(object, metaclass = _Singleton): def _SF_Session__OpenURLInBrowser(url: str): # Used by SF_Session.OpenURLInBrowser() Basic method """ - Display url using the default browser - """ + Display url using the default browser + """ try: webbrowser.open(url, new = 2) except Exception: # whatever happened, continue the execution ... @@ -264,10 +332,10 @@ def _SF_Session__OpenURLInBrowser(url: str): # Used by SF_Session.OpenURLInBr def _SF_String__HashStr(string: str, algorithm: str) -> str: # used by SF_String.HashStr() Basic method """ - Hash a given UTF-8 string with the given hashing algorithm - Example - hash = _SF_String__HashStr('This is a UTF-8 encoded string.','MD5') - """ + Hash a given UTF-8 string with the given hashing algorithm + Example + hash = _SF_String__HashStr('This is a UTF-8 encoded string.','MD5') + """ algo = algorithm.lower() try: if algo in hashlib.algorithms_guaranteed: @@ -301,6 +369,7 @@ def _SF_String__HashStr(string: str, algorithm: str) -> str: # used by SF_Strin g_exportedScripts = () if __name__ == "__main__": + """ print(_SF_Platform('Architecture')) print(_SF_Platform('ComputerName')) print(_SF_Platform('CPUCount')) @@ -323,10 +392,13 @@ if __name__ == "__main__": # _SF_Session__OpenURLInBrowser('https://docs.python.org/3/library/webbrowser.html') # js = """ - {"firstName": "John","lastName": "Smith","isAlive": true,"age": 27, + """{"firstName": "John","lastName": "Smith","isAlive": true,"age": 27, "address": {"streetAddress": "21 2nd Street","city": "New York","state": "NY","postalCode": "10021-3100"}, "phoneNumbers": [{"type": "home","number": "212 555-1234"},{"type": "office","number": "646 555-4567"}], "children": ["Q", "M", "G", "T"],"spouse": null} """ - arr = _SF_Dictionary__ImportFromJson(js) - print(arr) + """arr = _SF_Dictionary__ImportFromJson(js) + print(arr)""" + a = "/tmp/SF_YYY/zipped.odt" + b = _SF_FileSystem__CompressToZip(a, "a", "/tmp/SF_XXX", "New") + print(b) \ No newline at end of file diff --git a/wizards/source/scriptforge/python/scriptforge.py b/wizards/source/scriptforge/python/scriptforge.py index 2f54e5462f0a..cf86a1ca2e09 100644 --- a/wizards/source/scriptforge/python/scriptforge.py +++ b/wizards/source/scriptforge/python/scriptforge.py @@ -1220,6 +1220,9 @@ class SFScriptForge: else: return False + def CompressToZip(self, filename, foldername, rootfolder = ''): + return self.ExecMethod(self.vbMethod, 'CompressToZip', filename, foldername, rootfolder) + def CopyFile(self, source, destination, overwrite = True): return self.ExecMethod(self.vbMethod, 'CopyFile', source, destination, overwrite) @@ -1241,12 +1244,18 @@ class SFScriptForge: def ExtensionFolder(self, extension): return self.ExecMethod(self.vbMethod, 'ExtensionFolder', extension) + def ExtractFromZip(self, filename, destination, source = ''): + return self.ExecMethod(self.vbMethod, 'ExtractFromZip', filename, destination, source) + def FileExists(self, filename): return self.ExecMethod(self.vbMethod, 'FileExists', filename) def Files(self, foldername, filter = '', includesubfolders = False): return self.ExecMethod(self.vbMethod, 'Files', foldername, filter, includesubfolders) + def FilesInZip(self, filename, pattern = ''): + return self.ExecMethod(self.vbMethod, 'FilesInZip', filename, pattern) + def FolderExists(self, foldername): return self.ExecMethod(self.vbMethod, 'FolderExists', foldername) @@ -1284,6 +1293,14 @@ class SFScriptForge: else: return '' + def IsZipFile(self, filename): + py = ScriptForge.pythonhelpermodule + '$' + '_SF_FileSystem__IsZipFile' + if self.FileExists(filename): + file = self._ConvertFromUrl(filename) + return self.SIMPLEEXEC(py, file) + else: + return False + def MoveFile(self, source, destination): return self.ExecMethod(self.vbMethod, 'MoveFile', source, destination) diff --git a/wizards/source/scriptforge/python/scriptforge.pyi b/wizards/source/scriptforge/python/scriptforge.pyi index 444863a5da55..ed29d33944e9 100644 --- a/wizards/source/scriptforge/python/scriptforge.pyi +++ b/wizards/source/scriptforge/python/scriptforge.pyi @@ -975,6 +975,26 @@ class SFScriptForge: """ ... + def CompressToZip(self, filename: FILE, foldername: FILE, rootfolder: str = ...) -> bool: + """ + Stores the content of Folder, including its subfolders, into a zip archive. + Encrypted files are not supported. The compressed files are stored optionally under a root folder. + Files existing already in the archive are not overwritten. + Args + ``filename``: a string representing the zip file. + If it does not exist, the archive is created, oncluding its parent folders. + All files present in the archive are kept untouched. + + ``foldername``: the folder to compress, as a string. All files and subfolders are included. + The folder itself is not. + + ``rootfolder``: when absent, files and folders are loaded in the archive from the root ("/"). + When present, given as a string without path separator, they are stored below that folder. + Returns + ``True`` when successful. When ``False``, the reason might be found in the console. + """ + ... + def CopyFile(self, source: FILE, destination: FILE, overwrite: bool = ...) -> bool: """ Copies one or more files from one location to another. @@ -1092,6 +1112,24 @@ class SFScriptForge: """ ... + def ExtractFromZip(self, filename: FILE, destination: FILE, source: str | tuple[str, ...] = ...) -> bool: + """ + Extracts a list of files contained in a zip archive and stores them in a folder. + Args + ``filename``: a string representing the zip archive. + + ``destination``: the folder where to extract the compressed files to. + Existing files with the same names are overwritten without warning. Other files stay untouched. + When the folder or a subfolder does not exist, it is created. + + ``source``: a single string or a tuple of strings with valid names of files contained in + the zip file. When Source is omitted, is an empty tuple or an empty string, + all the files and folders are extracted. + Returns + ``True`` when successful. When ``False``, the reason might be found in the console. + """ + ... + def FileExists(self, filename: FILE) -> bool: """ Return ``True`` if the given file exists. @@ -1121,6 +1159,24 @@ class SFScriptForge: """ ... + def FilesInZip(self, filename: FILE, pattern: str = ...) -> tuple[str, ...]: + """ + Returns The list of the files contained in a zip container. The list of names may optionally + be filtered. + Args + ``filename``: a string representing the zip archive. + + ``pattern``: a pattern as a not case-sensitive string containing 0 or more wildcards. + Admitted wildcard are: the "?" represents any single character, + the "*" represents zero, one, or multiple characters. + Returns + An unordered tuple of file names without a heading slash. The path separator is always + the slash ("/"), also on Windows. The list may be filtered with a name pattern. + Without pattern, all files are returned. The resulting tuple may be empty. An empty tuple + is returned when the file is not a valid zip file. + """ + ... + def FolderExists(self, foldername: FILE) -> bool: """ Returns ``True`` if the given folder name exists. @@ -1235,6 +1291,16 @@ class SFScriptForge: """ ... + def IsZipFile(self, filename: FILE) -> bool: + """ + Returns ``True`` if filename is a valid ZIP file based on its magic number. + Args + ``filename``: a string representing the zip archive. + Returns + ``True`` when ``filename`` is a valid zip file. Otherwise ``False``. + """ + ... + def MoveFile(self, source: FILE, destination: FILE) -> bool: """ Moves one or more files from one location to another.