From 33ee1cb1723d6426f1275edf75bfc5f86ad46c9f Mon Sep 17 00:00:00 2001 From: mas2hc Date: Wed, 11 Jan 2023 18:09:20 +0700 Subject: [PATCH 01/18] Adds selftest for utf-8 encoding. --- .../jsonpreprocessor_unittest.py | 19 ++++++++++- .../config/08_utf8_encoding/utf8_format.json | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 atest/testdata/config/08_utf8_encoding/utf8_format.json diff --git a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py b/atest/jsonpreprocessor/jsonpreprocessor_unittest.py index 2d3ca65c..40a50b5c 100644 --- a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py +++ b/atest/jsonpreprocessor/jsonpreprocessor_unittest.py @@ -358,4 +358,21 @@ def test_none_true_false_datatype(self): assert oJsonData['convert_true_to_string'] == '"True"' assert oJsonData['convert_false_to_string'] == '"False"' assert oJsonData['params']['global'] == JSONFORMAT_NONE_TRUE_FALSE['params']['global'] - assert oJsonData['preprocessor']['definitions'] == JSONFORMAT_NONE_TRUE_FALSE['preprocessor']['definitions'] \ No newline at end of file + assert oJsonData['preprocessor']['definitions'] == JSONFORMAT_NONE_TRUE_FALSE['preprocessor']['definitions'] + +class TestUTF8Encoding: + + def test_utf8_encoding(self): + ''' + Test utf-8 encoding + ''' + sJsonfile = os.path.abspath("../testdata/config/08_utf8_encoding/utf8_format.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Vietnamese'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['Japanese'] == "これは UTF-8 セルフテストです" + assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['Chinese'] == "這是 UTF-8 自測" \ No newline at end of file diff --git a/atest/testdata/config/08_utf8_encoding/utf8_format.json b/atest/testdata/config/08_utf8_encoding/utf8_format.json new file mode 100644 index 00000000..acdc5835 --- /dev/null +++ b/atest/testdata/config/08_utf8_encoding/utf8_format.json @@ -0,0 +1,32 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "German": "Dies ist der UTF-8 SälfTest", + "Vietnamese": "Đây là bản tự kiểm tra UTF-8", + "Japanese": "これは UTF-8 セルフテストです", + "Hindi": "यह UTF-8 सेल्फ़टेस्ट है", + "Thai": "นี่คือการทดสอบตัวเอง UTF-8", + "Korean": "이것은 UTF-8 자체 테스트입니다", + "Chinese": "這是 UTF-8 自測", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" + } \ No newline at end of file From 733e89364cd264aa93a556b8c253a1ec856006f9 Mon Sep 17 00:00:00 2001 From: qth2hi Date: Mon, 5 Dec 2022 14:19:16 +0100 Subject: [PATCH 02/18] Added executerobottest.py to support the TestTrigger --- atest/executerobottest.py | 176 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 atest/executerobottest.py diff --git a/atest/executerobottest.py b/atest/executerobottest.py new file mode 100644 index 00000000..2e5fdd3a --- /dev/null +++ b/atest/executerobottest.py @@ -0,0 +1,176 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# executerobottest.py +# +# XC-CT/ECA3-Queckenstedt +# +# Executes robot tests recursively in current folder. +# Log file can be set in command line. If not, default log is written. +# Additional command line for involved framework can also be set in command line (of this script). +# +# -------------------------------------------------------------------------------------------------------------- +# +# 09.11.2022 +# +# -------------------------------------------------------------------------------------------------------------- + +import os, sys, platform, shlex, subprocess, shutil, argparse + +import colorama as col + +from PythonExtensionsCollection.String.CString import CString +from PythonExtensionsCollection.File.CFile import CFile +from PythonExtensionsCollection.Folder.CFolder import CFolder + +col.init(autoreset=True) + +COLBR = col.Style.BRIGHT + col.Fore.RED +COLBY = col.Style.BRIGHT + col.Fore.YELLOW +COLBG = col.Style.BRIGHT + col.Fore.GREEN + +SUCCESS = 0 +ERROR = 1 + +# -------------------------------------------------------------------------------------------------------------- + +def printerror(sMsg): + sys.stderr.write(COLBR + f"Error: {sMsg}!\n") + +def printexception(sMsg): + sys.stderr.write(COLBR + f"Exception: {sMsg}!\n") + +# -------------------------------------------------------------------------------------------------------------- + +# -- some informations about the environment of this script + +sThisScript = sys.argv[0] +sThisScript = CString.NormalizePath(sThisScript) +sThisScriptPath = os.path.dirname(sThisScript) +sThisScriptName = os.path.basename(sThisScript) + +sOSName = os.name +sPlatformSystem = platform.system() +sPythonPath = CString.NormalizePath(os.path.dirname(sys.executable)) +sPython = CString.NormalizePath(sys.executable) +sPythonVersion = sys.version + +if sPlatformSystem == "Windows": + # nothing specific to do + pass +elif sPlatformSystem == "Linux": + # nothing specific to do + pass +else: + bSuccess = False + sResult = f"Operating system {sPlatformSystem} ({sOSName}) not supported" + printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) + sys.exit(ERROR) + +print() +print(f"{sThisScriptName} is running under {sPlatformSystem} ({sOSName})") +print() + +# -- parse the command line of this script (optional path and name of robot xml log file) + +oCmdLineParser = argparse.ArgumentParser() +oCmdLineParser.add_argument('--logfile', type=str, help='Path and name of XML log file (optional).') +oCmdLineParser.add_argument('--robotcommandline', type=str, help='Command line for RobotFramework AIO (optional).') +oCmdLineArgs = oCmdLineParser.parse_args() + +sLogFile = None +if oCmdLineArgs.logfile is not None: + sLogFile = CString.NormalizePath(oCmdLineArgs.logfile, sReferencePathAbs=sThisScriptPath) +else: + # default log + sLogFile = f"{sThisScriptPath}/logfiles/RobotTestLog.xml" + +sRobotCommandLine = None +if oCmdLineArgs.robotcommandline is not None: + sRobotCommandLine = oCmdLineArgs.robotcommandline + +# -- create the log file folder + +oLogFile = CFile(sLogFile) +dLogFileInfo = oLogFile.GetFileInfo() +sLogFilePath = dLogFileInfo['sFilePath'] +sLogFileName = dLogFileInfo['sFileName'] +sLogFileNameOnly = dLogFileInfo['sFileNameOnly'] + +oLogFilePath = CFolder(sLogFilePath) +bSuccess, sResult = oLogFilePath.Create(bOverwrite=False, bRecursive=True) +del oLogFilePath +if bSuccess is not True: + printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) + sys.exit(ERROR) +print(sResult) +print() + +# -- prepare the command line for the test execution + +listCmdLineParts = [] +listCmdLineParts.append(f"\"{sPython}\"") +listCmdLineParts.append("-m robot") +if sRobotCommandLine is not None: + listCmdLineParts.append(f"{sRobotCommandLine}") +listCmdLineParts.append(f"-d \"{sLogFilePath}\"") +listCmdLineParts.append(f"-o \"{sLogFileName}\"") +listCmdLineParts.append(f"-l \"{sLogFileNameOnly}_log.html\"") +listCmdLineParts.append(f"-r \"{sLogFileNameOnly}_report.html\"") +listCmdLineParts.append(f"-b \"{sLogFileNameOnly}.log\"") +listCmdLineParts.append(f"\"{sThisScriptPath}\"") +sCmdLine = " ".join(listCmdLineParts) +del listCmdLineParts + +# -- execute the tests + +print(f"Now executing command line:\n{sCmdLine}") +print() + +listCmdLineParts = shlex.split(sCmdLine) + +nReturn = ERROR +try: + nReturn = subprocess.call(listCmdLineParts) + print() + print(f"[{sThisScriptName}] : Subprocess ROBOT returned {nReturn}") +except Exception as ex: + print() + printexception(str(ex)) + print() + sys.exit(ERROR) +print() + +if nReturn == SUCCESS: + print(f"Test results in '{sLogFile}'") + print() + print(COLBG + f"{sThisScriptName} done") +else: + printerror(f"[{sThisScriptName}] : Subprocess ROBOT has not returned expected value {SUCCESS}") + nReturn = -nReturn + +print() + +# nReturn: +# > 0 : internal error of this script +# < 0 : return value (!= 0) from subprocess +# == 0 : no internal error of this script and no error from subprocess + +sys.exit(nReturn) + +# -------------------------------------------------------------------------------------------------------------- From 89ac8aeec319a59ae9550e440af17e009aa28bc0 Mon Sep 17 00:00:00 2001 From: qth2hi Date: Tue, 6 Dec 2022 11:33:10 +0100 Subject: [PATCH 03/18] Replaced executerobottest.py by executepytest.py (because of planned rework of the atest) --- atest/executerobottest.py | 176 -------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 atest/executerobottest.py diff --git a/atest/executerobottest.py b/atest/executerobottest.py deleted file mode 100644 index 2e5fdd3a..00000000 --- a/atest/executerobottest.py +++ /dev/null @@ -1,176 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# executerobottest.py -# -# XC-CT/ECA3-Queckenstedt -# -# Executes robot tests recursively in current folder. -# Log file can be set in command line. If not, default log is written. -# Additional command line for involved framework can also be set in command line (of this script). -# -# -------------------------------------------------------------------------------------------------------------- -# -# 09.11.2022 -# -# -------------------------------------------------------------------------------------------------------------- - -import os, sys, platform, shlex, subprocess, shutil, argparse - -import colorama as col - -from PythonExtensionsCollection.String.CString import CString -from PythonExtensionsCollection.File.CFile import CFile -from PythonExtensionsCollection.Folder.CFolder import CFolder - -col.init(autoreset=True) - -COLBR = col.Style.BRIGHT + col.Fore.RED -COLBY = col.Style.BRIGHT + col.Fore.YELLOW -COLBG = col.Style.BRIGHT + col.Fore.GREEN - -SUCCESS = 0 -ERROR = 1 - -# -------------------------------------------------------------------------------------------------------------- - -def printerror(sMsg): - sys.stderr.write(COLBR + f"Error: {sMsg}!\n") - -def printexception(sMsg): - sys.stderr.write(COLBR + f"Exception: {sMsg}!\n") - -# -------------------------------------------------------------------------------------------------------------- - -# -- some informations about the environment of this script - -sThisScript = sys.argv[0] -sThisScript = CString.NormalizePath(sThisScript) -sThisScriptPath = os.path.dirname(sThisScript) -sThisScriptName = os.path.basename(sThisScript) - -sOSName = os.name -sPlatformSystem = platform.system() -sPythonPath = CString.NormalizePath(os.path.dirname(sys.executable)) -sPython = CString.NormalizePath(sys.executable) -sPythonVersion = sys.version - -if sPlatformSystem == "Windows": - # nothing specific to do - pass -elif sPlatformSystem == "Linux": - # nothing specific to do - pass -else: - bSuccess = False - sResult = f"Operating system {sPlatformSystem} ({sOSName}) not supported" - printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) - sys.exit(ERROR) - -print() -print(f"{sThisScriptName} is running under {sPlatformSystem} ({sOSName})") -print() - -# -- parse the command line of this script (optional path and name of robot xml log file) - -oCmdLineParser = argparse.ArgumentParser() -oCmdLineParser.add_argument('--logfile', type=str, help='Path and name of XML log file (optional).') -oCmdLineParser.add_argument('--robotcommandline', type=str, help='Command line for RobotFramework AIO (optional).') -oCmdLineArgs = oCmdLineParser.parse_args() - -sLogFile = None -if oCmdLineArgs.logfile is not None: - sLogFile = CString.NormalizePath(oCmdLineArgs.logfile, sReferencePathAbs=sThisScriptPath) -else: - # default log - sLogFile = f"{sThisScriptPath}/logfiles/RobotTestLog.xml" - -sRobotCommandLine = None -if oCmdLineArgs.robotcommandline is not None: - sRobotCommandLine = oCmdLineArgs.robotcommandline - -# -- create the log file folder - -oLogFile = CFile(sLogFile) -dLogFileInfo = oLogFile.GetFileInfo() -sLogFilePath = dLogFileInfo['sFilePath'] -sLogFileName = dLogFileInfo['sFileName'] -sLogFileNameOnly = dLogFileInfo['sFileNameOnly'] - -oLogFilePath = CFolder(sLogFilePath) -bSuccess, sResult = oLogFilePath.Create(bOverwrite=False, bRecursive=True) -del oLogFilePath -if bSuccess is not True: - printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) - sys.exit(ERROR) -print(sResult) -print() - -# -- prepare the command line for the test execution - -listCmdLineParts = [] -listCmdLineParts.append(f"\"{sPython}\"") -listCmdLineParts.append("-m robot") -if sRobotCommandLine is not None: - listCmdLineParts.append(f"{sRobotCommandLine}") -listCmdLineParts.append(f"-d \"{sLogFilePath}\"") -listCmdLineParts.append(f"-o \"{sLogFileName}\"") -listCmdLineParts.append(f"-l \"{sLogFileNameOnly}_log.html\"") -listCmdLineParts.append(f"-r \"{sLogFileNameOnly}_report.html\"") -listCmdLineParts.append(f"-b \"{sLogFileNameOnly}.log\"") -listCmdLineParts.append(f"\"{sThisScriptPath}\"") -sCmdLine = " ".join(listCmdLineParts) -del listCmdLineParts - -# -- execute the tests - -print(f"Now executing command line:\n{sCmdLine}") -print() - -listCmdLineParts = shlex.split(sCmdLine) - -nReturn = ERROR -try: - nReturn = subprocess.call(listCmdLineParts) - print() - print(f"[{sThisScriptName}] : Subprocess ROBOT returned {nReturn}") -except Exception as ex: - print() - printexception(str(ex)) - print() - sys.exit(ERROR) -print() - -if nReturn == SUCCESS: - print(f"Test results in '{sLogFile}'") - print() - print(COLBG + f"{sThisScriptName} done") -else: - printerror(f"[{sThisScriptName}] : Subprocess ROBOT has not returned expected value {SUCCESS}") - nReturn = -nReturn - -print() - -# nReturn: -# > 0 : internal error of this script -# < 0 : return value (!= 0) from subprocess -# == 0 : no internal error of this script and no error from subprocess - -sys.exit(nReturn) - -# -------------------------------------------------------------------------------------------------------------- From 41708dd95fc35a19d8214ee2b19a1bf6f5be2350 Mon Sep 17 00:00:00 2001 From: qth2hi Date: Wed, 14 Dec 2022 10:39:40 +0100 Subject: [PATCH 04/18] tiny fixes --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index 059d94b5..a27af4ab 100644 --- a/README.rst +++ b/README.rst @@ -35,9 +35,16 @@ How to install 1. Installation via PyPi (recommended for users) +<<<<<<< HEAD .. code:: pip install JsonPreprocessor +======= +.. code:: + + setup.py build will build the package underneath 'build/' + setup.py install will install the package +>>>>>>> 6218d44 (tiny fixes) `JsonPreprocessor in PyPi `_ From f855691e2cc3c49fd2c87bcd4aefebe51846c116 Mon Sep 17 00:00:00 2001 From: ngoan1608 Date: Tue, 17 Jan 2023 16:41:32 +0700 Subject: [PATCH 05/18] rename test file so that pytest can collect it for execution --- .../{jsonpreprocessor_unittest.py => test_jsonpreprocessor.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename atest/jsonpreprocessor/{jsonpreprocessor_unittest.py => test_jsonpreprocessor.py} (100%) diff --git a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py b/atest/jsonpreprocessor/test_jsonpreprocessor.py similarity index 100% rename from atest/jsonpreprocessor/jsonpreprocessor_unittest.py rename to atest/jsonpreprocessor/test_jsonpreprocessor.py From 33a0826b12d021882b8675431220aa447289f8e8 Mon Sep 17 00:00:00 2001 From: ngoan1608 Date: Tue, 17 Jan 2023 16:44:42 +0700 Subject: [PATCH 06/18] ignore build stuff and logfiles --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ba0430d2..58c2bad0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -__pycache__/ \ No newline at end of file +__pycache__/ +build/ +aiotestlogfiles/ +logfiles/ \ No newline at end of file From aa29cd2d98ea43126372b2d3e8466fc4dd536a8e Mon Sep 17 00:00:00 2001 From: qth2hi Date: Thu, 2 Feb 2023 16:05:58 +0100 Subject: [PATCH 07/18] Removed PythonExtensionsCollection (because is already part of the install dependencies) --- .../PythonExtensionsCollection/File/CFile.py | 1072 --------------- .../File/__init__.py | 13 - .../Folder/CFolder.py | 460 ------- .../Folder/__init__.py | 13 - .../String/CString.py | 1165 ----------------- .../String/__init__.py | 13 - .../Utils/CUtils.py | 374 ------ .../Utils/__init__.py | 13 - .../PythonExtensionsCollection/__init__.py | 13 - .../PythonExtensionsCollection/version.py | 23 - 10 files changed, 3159 deletions(-) delete mode 100644 additions/PythonExtensionsCollection/File/CFile.py delete mode 100644 additions/PythonExtensionsCollection/File/__init__.py delete mode 100644 additions/PythonExtensionsCollection/Folder/CFolder.py delete mode 100644 additions/PythonExtensionsCollection/Folder/__init__.py delete mode 100644 additions/PythonExtensionsCollection/String/CString.py delete mode 100644 additions/PythonExtensionsCollection/String/__init__.py delete mode 100644 additions/PythonExtensionsCollection/Utils/CUtils.py delete mode 100644 additions/PythonExtensionsCollection/Utils/__init__.py delete mode 100644 additions/PythonExtensionsCollection/__init__.py delete mode 100644 additions/PythonExtensionsCollection/version.py diff --git a/additions/PythonExtensionsCollection/File/CFile.py b/additions/PythonExtensionsCollection/File/CFile.py deleted file mode 100644 index 320424c6..00000000 --- a/additions/PythonExtensionsCollection/File/CFile.py +++ /dev/null @@ -1,1072 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# CFile.py -# -# XC-CT/ECA3-Queckenstedt -# -# 27.06.2022 -# -# ************************************************************************************************************** - -# -- import standard Python modules -import os, shutil, platform - -# -- import Bosch Python modules -from PythonExtensionsCollection.String.CString import CString - -# ************************************************************************************************************** - -class enFileStatiType: - """ -The class ``enFileStatiType`` defines the sollowing file states: - -* ``closed`` -* ``openedforwriting`` -* ``openedforappending`` -* ``openedforreading`` - """ - closed = "closed" - openedforwriting = "openedforwriting" - openedforappending = "openedforappending" - openedforreading = "openedforreading" - -# -------------------------------------------------------------------------------------------------------------- - -class CFile(object): - """ -The class ``CFile`` provides a small set of file functions with extended parametrization (like switches -defining if a file is allowed to be overwritten or not). - -Most of the functions at least returns ``bSuccess`` and ``sResult``. - -* ``bSuccess`` is ``True`` in case of no error occurred. -* ``bSuccess`` is ``False`` in case of an error occurred. -* ``bSuccess`` is ``None`` in case of a very fatal error occurred (exceptions). - -* ``sResult`` contains details about what happens during computation. - -Every instance of CFile handles one single file only and forces exclusive access to this file. - -It is not possible to create an instance of this class with a file that is already in use by another instance. - -It is also not possible to use ``CopyTo`` or ``MoveTo`` to overwrite files that are already in use by another instance. -This makes the file handling more save against access violations. - """ - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __init__(self, sFile=None): - self.__sFile = CString.NormalizePath(sFile) - self.__oFileHandle = None - self.__oFileStatus = enFileStatiType.closed - self.__sLastDestination = None - - try: - CFile.__listFilesInUse - except: - CFile.__listFilesInUse = [] - - # exclusive access is required (checked by self.__bIsFreeToUse; relevant for destination in CopyTo and MoveTo) - if self.__sFile in CFile.__listFilesInUse: - raise Exception(f"The file '{self.__sFile}' is already in use by another CFile instance.") - else: - CFile.__listFilesInUse.append(self.__sFile) - - # eof def __init__(self, sFile=None): - - def __del__(self): - self.Close() - if self.__sFile in CFile.__listFilesInUse: - CFile.__listFilesInUse.remove(self.__sFile) - - # eof def __del__(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __bIsFreeToUse(self, sFile=None): - """ -Checks if the file ``sFile`` is free to use, that means: not used by another instance of ``CFile``. - """ - - bIsFreeToUse = False # init - if sFile is None: - bIsFreeToUse = False # error handling - else: - if sFile in CFile.__listFilesInUse: - bIsFreeToUse = False - else: - bIsFreeToUse = True - return bIsFreeToUse - - # eof def __bIsFreeToUse(self, sFile=None): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __OpenForWriting(self): - """ -Opens a text file for writing. - -Returns ``bSuccess`` and ``sResult`` (feedback). - """ - - sMethod = "CFile.__OpenForWriting" - - if self.__sFile is None: - bSuccess = False - sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - try: - self.__oFileHandle = open(self.__sFile, "w", encoding="utf-8") - self.__oFileStatus = enFileStatiType.openedforwriting - bSuccess = True - sResult = f"File '{self.__sFile}' is open for writing" - except Exception as reason: - self.Close() - bSuccess = None - sResult = f"Not possible to open file '{self.__sFile}' for writing.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def __OpenForWriting(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __OpenForAppending(self): - """ -Opens a text file for appending. - -Returns ``bSuccess`` and ``sResult`` (feedback). - """ - - sMethod = "CFile.__OpenForAppending" - - if self.__sFile is None: - bSuccess = False - sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - try: - self.__oFileHandle = open(self.__sFile, "a", encoding="utf-8") - self.__oFileStatus = enFileStatiType.openedforappending - bSuccess = True - sResult = f"File '{self.__sFile}' is open for appending" - except Exception as reason: - self.Close() - bSuccess = None - sResult = f"Not possible to open file '{self.__sFile}' for appending.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def __OpenForAppending(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __OpenForReading(self): - """ -Opens a text file for reading. - -Returns ``bSuccess`` and ``sResult`` (feedback). - """ - - sMethod = "CFile.__OpenForReading" - - if self.__sFile is None: - bSuccess = False - sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - try: - self.__oFileHandle = open(self.__sFile, "r", encoding="utf-8") - self.__oFileStatus = enFileStatiType.openedforreading - bSuccess = True - sResult = f"File '{self.__sFile}' is open for reading" - except Exception as reason: - self.Close() - bSuccess = None - sResult = f"Not possible to open file '{self.__sFile}' for reading.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def __OpenForReading(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Close(self): - """ -Closes the opened file. - -**Arguments:** - -(no args) - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFile.Close" - - if self.__oFileHandle is not None: - try: - self.__oFileHandle.flush() - self.__oFileHandle.close() - bSuccess = True - sResult = f"File '{self.__sFile}' closed" - except Exception as reason: - bSuccess = None - sResult = f"Exception while closing file '{self.__sFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - self.__oFileHandle = None - else: - bSuccess = True - sResult = "Done" - - self.__oFileStatus = enFileStatiType.closed - - return bSuccess, sResult - - # eof def Close(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Delete(self, bConfirmDelete=True): - """ -Deletes the current file. - -**Arguments:** - -* ``bConfirmDelete`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - Defines if it will be handled as error if the file does not exist. - - If ``True``: If the file does not exist, the method indicates an error (``bSuccess = False``). - - If ``False``: It doesn't matter if the file exists or not. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - - sMethod = "CFile.Delete" - - if self.__sFile is None: - bSuccess = False - sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isfile(self.__sFile) is False: - if bConfirmDelete is True: - bSuccess = False - else: - bSuccess = True - sResult = f"Nothing to delete. The file '{self.__sFile}' does not exist." - return bSuccess, sResult - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - try: - os.remove(self.__sFile) - bSuccess = True - sResult = f"File '{self.__sFile}' deleted." - except Exception as reason: - bSuccess = None - sResult = f"Exception while deleting file '{self.__sFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def Delete(self, bConfirmDelete=True): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __PrepareOutput(self, Content=""): - """ -Helper for ``Write`` and ``Append`` (consideration of composite data types). - -Returns a list of strings (that will be written to file). - """ - - listOut = [] - - if type(Content) == list: - for element in Content: - listOut.append(str(element)) - elif type(Content) == tuple: - for element in Content: - listOut.append(str(element)) - elif type(Content) == set: - for element in Content: - listOut.append(str(element)) - elif type(Content) == dict: - listKeys = Content.keys() - nRJust = 0 - for key in listKeys: - sKey = str(key) # because also numerical values can be keys - if len(sKey) > nRJust: - nRJust = len(sKey) - for key in listKeys: - sKey = str(key) # because also numerical values can be keys - sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) - listOut.append(sOut) - elif str(type(Content)).lower().find('dotdict') >=0: - try: - listKeys = Content.keys() - nRJust = 0 - for key in listKeys: - sKey = str(key) # because also numerical values can be keys - if len(sKey) > nRJust: - nRJust = len(sKey) - for key in listKeys: - sKey = str(key) # because also numerical values can be keys - sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) - listOut.append(sOut) - except Exception as reason: - listOut.append(str(Content)) - else: - listOut.append(str(Content)) - - return listOut - - # eof def __PrepareOutput(self, Content=""): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): - """ -Writes the content of a variable ``Content`` to file. - -**Arguments:** - -* ``Content`` - - / *Condition*: required / *Type*: one of: str, list, tuple, set, dict, dotdict / - - If ``Content`` is not a string, the ``Write`` method resolves the data structure before writing the content to file. - -* ``nVSpaceAfter`` - - / *Condition*: optional / *Type*: int / *Default*: 0 / - - Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. - -* ``sPrefix`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - `sPrefix`` is added to every line of output (in case of ``sPrefix`` is not ``None``). - -* ``bToScreen`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - Prints ``Content`` also to screen (in case of ``bToScreen`` is ``True``). - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - - sMethod = "CFile.Write" - - if self.__oFileStatus != enFileStatiType.openedforwriting: - bSuccess, sResult = self.__OpenForWriting() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - listOut = self.__PrepareOutput(Content) - - for nCnt in range(nVSpaceAfter): - listOut.append("") - - if bToScreen is True: - for sOut in listOut: - if ( (sPrefix is not None) and (sOut != '') ): - sOut = f"{sPrefix}{sOut}" - print(sOut) - - bSuccess = True - sResult = "Done" - try: - for sOut in listOut: - if ( (sPrefix is not None) and (sOut != '') ): - sOut = f"{sPrefix}{sOut}" - self.__oFileHandle.write(sOut + "\n") - except Exception as reason: - bSuccess = None - sResult = f"Not possible to write to file '{self.__sFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): - """ -Appends the content of a variable ``Content`` to file. - -**Arguments:** - -* ``Content`` - - / *Condition*: required / *Type*: one of: str, list, tuple, set, dict, dotdict / - - If ``Content`` is not a string, the ``Write`` method resolves the data structure before writing the content to file. - -* ``nVSpaceAfter`` - - / *Condition*: optional / *Type*: int / *Default*: 0 / - - Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. - -* ``sPrefix`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - `sPrefix`` is added to every line of output (in case of ``sPrefix`` is not ``None``). - -* ``bToScreen`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - Prints ``Content`` also to screen (in case of ``bToScreen`` is ``True``). - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFile.Append" - - if self.__oFileStatus != enFileStatiType.openedforappending: - bSuccess, sResult = self.__OpenForAppending() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - listOut = self.__PrepareOutput(Content) - - for nCnt in range(nVSpaceAfter): - listOut.append("") - - if bToScreen is True: - for sOut in listOut: - if ( (sPrefix is not None) and (sOut != '') ): - sOut = f"{sPrefix}{sOut}" - print(sOut) - - bSuccess = True - sResult = "Done" - try: - for sOut in listOut: - if ( (sPrefix is not None) and (sOut != '') ): - sOut = f"{sPrefix}{sOut}" - self.__oFileHandle.write(sOut + "\n") - except Exception as reason: - bSuccess = None - sResult = f"Not possible to append to file '{self.__sFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def ReadLines(self, - bCaseSensitive = True, - bSkipBlankLines = False, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None, - bLStrip = False, - bRStrip = True, - bToScreen = False): - """ -Reads content from current file. Returns an array of lines together with ``bSuccess`` and ``sResult`` (feedback). - -The method takes care of opening and closing the file. The complete file content is read by ``ReadLines`` in one step, -but with the help of further parameters it is possible to reduce the content by including and excluding lines. - -The logical join of all filter is: ``AND``. - -**Arguments:** - -* ``bCaseSensitive`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - * If ``True``, the standard filters work case sensitive, otherwise not. - * This has no effect to the regular expression based filters ``sInclRegEx`` and ``sExclRegEx``. - -* ``bSkipBlankLines`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True``, blank lines will be skipped, otherwise not. - -* ``sComment`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - In case of a line starts with the string ``sComment``, this line is skipped. - -* ``sStartsWith`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string starts with the string ``sStartsWith`` - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -* ``sEndsWith`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string ends with the string ``sEndsWith`` - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -* ``sStartsNotWith`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string starts not with the string ``sStartsNotWith`` - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -* ``sEndsNotWith`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string ends not with the string ``sEndsNotWith`` - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -* ``sContains`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string contains the string ``sContains`` at any position - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -* ``sContainsNot`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * The criterion of this filter is fulfilled in case of the input string does **not** contain the string ``sContainsNot`` at any position - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -* ``sInclRegEx`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * *Include* filter based on regular expressions (consider the syntax of regular expressions!) - * The criterion of this filter is fulfilled in case of the regular expression ``sInclRegEx`` matches the input string - * Leading and trailing blanks within the input string are considered - * ``bCaseSensitive`` has no effect - * A semicolon separated list of several regular expressions is **not** supported - -* ``sExclRegEx`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - * *Exclude* filter based on regular expressions (consider the syntax of regular expressions!) - * The criterion of this filter is fulfilled in case of the regular expression ``sExclRegEx`` does **not** match the input string - * Leading and trailing blanks within the input string are considered - * ``bCaseSensitive`` has no effect - * A semicolon separated list of several regular expressions is **not** supported - -* ``bLStrip`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True``, leading spaces are removed from line before the filters are used, otherwise not. - -* ``bRStrip`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - If ``True``, trailing spaces are removed from line before the filters are used, otherwise not. - -* ``bToScreen`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True``, the content read from file is also printed to screen, otherwise not. - """ - - sMethod = "CFile.ReadLines" - - listLines = [] - - if os.path.isfile(self.__sFile) is False: - bSuccess = False - sResult = f"The file '{self.__sFile}' does not exist." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return listLines, bSuccess, sResult - - # !!! independend from: self.__oFileStatus != enFileStatiType.openedforreading: !!! - # Reason: Repeated call of ReadLines needs to have the read pointer at the beginning of the file. - bSuccess, sResult = self.__OpenForReading() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return listLines, bSuccess, sResult - - try: - sFileContent = self.__oFileHandle.read() - except Exception as reason: - bSuccess = None - sResult = f"Not possible to read from file '{self.__sFile}'.\nReason: " + str(reason) - return listLines, bSuccess, sResult - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return listLines, bSuccess, sResult - - listFileContent = sFileContent.splitlines() # in opposite to readlines this is OS independend! - - for sLine in listFileContent: - if CString.StringFilter(sString = sLine, - bCaseSensitive = bCaseSensitive, - bSkipBlankStrings = bSkipBlankLines, - sComment = sComment, - sStartsWith = sStartsWith, - sEndsWith = sEndsWith, - sStartsNotWith = sStartsNotWith, - sEndsNotWith = sEndsNotWith, - sContains = sContains, - sContainsNot = sContainsNot, - sInclRegEx = sInclRegEx, - sExclRegEx = sExclRegEx, - bDebug = False) is True: - if bLStrip is True: - sLine = sLine.lstrip(" \t\r\n") - - if bRStrip is True: - sLine = sLine.rstrip(" \t\r\n") - - if bToScreen is True: - print(sLine) - - listLines.append(sLine) - - # eof for sLine in listFileContent: - - del listFileContent - - nNrOfLines = len(listLines) - - bSuccess = True - sResult = f"Read {nNrOfLines} lines from '{self.__sFile}'." - return listLines, bSuccess, sResult - - # eof def ReadLines(...) - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def GetFileInfo(self): - """ -Returns the following informations about the file (encapsulated within a dictionary ``dFileInfo``): - -**Returns:** - -* Key ``sFile`` - - / *Type*: str / - - Path and name of current file - - -* Key ``bFileIsExisting`` - - / *Type*: bool / - - ``True`` if file is existing, otherwise ``False`` - -* Key ``sFileName`` - - / *Type*: str / - - The name of the current file (incl. extension) - -* Key ``sFileExtension`` - - / *Type*: str / - - The extension of the current file - -* Key ``sFileNameOnly`` - - / *Type*: str / - - The pure name of the current file (without extension) - -* Key ``sFilePath`` - - / *Type*: str / - - The the path to current file - -* Key ``bFilePathIsExisting`` - - / *Type*: bool / - - ``True`` if file path is existing, otherwise ``False`` - """ - - sMethod = "CFile.GetFileInfo" - - dFileInfo = {} - dFileInfo['sFile'] = None - dFileInfo['bFileIsExisting'] = None - dFileInfo['sFileName'] = None - dFileInfo['sFileExtension'] = None - dFileInfo['sFileNameOnly'] = None - dFileInfo['sFilePath'] = None - dFileInfo['bFilePathIsExisting'] = None - - if self.__sFile is None: - return None - - dFileInfo['sFile'] = self.__sFile - dFileInfo['bFileIsExisting'] = os.path.isfile(self.__sFile) - - sFileName = os.path.basename(self.__sFile) - dFileInfo['sFileName'] = sFileName - - sFileExtension = "" - sFileNameOnly = "" - listParts = sFileName.split('.') - if len(listParts) > 1: - sFileExtension = listParts[len(listParts)-1] - sFileNameOnly = sFileName[:-len(sFileExtension)-1] - else: - sFileExtension = "" - sFileNameOnly = sFileName - - dFileInfo['sFileExtension'] = sFileExtension - dFileInfo['sFileNameOnly'] = sFileNameOnly - dFileInfo['sFilePath'] = os.path.dirname(self.__sFile) - dFileInfo['bFilePathIsExisting'] = os.path.isdir(dFileInfo['sFilePath']) - - return dFileInfo - - # eof def GetFileInfo(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def CopyTo(self, sDestination=None, bOverwrite=False): - """ -Copies the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. - -In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. - -In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten -and ``CopyTo`` returns ``bSuccess = False``. - -**Arguments:** - -* ``sDestination`` - - / *Condition*: required / *Type*: string / - - The path to destination file (either incl. file name or without file name) - -* ``bOverwrite`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - * In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. - * In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten - and ``CopyTo`` returns ``bSuccess = False``. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFile.CopyTo" - - if self.__sFile is None: - bSuccess = False - sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isfile(self.__sFile) is False: - bSuccess = False - sResult = f"The file '{self.__sFile}' does not exist, therefore nothing can be copied." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if sDestination is None: - bSuccess = False - sResult = "sDestination is None; please provide path and name of destination file. Or at least the destination path. In this case the file name will be taken over." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - sDestination = CString.NormalizePath(sDestination) - - bDeleteDestFile = False - - sDestFile = sDestination # default - - if os.path.isdir(sDestination) is True: - sFileName = os.path.basename(self.__sFile) - sDestFile = f"{sDestination}/{sFileName}" # file name in destination is required for: shutil.copyfile - - if self.__bIsFreeToUse(sDestFile) is False: - bSuccess = False - sResult = f"The destination file '{sDestFile}' is already in use by another CFile instance." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - self.__sLastDestination = sDestFile - - if os.path.isfile(sDestFile) is True: - # destination file already exists - if sDestFile == self.__sFile: - bSuccess = False - sResult = f"Source file and destination file are the same: '{self.__sFile}'. Therefore nothing to do." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if bOverwrite is True: - bDeleteDestFile = True - else: - bSuccess = False - sResult = f"Not allowed to overwrite existing destination file '{sDestFile}'. Therefore nothing to do." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - else: - # destination file not yet exists - # (we assume here that the destination shall be a file because we already have figured out that the destination is not a folder) - # => we have to check if the path to the file exists - sDestFilePath = os.path.dirname(sDestFile) - if os.path.isdir(sDestFilePath) is True: - bDeleteDestFile = False - else: - bSuccess = False - sResult = f"The destination path '{sDestFilePath}' does not exist. The file '{self.__sFile}' cannot be copied." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - # eof else - if os.path.isfile(sDestFile) is True: - - # analysis done, now the action - - bSuccess, sResult = self.Close() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if bDeleteDestFile is True: - # To delete the destination file explicitely before executing any copy-function is an addon here in this library. - # The purpose is to be independend from the way the used copy function is handling existing destination files. - # But this makes only sense under Windows and not under Linux, because Windows is much more strict with access - # violations than Linux. Therefore we avoid such kind of additional steps in case of the platform is not Windows. - if platform.system() == "Windows": - try: - os.remove(sDestFile) - bSuccess = True - sResult = f"File '{sDestFile}' deleted." - except Exception as reason: - bSuccess = None - sResult = f"Exception while deleting destination file '{sDestFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - # eof if bDeleteDestFile is True: - - try: - shutil.copyfile(self.__sFile, sDestFile) - bSuccess = True - sResult = f"File '{self.__sFile}' copied to '{sDestFile}'." - except Exception as reason: - bSuccess = None - sResult = f"Exception while copying file '{self.__sFile}' to '{sDestFile}'.\nReason: " + str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def CopyTo(self, sDestination=None, bOverwrite=False): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def MoveTo(self, sDestination=None, bOverwrite=False): - """ -Moves the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. - -**Arguments:** - -* ``sDestination`` - - / *Condition*: required / *Type*: string / - - The path to destination file (either incl. file name or without file name) - -* ``bOverwrite`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - * In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. - * In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten - and ``MoveTo`` returns ``bSuccess = False``. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation was successful or not - -* ``sResult`` - - / *Type*: str / - - Contains details about what happens during computation - """ - sMethod = "CFile.MoveTo" - - bSuccess, sResult = self.CopyTo(sDestination, bOverwrite) - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isfile(self.__sLastDestination) is False: - # the copied file should exist at new location - bSuccess = None - sResult = f"Someting went wrong while copying the file '{self.__sFile}' to '{self.__sLastDestination}'. Aborting." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - else: - bSuccess, sResult = self.Delete() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - bSuccess = True - sResult = f"File moved from '{self.__sFile}' to '{self.__sLastDestination}'" - return bSuccess, sResult - - # eof def MoveTo(self, sDestination=None, bOverwrite=False): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - -# eof class CFile(object): - -# ************************************************************************************************************** - - diff --git a/additions/PythonExtensionsCollection/File/__init__.py b/additions/PythonExtensionsCollection/File/__init__.py deleted file mode 100644 index 958420af..00000000 --- a/additions/PythonExtensionsCollection/File/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/additions/PythonExtensionsCollection/Folder/CFolder.py b/additions/PythonExtensionsCollection/Folder/CFolder.py deleted file mode 100644 index 451c32f9..00000000 --- a/additions/PythonExtensionsCollection/Folder/CFolder.py +++ /dev/null @@ -1,460 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# CFolder.py -# -# XC-CT/ECA3-Queckenstedt -# -# 28.06.2022 -# -# ************************************************************************************************************** - -# -- import standard Python modules -import os, shutil, time, stat - -# -- import Bosch Python modules -from PythonExtensionsCollection.String.CString import CString - -# -------------------------------------------------------------------------------------------------------------- - -# little helper to delete folders containing files that are write protected -def rm_dir_readonly(func, path, excinfo): - """ -Calls ``os.chmod`` in case of ``shutil.rmtree`` (within ``Delete()``) throws an exception (making files writable). - """ - # print(f"{excinfo}") # debug only - os.chmod(path, stat.S_IWRITE) - func(path) - -# -------------------------------------------------------------------------------------------------------------- - -class CFolder(object): - """ -The class ``CFolder`` provides a small set of folder functions with extended parametrization (like switches -defining if a folder is allowed to be overwritten or not). - -Most of the functions at least returns ``bSuccess`` and ``sResult``. - -* ``bSuccess`` is ``True`` in case of no error occurred. -* ``bSuccess`` is ``False`` in case of an error occurred. -* ``bSuccess`` is ``None`` in case of a very fatal error occurred (exceptions). - -* ``sResult`` contains details about what happens during computation. - -Every instance of CFolder handles one single folder only and forces exclusive access to this folder. - -It is not possible to create an instance of this class with a folder that is already in use by another instance. - -The constructor of ``CFolder`` requires the input parameter ``sFolder``, that is the path and the name of a folder -that is handled by the current class instance. - """ - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __init__(self, sFolder=None): - self.__sFolder = CString.NormalizePath(sFolder) - - try: - CFolder.__listFoldersInUse - except: - CFolder.__listFoldersInUse = [] - - # exclusive access is required (checked by self.__bIsFreeToUse; relevant for destination in CopyTo and MoveTo) - if self.__sFolder in CFolder.__listFoldersInUse: - raise Exception(f"The folder '{self.__sFolder}' is already in use by another CFolder instance.") - else: - CFolder.__listFoldersInUse.append(self.__sFolder) - - # eof def __init__(self, sFolder=None): - - def __del__(self): - if self.__sFolder in CFolder.__listFoldersInUse: - CFolder.__listFoldersInUse.remove(self.__sFolder) - - # eof def __del__(self): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __bIsFreeToUse(self, sFolder=None): - """ -Checks if the folder ``sFolder`` is free to use, that means: not used by another instance of ``CFolder``. - """ - - bIsFreeToUse = False # init - if sFolder is None: - bIsFreeToUse = False # error handling - else: - if sFolder in CFolder.__listFoldersInUse: - bIsFreeToUse = False - else: - bIsFreeToUse = True - return bIsFreeToUse - - # eof def __bIsFreeToUse(self, sFolder=None): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def __Delete(self, sFolder=None, bConfirmDelete=True): - """ -Deletes the folder ``sFolder``. - -**Arguments:** - -* ``sFolder`` - - / *Condition*: required / *Type*: str / - - Path and name of folder to be deleted - -* ``bConfirmDelete`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - Defines if it will be handled as error if the folder does not exist. - - If ``True``: If the folder does not exist, the method indicates an error (``bSuccess = False``). - - If ``False``: It doesn't matter if the folder exists or not. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFolder.__Delete" - - if sFolder is None: - bSuccess = False - sResult = "sFolder is None; please provide path and name of a folder when creating a CFolder object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isdir(sFolder) is False: - sResult = f"Nothing to delete. The folder '{sFolder}' does not exist." - if bConfirmDelete is True: - bSuccess = False - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - else: - bSuccess = True - return bSuccess, sResult - # eof if os.path.isdir(sFolder) is False: - - bSuccess = False - sResult = "UNKNOWN" - nCntTries = 1 - nTriesMax = 4 - nDelay = 2 # sec - listResults = [] - while nCntTries <= nTriesMax: - try: - print(f"Trying to delete '{sFolder}'") - print() - shutil.rmtree(sFolder, ignore_errors=False, onerror=rm_dir_readonly) - except Exception as reason: - listResults.append(str(reason)) - if os.path.isdir(sFolder) is True: - sResult = f"({nCntTries}/{nTriesMax}) Problem with deleting the folder '{sFolder}'. Folder still present." - listResults.append(sResult) - time.sleep(nDelay) # delay before next try - else: - bSuccess = True - sResult = f"Folder '{sFolder}' deleted." - break - nCntTries = nCntTries + 1 - # eof while nCntTries <= nTriesMax: - - if bSuccess is False: - sResult = "\n".join(listResults) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def __Delete(self, sFolder=None, bConfirmDelete=True): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Delete(self, bConfirmDelete=True): - """ -Deletes the folder the current class instance contains. - -**Arguments:** - -* ``bConfirmDelete`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - Defines if it will be handled as error if the folder does not exist. - - If ``True``: If the folder does not exist, the method indicates an error (``bSuccess = False``). - - If ``False``: It doesn't matter if the folder exists or not. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFolder.Delete" - bSuccess, sResult = self.__Delete(self.__sFolder, bConfirmDelete) - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - # eof def Delete(self, bConfirmDelete=True): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def Create(self, bOverwrite=False, bRecursive=False): - """ -Creates the current folder ``sFolder``. - -**Arguments:** - -* ``bOverwrite`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - * In case of the folder already exists and ``bOverwrite`` is ``True``, than the folder will be deleted before creation. - * In case of the folder already exists and ``bOverwrite`` is ``False`` (default), than the folder will not be touched. - - In both cases the return value ``bSuccess`` is ``True`` - because the folder exists. - -* ``bRecursive`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - * In case of ``bRecursive`` is ``True``, than the complete destination path will be created (including all intermediate subfolders). - * In case of ``bRecursive`` is ``False``, than it is expected that the parent folder of the new folder already exists. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFolder.Create" - - if self.__sFolder is None: - bSuccess = False - sResult = "self.__sFolder is None; please provide path and name of a folder when creating a CFolder object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - bCreateFolder = False - if os.path.isdir(self.__sFolder) is True: - if bOverwrite is True: - bSuccess, sResult = self.Delete() - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - bCreateFolder = True - else: - bSuccess = True - sResult = f"Folder '{self.__sFolder}' already exists." - return bSuccess, sResult - else: - bCreateFolder = True - - bSuccess = False - sResult = "UNKNOWN" - - if bCreateFolder is True: - nCntTries = 1 - nTriesMax = 3 - nDelay = 2 # sec - listResults = [] - while nCntTries <= nTriesMax: - try: - print(f"Trying to create '{self.__sFolder}'") - print() - if bRecursive is True: - os.makedirs(self.__sFolder) - else: - os.mkdir(self.__sFolder) - except Exception as reason: - listResults.append(str(reason)) - if os.path.isdir(self.__sFolder) is False: - sResult = f"({nCntTries}/{nTriesMax}) Problem with creating the folder '{self.__sFolder}'." - listResults.append(sResult) - time.sleep(nDelay) # delay before next try - else: - bSuccess = True - sResult = f"Folder '{self.__sFolder}' created." - break - nCntTries = nCntTries + 1 - # eof while nCntTries <= nTriesMax: - - if bSuccess is False: - sResult = "\n".join(listResults) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - # eof if bCreateFolder is True: - - return bSuccess, sResult - - # eof def Create(self, bOverwrite=False, bRecursive=False): - - # -------------------------------------------------------------------------------------------------------------- - # TM*** - - def CopyTo(self, sDestination=None, bOverwrite=False): - """ -Copies the current folder to ``sDestination``, that has to be a path to a folder **within** the source folder will be copied to -(with it's original name), - -In case of the destination folder already exists and ``bOverwrite`` is ``True``, than the destination folder will be overwritten. - -In case of the destination folder already exists and ``bOverwrite`` is ``False`` (default), than the destination folder will not be overwritten -and ``CopyTo`` returns ``bSuccess = False``. - -**Arguments:** - -* ``sDestination`` - - / *Condition*: required / *Type*: string / - - The path to destination folder - -* ``bOverwrite`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - * In case of the destination folder already exists and ``bOverwrite`` is ``True``, than the destination folder will be overwritten. - * In case of the destination folder already exists and ``bOverwrite`` is ``False`` (default), than the destination folder will not be overwritten - and ``CopyTo`` returns ``bSuccess = False``. - -**Returns:** - -* ``bSuccess`` - - / *Type*: bool / - - Indicates if the computation of the method was successful or not. - -* ``sResult`` - - / *Type*: str / - - The result of the computation of the method. - """ - sMethod = "CFolder.CopyTo" - - if self.__sFolder is None: - bSuccess = False - sResult = "self.__sFolder is None; please provide path and name of a folder when creating a CFolder object." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isdir(self.__sFolder) is False: - bSuccess = False - sResult = f"The folder '{self.__sFolder}' does not exist, therefore nothing can be copied." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if sDestination is None: - bSuccess = False - sResult = "sDestination is None; please provide a path to a destination folder." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - sDestination = CString.NormalizePath(sDestination) - - if os.path.isdir(sDestination) is False: - # the folder to be copied will be created within the destination folder, therefore we expect that the destination folder already exists - bSuccess = False - sResult = f"The destination folder '{sDestination}' does not exist." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - sSourceFolderName = os.path.basename(self.__sFolder) - sDestFolder = f"{sDestination}/{sSourceFolderName}" - - if sDestFolder == self.__sFolder: - bSuccess = False - sResult = f"Source folder and destination folder are the same: '{self.__sFolder}'. Therefore nothing to do." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if self.__bIsFreeToUse(sDestFolder) is False: - bSuccess = False - sResult = f"The destination folder '{sDestFolder}' is already in use by another CFolder instance." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - - if os.path.isdir(sDestFolder) is True: - # destination folder already exists - if bOverwrite is True: - bSuccess, sResult = self.__Delete(sDestFolder) - if bSuccess is not True: - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - else: - bSuccess = False - sResult = f"Not allowed to overwrite existing destination folder '{sDestFolder}'. Therefore nothing to do." - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - return bSuccess, sResult - # eof if os.path.isdir(sDestFolder) is True: - - # analysis and preconditions done, now the action - - try: - shutil.copytree(self.__sFolder, sDestFolder) - bSuccess = True - sResult = "Folder copied from\n> '" + self.__sFolder + "'\nto\n> '" + sDestFolder + "'" - except Exception as reason: - bSuccess = None - sResult = str(reason) - sResult = CString.FormatResult(sMethod, bSuccess, sResult) - - return bSuccess, sResult - - # eof def CopyTo(self, sDestination=None, bOverwrite=False): - -# -------------------------------------------------------------------------------------------------------------- - - diff --git a/additions/PythonExtensionsCollection/Folder/__init__.py b/additions/PythonExtensionsCollection/Folder/__init__.py deleted file mode 100644 index 958420af..00000000 --- a/additions/PythonExtensionsCollection/Folder/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/additions/PythonExtensionsCollection/String/CString.py b/additions/PythonExtensionsCollection/String/CString.py deleted file mode 100644 index 82aeb204..00000000 --- a/additions/PythonExtensionsCollection/String/CString.py +++ /dev/null @@ -1,1165 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# CString.py -# -# XC-CT/ECA3-Queckenstedt -# -# 02.06.2022 -# -# ************************************************************************************************************** - -# -- import standard Python modules -import os, ntpath, re - -# ************************************************************************************************************** - -class CString(object): - """ -The class ``CString`` contains some string computation methods like e.g. normalizing a path. - """ - - # -------------------------------------------------------------------------------------------------------------- - #TM*** - - def NormalizePath(sPath=None, bWin=False, sReferencePathAbs=None, bConsiderBlanks=False, bExpandEnvVars=True, bMask=True): - """ -Normalizes local paths, paths to local network resources and internet addresses - -**Arguments:** - -* ``sPath`` - - / *Condition*: required / *Type*: str / - - The path to be normalized - -* ``bWin`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True`` then returned path contains masked backslashes as separator, otherwise slashes - -* ``sReferencePathAbs`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - In case of ``sPath`` is relative and ``sReferencePathAbs`` (expected to be absolute) is given, then - the returned absolute path is a join of both input paths - -* ``bConsiderBlanks`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True`` then the returned path is encapsulated in quotes - in case of the path contains blanks - -* ``bExpandEnvVars`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - If ``True`` then in the returned path environment variables are resolved, otherwise not. - -* ``bMask`` - - / *Condition*: optional / *Type*: bool / *Default*: True (requires ``bWin=True``)/ - - * If ``bWin`` is ``True`` and ``bMask`` is ``True`` then the returned path contains masked backslashes as separator. - * If ``bWin`` is ``True`` and ``bMask`` is ``False`` then the returned path contains single backslashes only - this might be - required for applications, that are not able to handle masked backslashes. - * In case of ``bWin`` is ``False`` ``bMask`` has no effect. - -**Returns:** - -* ``sPath`` - - / *Type*: str / - - The normalized path (is ``None`` in case of ``sPath`` is ``None``) - """ - - if sPath is not None: - - # -- expand Windows environment variables - if bExpandEnvVars is True: - sPath = os.path.expandvars(sPath) - - # - remove leading and trailing horizontal space - sPath = sPath.strip(" \t\r\n") - - # - remove leading and trailing quotes - sPath = sPath.strip("\"'") - - # - remove once more leading and trailing horizontal space - # (after the removal of leading and trailing quotes further horizontal space might be there, that has to be removed; - # but further levels of nesting are not considered) - sPath = sPath.strip(" \t") - - if sPath == "": - return sPath - - # - remove trailing slash or backslash (maybe at end of path to folder) - sPath = sPath.rstrip("/\\") - - # -------------------------------------------------------------------------------------------------------------- - # consider internet addresses and local network resources - # -------------------------------------------------------------------------------------------------------------- - # -- local network resource / file server - # (prepare for Windows explorer) - # either (default) - # //server.com/abc/xyz - # or (with bWin=True); bMask must be False because \\server.com\\abc\\xyz is not allowed - # \\server.com\abc\xyz - # (=> user is allowed to select bWin but not bMask) - # - # -- local network resource / file server - # (prepare for web browser) - # after 'file://///' only single slashes allowed; bWin and bMask must be False - # file://///server.com/abc/xyz - # (=> user is NOT allowed to select bWin and bMask) - # - # -- internet address - # after server name only single slashes allowed; bWin and bMask must be False - # http://server.com/abc/xyz - # https://server.com/abc/xyz - # (=> user is NOT allowed to select bWin and bMask) - # - # - not allowed (=> this method must not return this format): - # http:\\server.com - # https:\\server.com - # -------------------------------------------------------------------------------------------------------------- - - sPathPrefix = None - - # In case there is any prefix, we remove this prefix, we compute the remaining part of the path separately, - # we also modify this prefix manually, and at the end we put the new prefix back to the path. - - if ( (sPath[:2] == "\\\\") or (sPath[:2] == "//") ): - sPath = sPath[2:] - if bWin is True: - sPathPrefix = "\\\\" - else: - sPathPrefix = "//" - bMask = False # !!! this overrules the input parameter value, because masked backslashes are not allowed in remaining path !!! - elif sPath[:10] == "file://///": # exactly this must be given; all other combinations of slashes and backslashes are not handled - sPath = sPath[10:] - sPathPrefix = "file://///" - bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - elif ( (sPath[:7] == "http://") or (sPath[:7] == "http:\\\\") ): - sPath = sPath[7:] - sPathPrefix = "http://" - bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - elif ( (sPath[:8] == "https://") or (sPath[:8] == "https:\\\\") ): - sPath = sPath[8:] - sPathPrefix = "https://" - bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! - else: - # Internet addresses and local network resources handled, now checking for relative paths: - # In case of sPath is a relative path AND an absolute reference path is provided - # merge them to an absolute path; without reference path use standard function to - # convert relative path to absolute path - if ( (sPath[0] != "%") and (sPath[0] != "$") ): - # If sPath starts with '%' or with '$' it is assumed that the path starts with an environment variable (Windows or Linux). - # But in this case 'os.path.isabs(sPath)' will not detect this to be an absolute path and will call - # 'sPath = os.path.abspath(sPath)' (depending on sReferencePathAbs). This will accidently merge - # the root path together with the path starting with the environment variable and cause invalid results. - if os.path.isabs(sPath) is False: - if sReferencePathAbs is not None: - sPath = os.path.join(sReferencePathAbs, sPath) - else: - sPath = os.path.abspath(sPath) - - # eof computation of sPathPrefix - - # - normalize the path (collapse redundant separators and up-level references) - # on Windows this converts slashes to backward slashes - # sPath = os.path.normpath(sPath) # under Linux this unfortunately keeps redundant separators (in opposite to Windows) - # -- alternative - sPath = ntpath.normpath(sPath) - - # - exchange single backslashes by single slashes (= partly we have to repair the outcome of normpath) - if bWin is False: - sPath = sPath.replace("\\", "/") - else: - if bMask is True: - sPath = sPath.replace("\\", "\\\\") - - # - restore the path prefix - if sPathPrefix is not None: - sPath = f"{sPathPrefix}{sPath}" - - # - consider blanks (prepare path for usage in Windows command line) - if bConsiderBlanks is True: - if sPath.find(" ") >= 0: - sPath = f"\"{sPath}\"" - - # eof if sPath is not None: - - return sPath - - # eof NormalizePath(sPath=None, bWin=False, sReferencePathAbs=None, bConsiderBlanks=False, bExpandEnvVars=True, bMask=True) - - # -------------------------------------------------------------------------------------------------------------- - #TM*** - - def DetectParentPath(sStartPath=None, sFolderName=None, sFileName=None): - """ -Computes the path to any parent folder inside a given path. Optionally DetectParentPath is able -to search for files inside the parent folder. - -**Arguments:** - -* ``sStartPath`` - - / *Condition*: required / *Type*: str / - - The path in which to search for a parent folder - -* ``sFolderName`` - - / *Condition*: required / *Type*: str / - - The name of the folder to search for within ``sStartPath``. It is possible to provide more than one folder name separated by semicolon - -* ``sFileName`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - The name of a file to search within the detected parent folder - -**Returns:** - -* ``sDestPath`` - - / *Type*: str / - - Path and name of parent folder found inside ``sStartPath``, ``None`` in case of ``sFolderName`` is not found inside ``sStartPath``. - In case of more than one parent folder is found ``sDestPath`` contains the first result and ``listDestPaths`` contains all results. - -* ``listDestPaths`` - - / *Type*: list / - - If ``sFolderName`` contains a single folder name this list contains only one element that is ``sDestPath``. - In case of ``FolderName`` contains a semicolon separated list of several folder names this list contains all found paths of the given folder names. - ``listDestPaths`` is ``None`` (and not an empty list!) in case of ``sFolderName`` is not found inside ``sStartPath``. - -* ``sDestFile`` - - / *Type*: str / - - Path and name of ``sFileName``, in case of ``sFileName`` is given and found inside ``listDestPaths``. - In case of more than one file is found ``sDestFile`` contains the first result and ``listDestFiles`` contains all results. - ``sDestFile`` is ``None`` in case of ``sFileName`` is ``None`` and also in case of ``sFileName`` is not found inside ``listDestPaths`` - (and therefore also in case of ``sFolderName`` is not found inside ``sStartPath``). - -* ``listDestFiles`` - - / *Type*: list / - - Contains all positions of ``sFileName`` found inside ``listDestPaths``. - - ``listDestFiles`` is ``None`` (and not an empty list!) in case of ``sFileName`` is ``None`` and also in case of ``sFileName`` - is not found inside ``listDestPaths`` (and therefore also in case of ``sFolderName`` is not found inside ``sStartPath``). - -* ``sDestPathParent`` - - / *Type*: str / - - The parent folder of ``sDestPath``, ``None`` in case of ``sFolderName`` is not found inside ``sStartPath`` (``sDestPath`` is ``None``). - """ - - sDestPath = None - listDestPaths = None - sDestFile = None - listDestFiles = None - sDestPathParent = None - - if sStartPath is None: - return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent - - if sFolderName is None: - return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent - - sStartPath = sStartPath.strip() - if sStartPath == "": - return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent - - sFolderName = sFolderName.strip() - if sFolderName == "": - return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent - - listSplit = sFolderName.split(';') - - listTopLevelFolders = [] - for sFolder in listSplit: - # removing duplicates - sFolder = sFolder.strip() - if sFolder != "": - if sFolder not in listTopLevelFolders: - listTopLevelFolders.append(sFolder) - # eof for sFolder in listSplit: - - nNrOfFolders = len(listTopLevelFolders) - sStartPath = CString.NormalizePath(sStartPath) - listLevels = sStartPath.split("/") - - listDestPaths = [] - - while len(listLevels) > 0: - # -- merging paths with folder names and search for existing combinations - sPathParent = "/".join(listLevels) - for sTLFolder in listTopLevelFolders: - sSubPath = sPathParent + "/" + sTLFolder - if os.path.isdir(sSubPath) is True: - listDestPaths.append(sSubPath) - if len(listTopLevelFolders) == len(listDestPaths): - # all folders found - break - else: - listLevels.pop() - # eof while len(listLevels) > 0: - - sDestPath = None - sDestPathParent = None - if len(listDestPaths) > 0: - # -- returning sDestPath and sDestPathParent related to first entry in list; just to return anything else than None - sDestPath = listDestPaths[0] - sDestPathParent = CString.NormalizePath(os.path.dirname(sDestPath)) - - # -- optionally searching also for a single file - # Input: file name - # Output: full path of file and list of full paths of files (!!! limited to 'listDestPaths' !!!) - - listDestFiles = [] - - if ( (sFileName is not None) and (len(listDestPaths) > 0) ): - for sDestPathToWalk in listDestPaths: - for sLocalRootPath, listFolderNames, listFileNames in os.walk(sDestPathToWalk): - for sFileNameTmp in listFileNames: - if sFileNameTmp == sFileName: - sFile = CString.NormalizePath(os.path.join(sLocalRootPath, sFileName)) - listDestFiles.append(sFile) - # eof for sLocalRootPath, listFolderNames, listFileNames in os.walk(sDestPathToWalk): - # eof for sDestPathToWalk in listDestPaths: - # eof if ( (sFileName is not None) and (len(listDestPaths) > 0) ): - - if len(listDestFiles) > 0: - listDestFiles.sort() - sDestFile = listDestFiles[0] # just to return anything else than None - - # -- preparing output (setting empty lists to None, to have unique criteria for results not available) - if listDestPaths is not None: - if len(listDestPaths) == 0: - listDestPaths = None - if listDestFiles is not None: - if len(listDestFiles) == 0: - listDestFiles = None - - return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent - - # eof def DetectParentPath(sStartPath=None, sFolderName=None, sFileName=None): - - # -------------------------------------------------------------------------------------------------------------- - #TM*** - - def StringFilter(sString = None, - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None, - bDebug = False): - """ -During the computation of strings there might occur the need to get to know if this string fulfils certain criteria or not. -Such a criterion can e.g. be that the string contains a certain substring. Also an inverse logic might be required: -In this case the criterion is that the string does **not** contain this substring. - -It might also be required to combine several criteria to a final conclusion if in total the criterion for a string is fulfilled or not. -For example: The string must start with the string *prefix* and must also contain either the string *substring1* or the string *substring2* -but must also **not** end with the string *suffix*. - -This method provides a bunch of predefined filters that can be used singly or combined to come to a final conclusion if the string fulfils all criteria or not. - -The filters are divided into three different types: - -1. Filters that are interpreted as raw strings (called 'standard filters'; no wild cards supported) -2. Filters that are interpreted as regular expressions (called 'regular expression based filters'; the syntax of regular expressions has to be considered) -3. Boolean switches (e.g. indicating if also an empty string is accepted or not) - -The input string might contain leading and trailing blanks and tabs. This kind of horizontal space is removed from the input string -before the standard filters start their work (except the regular expression based filters). - -The regular expression based filters consider the original input string (including the leading and trailing space). - -The outcome is that in case of the leading and trailing space shall be part of the criterion, the regular expression based filters can be used only. - -It is possible to decide if the standard filters shall work case sensitive or not. This decision has no effect on the regular expression based filters. - -The regular expression based filters always work with the original input string that is not modified in any way. - -Except the regular expression based filters it is possible to provide more than one string for every standard filter (must be a semikolon separated list in this case). -A semicolon that shall be part of the search string, has to be masked in this way: ``\;``. - -This method returns a boolean value that is ``True`` in case of all criteria are fulfilled, and ``False`` in case of some or all of them are not fulfilled. - -The default value for all filters is ``None`` (except ``bSkipBlankStrings``). In case of a filter value is ``None`` this filter has no influence on the result. - -In case of all filters are ``None`` (default) the return value is ``True`` (except the string itself is ``None`` -or the string is empty and ``bSkipBlankStrings`` is ``True``). - -In case of the string is ``None``, the return value is ``False``, because nothing concrete can be done with ``None`` strings. - -Internally every filter has his own individual acknowledge that indicates if the criterion of this filter is fulfilled or not. - -The meaning of *criterion fulfilled* of a filter is that the filter supports the final return value ``bAck`` of this method with ``True``. - -The final return value ``bAck`` of this method is a logical join (``AND``) of all individual acknowledges (except ``bSkipBlankStrings`` and ``sComment``; -in case of their criteria are **not** fulfilled, immediately ``False`` is returned). - -Summarized: - -* Filters are used to define *criteria* -* The return value of this method provides the *conclusion* - indicating if all criteria are fulfilled or not - -The following filters are available: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**bSkipBlankStrings** - - * Like already mentioned above leading and trailing spaces are removed from the input string at the beginning - * In case of the result is an empty string and ``bSkipBlankStrings`` is ``True``, the method immediately returns ``False`` - and all other filters are ignored - -**sComment** - - * In case of the input string starts with the string ``sComment``, the method immediately returns ``False`` - and all other filters are ignored - * Leading blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * The idea behind this decision is: Ignore a string that is commented out - -**sStartsWith** - - * The criterion of this filter is fulfilled in case of the input string starts with the string ``sStartsWith`` - * Leading blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -**sEndsWith** - - * The criterion of this filter is fulfilled in case of the input string ends with the string ``sEndsWith`` - * Trailing blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -**sStartsNotWith** - - * The criterion of this filter is fulfilled in case of the input string does **not** start with the string ``sStartsNotWith`` - * Leading blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -**sEndsNotWith** - - * The criterion of this filter is fulfilled in case of the input string does **not** end with the string ``sEndsNotWith`` - * Trailing blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -**sContains** - - * The criterion of this filter is fulfilled in case of the input string contains the string ``sContains`` at any position - * Leading and trailing blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``OR``) - -**sContainsNot** - - * The criterion of this filter is fulfilled in case of the input string does **not** contain the string ``sContainsNot`` at any position - * Leading and trailing blanks within the input string have no effect - * The decision also depends on ``bCaseSensitive`` - * More than one string can be provided (semicolon separated; logical join: ``AND``) - -**sInclRegEx** - - * *Include* filter based on regular expressions (consider the syntax of regular expressions!) - * The criterion of this filter is fulfilled in case of the regular expression ``sInclRegEx`` matches the input string - * Leading and trailing blanks within the input string are considered - * ``bCaseSensitive`` has no effect - * A semicolon separated list of several regular expressions is **not** supported - -**sExclRegEx** - - * *Exclude* filter based on regular expressions (consider the syntax of regular expressions!) - * The criterion of this filter is fulfilled in case of the regular expression ``sExclRegEx`` does **not** match the input string - * Leading and trailing blanks within the input string are considered - * ``bCaseSensitive`` has no effect - * A semicolon separated list of several regular expressions is **not** supported - -**Further arguments:** - -* ``sString`` - - / *Condition*: required / *Type*: str / - - The input string that has to be investigated. - -* ``bCaseSensitive`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - If ``True``, the standard filters work case sensitive, otherwise not. - -* ``bDebug`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True``, additional output is printed to console (e.g. the decision of every single filter), otherwise not. - -**Returns:** - -* ``bAck`` - - / *Type*: bool / - - Final statement about the input string ``sString`` after filter computation - -Examples: -~~~~~~~~~ - -1. Returns ``True``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = "Sp", - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = "beats", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - -2. Returns ``False``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = "Sp", - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = "minute", - sContains = "beats", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - -3. Returns ``True``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = "Beats", - sInclRegEx = None, - sExclRegEx = None) - -4. Returns ``True``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = r"\d{2}", - sExclRegEx = None) - -5. Returns ``False``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = "Speed", - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = r"\d{3}", - sExclRegEx = None) - -6. Returns ``False``: - -.. code:: python - - StringFilter(sString = "Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = "Speed", - sEndsWith = "minute", - sStartsNotWith = "speed", - sEndsNotWith = None, - sContains = "beats", - sContainsNot = None, - sInclRegEx = r"\d{2}", - sExclRegEx = r"\d{2}") - -7. Returns ``False``: - -.. code:: python - - StringFilter(sString = " ", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - -8. Returns ``False``: - -.. code:: python - - StringFilter(sString = "# Speed is 25 beats per minute", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = "#", - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = "beats", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - - -9. Returns ``False``: - -.. code:: python - - StringFilter(sString = " Alpha is not beta; and beta is not gamma ", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = " Alpha ", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - -Because blanks around search strings (here ``" Alpha "``) are considered, whereas the blanks around the input string are removed before computation. -Therefore ``" Alpha "`` cannot be found within the (shortened) input string. - - -10. This alternative solution returns ``True``: - -.. code:: python - - StringFilter(sString = " Alpha is not beta; and beta is not gamma ", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = None, - sContainsNot = None, - sInclRegEx = r"\s{3}Alpha", - sExclRegEx = None) - - -11. Returns ``True``: - -.. code:: python - - StringFilter(sString = "Alpha is not beta; and beta is not gamma", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = "beta; and", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - -The meaning of ``"beta; and"`` is: The criterion is fulfilled in case of either ``"beta"`` or ``" and"`` can be found. That's ``True`` in this example - but this -has nothing to do with the fact, that also this string ``"beta; and"`` can be found. Here the semikolon is a separator character and therefore part of the syntax. - -A semicolon that shall be part of the search string, has to be masked with '``\;``'! - -The meaning of ``"beta\; not"`` in the following example is: The criterion is fulfilled in case of ``"beta; not"`` can be found. - -That's **not** ``True``. Therefore the method returns ``False``: - -.. code:: python - - StringFilter(sString = "Alpha is not beta; and beta is not gamma", - bCaseSensitive = True, - bSkipBlankStrings = True, - sComment = None, - sStartsWith = None, - sEndsWith = None, - sStartsNotWith = None, - sEndsNotWith = None, - sContains = r"beta\; not", - sContainsNot = None, - sInclRegEx = None, - sExclRegEx = None) - """ - - if sString is None: - return False # hard coded here; no separate filter for that decision - - # The original string 'sString' is used by regular expression filters sInclRegEx and sExclRegEx. - # The stripped string 'sStringStripped' is used by all other filters. - sStringStripped = sString.strip(" \t\r\n") - - # -- skipping blank strings or strings commented out; other filters will not be considered any more in this case - - if bSkipBlankStrings is True: - if sStringStripped == "": - return False - - if sComment is not None: - if sComment != "": - if bCaseSensitive is True: - if sStringStripped.startswith(sComment) is True: - return False - else: - if sStringStripped.upper().startswith(sComment.upper()) is True: - return False - - # -- consider further filters - # - # No filter set (= no criteria defined) => use this string (bAck is True). - # - # At least one filter set (except sExclRegEx), at least one set filter fits (except sExclRegEx) => use this string. - # Filter sExclRegEx is set and fits => skip this string (final veto). - # At least one filter does not fit (except sExclRegEx) => skip this string. - # - # All filters (except sExclRegEx) are include filter (bAck is True in case of all set filters fit, also the 'not' filters) - # The filter sExclRegEx is an exclude filter and has final veto right (can revoke the True from other filters). - # - # All filters (except sInclRegEx and sExclRegEx) are handled as 'raw strings': no wild cards, just strings, considering bCaseSensitive. - # The filters sInclRegEx and sExclRegEx are handled as regular expressions; bCaseSensitive is not considered here. - - # -- filter specific flags (containing the names of the criteria within their names) - bStartsWith = None - bEndsWith = None - bStartsNotWith = None - bEndsNotWith = None - bContains = None - bContainsNot = None - bInclRegEx = None - bExclRegEx = None - - # Meaning: - # - Flag is None : filter not set => filter has no effect - # - Flag is True : filter set => result: use the input string (from this single filter flag point of view) - # - Flag is False: filter set => result: do not use the input string (from this single filter flag point of view) - # The results of all flags will be merged at the end of this function to one final conclusion to use the input string - # (bAck is True) or not (bAck is False). - # Logical join between all set filters: AND - - # substitute for the masked filter separator '\n' (hopefully the input string does not contain this substitute) - sSeparatorSubstitute = "#|S#|E#|P#|A#|R#|A#|T#|O#|R#" - - # -- filter: starts with - # > several filter strings possible (separated by semicolon; logical join: OR) - if sStartsWith is not None: - if sStartsWith != "": - sStartsWithModified = sStartsWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listStartsWith = [] - if sStartsWith.find(";") >= 0: - listParts = sStartsWithModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - listStartsWith.append(sPart) - else: - sStartsWithModified = sStartsWith.replace(r"\;", ";") # convert to unmasked version - listStartsWith.append(sStartsWithModified) - - bStartsWith = False - for sStartsWith in listStartsWith: - if bCaseSensitive is True: - if sStringStripped.startswith(sStartsWith) is True: - bStartsWith = True - break - else: - if sStringStripped.upper().startswith(sStartsWith.upper()) is True: - bStartsWith = True - break - - # -- filter: ends with - # > several filter strings possible (separated by semicolon; logical join: OR) - if sEndsWith is not None: - if sEndsWith != "": - sEndsWithModified = sEndsWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listEndsWith = [] - if sEndsWith.find(";") >= 0: - listParts = sEndsWithModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - listEndsWith.append(sPart) - else: - sEndsWithModified = sEndsWith.replace(r"\;", ";") # convert to unmasked version - listEndsWith.append(sEndsWithModified) - - bEndsWith = False - for sEndsWith in listEndsWith: - if bCaseSensitive is True: - if sStringStripped.endswith(sEndsWith) is True: - bEndsWith = True - break - else: - if sStringStripped.upper().endswith(sEndsWith.upper()) is True: - bEndsWith = True - break - - # -- filter: starts not with - # > several filter strings possible (separated by semicolon; logical join: AND) - if sStartsNotWith is not None: - if sStartsNotWith != "": - sStartsNotWithModified = sStartsNotWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listStartsNotWith = [] - if sStartsNotWith.find(";") >= 0: - listParts = sStartsNotWithModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - listStartsNotWith.append(sPart) - else: - sStartsNotWithModified = sStartsNotWith.replace(r"\;", ";") # convert to unmasked version - listStartsNotWith.append(sStartsNotWithModified) - - bStartsNotWith = True - for sStartsNotWith in listStartsNotWith: - if bCaseSensitive is True: - if sStringStripped.startswith(sStartsNotWith) is True: - bStartsNotWith = False - break - else: - if sStringStripped.upper().startswith(sStartsNotWith.upper()) is True: - bStartsNotWith = False - break - - # -- filter: ends not with - # > several filter strings possible (separated by semicolon; logical join: AND) - if sEndsNotWith is not None: - if sEndsNotWith != "": - sEndsNotWithModified = sEndsNotWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listEndsNotWith = [] - if sEndsNotWith.find(";") >= 0: - listParts = sEndsNotWithModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - listEndsNotWith.append(sPart) - else: - sEndsNotWithModified = sEndsNotWith.replace(r"\;", ";") # convert to unmasked version - listEndsNotWith.append(sEndsNotWithModified) - - bEndsNotWith = True - for sEndsNotWith in listEndsNotWith: - if bCaseSensitive is True: - if sStringStripped.endswith(sEndsNotWith) is True: - bEndsNotWith = False - break - else: - if sStringStripped.upper().endswith(sEndsNotWith.upper()) is True: - bEndsNotWith = False - break - - # -- filter: contains - # > several filter strings possible (separated by semicolon; logical join: OR) - if sContains is not None: - if sContains != "": - sContainsModified = sContains.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listContains = [] - if sContainsModified.find(";") >= 0: - listParts = sContainsModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - print(f"- Part: '{sPart}'") - listContains.append(sPart) - else: - sContainsModified = sContains.replace(r"\;", ";") # convert to unmasked version - listContains.append(sContainsModified) - - bContains = False - for sContains in listContains: - if bCaseSensitive is True: - if sStringStripped.find(sContains) >= 0: - bContains = True - break - else: - if sStringStripped.upper().find(sContains.upper()) >= 0: - bContains = True - break - - # -- filter: contains not - # > several filter strings possible (separated by semicolon; logical join: AND) - if sContainsNot is not None: - if sContainsNot != "": - sContainsNotModified = sContainsNot.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator - listContainsNot = [] - if sContainsNot.find(";") >= 0: - listParts = sContainsNotModified.split(";") - for sPart in listParts: - sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version - listContainsNot.append(sPart) - else: - sContainsNotModified = sContainsNot.replace(r"\;", ";") # convert to unmasked version - listContainsNot.append(sContainsNotModified) - - bContainsNot = True - for sContainsNot in listContainsNot: - if bCaseSensitive is True: - if sStringStripped.find(sContainsNot) >= 0: - bContainsNot = False - break - else: - if sStringStripped.upper().find(sContainsNot.upper()) >= 0: - bContainsNot = False - break - - # -- filter: sInclRegEx - # > (take care to mask special characters that are part of the syntax of regular expressions!) - # > bCaseSensitive not considered here - if sInclRegEx is not None: - if sInclRegEx != "": - bInclRegEx = False - if re.search(sInclRegEx, sString) is not None: - bInclRegEx = True - - # -- last filter: sExclRegEx (final veto right) - # > (take care to mask special characters that are part of the syntax of regular expressions!) - # > bCaseSensitive not considered here - if sExclRegEx is not None: - if sExclRegEx != "": - bExclRegEx = True - if re.search(sExclRegEx, sString) is not None: - bExclRegEx = False - - # -- debug info - if bDebug is True: - print("\n* [sString] : '" + str(sString) + "'\n") - print(" -> [bStartsWith] : '" + str(bStartsWith) + "'") - print(" -> [bEndsWith] : '" + str(bEndsWith) + "'") - print(" -> [bStartsNotWith] : '" + str(bStartsNotWith) + "'") - print(" -> [bEndsNotWith] : '" + str(bEndsNotWith) + "'") - print(" -> [bContains] : '" + str(bContains) + "'") - print(" -> [bContainsNot] : '" + str(bContainsNot) + "'") - print(" -> [bInclRegEx] : '" + str(bInclRegEx) + "'") - print(" -> [bExclRegEx] : '" + str(bExclRegEx) + "'\n") - - # -- final conclusion (AND condition between filters) - - listDecisions = [] - listDecisions.append(bStartsWith) - listDecisions.append(bEndsWith) - listDecisions.append(bStartsNotWith) - listDecisions.append(bEndsNotWith) - listDecisions.append(bContains) - listDecisions.append(bContainsNot) - listDecisions.append(bInclRegEx) - listDecisions.append(bExclRegEx) - - bAck = False # initial - - # -- 1.) no filter set (all None) - nCntDecisions = 0 - for bDecision in listDecisions: - if bDecision is None: - nCntDecisions = nCntDecisions + 1 - if nCntDecisions == len(listDecisions): - bAck = True - if bDebug is True: - print(" > case [1] - bAck: " + str(bAck)) - - # -- 2.) final veto from exclude filter - if bExclRegEx is False: - bAck = False - if bDebug is True: - print(" > case [2] - bAck: " + str(bAck)) - - # -- 3.) exclude filter not set; decision only made by other filters (include) - if bExclRegEx is None: - bAck = True - for bDecision in listDecisions: - if bDecision is False: - bAck = False - break - if bDebug is True: - print(" > case [3] - bAck: " + str(bAck)) - - # -- 4.) exclude filter is True (only relevant in case of all other filters are not set; otherwise decision only made by other filters (include)) - if bExclRegEx is True: - if ( (bStartsWith is None) and - (bEndsWith is None) and - (bStartsNotWith is None) and - (bEndsNotWith is None) and - (bContains is None) and - (bContainsNot is None) and - (bInclRegEx is None) ): - bAck = True - if bDebug is True: - print(" > case [4.1] - bAck: " + str(bAck)) - else: - bAck = True - for bDecision in listDecisions: - if bDecision is False: - bAck = False - break - if bDebug is True: - print(" > case [4.2] - bAck: " + str(bAck)) - - if bDebug is True: - print() - - return bAck - - # eof def StringFilter(...) - - # -------------------------------------------------------------------------------------------------------------- - #TM*** - - def FormatResult(sMethod="", bSuccess=True, sResult=""): - """ -Formats the result string ``sResult`` depending on ``bSuccess``: - -* ``bSuccess`` is ``True`` indicates *success* -* ``bSuccess`` is ``False`` indicates an *error* -* ``bSuccess`` is ``None`` indicates an *exception* - -Additionally the name of the method that causes the result, can be provided (*optional*). -This is useful for debugging. - -**Arguments:** - -* ``sMethod`` - - / *Condition*: optional / *Type*: str / *Default*: (empty string) / - - Name of the method that causes the result. - -* ``bSuccess`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - Indicates if the computation of the method ``sMethod`` was successful or not. - -* ``sResult`` - - / *Condition*: optional / *Type*: str / *Default*: (empty string) / - - The result of the computation of the method ``sMethod``. - -**Returns:** - -* ``sResult`` - - / *Type*: str / - - The formatted result string. - """ - - if sMethod is None: - sMethod = str(sMethod) - if sResult is None: - sResult = str(sResult) - if bSuccess is True: - if sMethod != "": - sResult = f"[{sMethod}] : {sResult}" - elif bSuccess is False: - sError = "!!! ERROR !!!" - if sMethod != "": - sResult = f"{sError}\n[{sMethod}] : {sResult}" - else: - sResult = f"{sError}\n{sResult}" - else: - sException = "!!! EXCEPTION !!!" - if sMethod != "": - sResult = f"{sException}\n[{sMethod}] : {sResult}" - else: - sResult = f"{sException}\n{sResult}" - return sResult - - # eof def FormatResult(sMethod="", bSuccess=True, sResult=""): - - # -------------------------------------------------------------------------------------------------------------- - #TM*** - - # - make the methods static - - NormalizePath = staticmethod(NormalizePath) - DetectParentPath = staticmethod(DetectParentPath) - StringFilter = staticmethod(StringFilter) - FormatResult = staticmethod(FormatResult) - -# eof class CString(object): - -# ************************************************************************************************************** - - - diff --git a/additions/PythonExtensionsCollection/String/__init__.py b/additions/PythonExtensionsCollection/String/__init__.py deleted file mode 100644 index 958420af..00000000 --- a/additions/PythonExtensionsCollection/String/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/additions/PythonExtensionsCollection/Utils/CUtils.py b/additions/PythonExtensionsCollection/Utils/CUtils.py deleted file mode 100644 index 20447501..00000000 --- a/additions/PythonExtensionsCollection/Utils/CUtils.py +++ /dev/null @@ -1,374 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# CUtils.py -# -# XC-CT/ECA3-Queckenstedt -# -# 20.05.2022 -# -# ************************************************************************************************************** - -# -- import standard Python modules -from dotdict import dotdict - -# ************************************************************************************************************** -# wrapper -# ************************************************************************************************************** - -def PrettyPrint(oData=None, hOutputFile=None, bToConsole=True, nIndent=0, sPrefix=None, bHexFormat=False): - """ -Wrapper function to create and use a ``CTypePrint`` object. This wrapper function is responsible for -printing out the content to console and to a file (depending on input parameter). - -The content itself is prepared by the method ``TypePrint`` of class ``CTypePrint``. This happens ``PrettyPrint`` internally. - -The idea behind the ``PrettyPrint`` function is to resolve also the content of composite data types and provide for every parameter inside: - -* the type -* the total number of elements inside (e.g. the number of keys inside a dictionary) -* the counter number of the current element -* the value - -Example call: - -.. code:: python - - PrettyPrint(oData) - -(*with oData is a Python variable of any type*) - -The output can e.g. look like this: - -.. code:: python - - [DICT] (3/1) > {K1} [STR] : 'Val1' - [DICT] (3/2) > {K2} [LIST] (4/1) > [INT] : 1 - [DICT] (3/2) > {K2} [LIST] (4/2) > [STR] : 'A' - [DICT] (3/2) > {K2} [LIST] (4/3) > [INT] : 2 - [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/1) > [INT] : 9 - [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/2) > [STR] : 'Z' - [DICT] (3/3) > {K3} [INT] : 5 - -Every line of output has to be interpreted strictly from left to right. - -For example the meaning of the fifth line of output - -.. code:: python - - [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/1) > [INT] : 9 - -is: - -* The type of input parameter (``oData``) is ``dict`` -* The dictionary contains 3 keys -* The current line gives information about the second key of the dictionary -* The name of the second key is 'K2' -* The value of the second key is of type ``list`` -* The list contains 4 elements -* The current line gives information about the fourth element of the list -* The fourth element of the list is of type ``tuple`` -* The tuple contains 2 elements -* The current line gives information about the first element of the tuple -* The first element of the tuple is of type ``int`` and has the value 9 - -Types are encapsulated in square brackets, counter in round brackets and key names are encapsulated in curly brackets. - -**Arguments:** - -* ``oData`` - - / *Condition*: required / *Type*: (*any Python data type*) / - - A variable of any Python data type. - -* ``hOutputFile`` - - / *Condition*: optional / *Type*: file handle / *Default*: None / - - If handle is not ``None`` the content is written to this file, otherwise not. - -* ``bToConsole`` - - / *Condition*: optional / *Type*: bool / *Default*: True / - - If ``True`` the content is written to console, otherwise not. - -* ``nIndent`` - - / *Condition*: optional / *Type*: int / *Default*: 0 / - - Sets the number of additional blanks at the beginning of every line of output (indentation). - -* ``sPrefix`` - - / *Condition*: optional / *Type*: str / *Default*: None / - - Sets a prefix string that is added at the beginning of every line of output. - -* ``bHexFormat`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True`` the output is printed in hexadecimal format (but valid for strings only). - -**Returns:** - -* ``listOutLines`` (*list*) - - / *Type*: list / - - List of lines containing the prepared output - """ - - oTypePrint = CTypePrint() - listOutLines = oTypePrint.TypePrint(oData, bHexFormat) - - listReturned = [] - for sLine in listOutLines: - # if requested add indentation and prefix - sLineOut = "" - if sPrefix is not None: - sLineOut = nIndent*" " + sPrefix + " " + sLine - else: - sLineOut = nIndent*" " + sLine - listReturned.append(sLineOut) - - if hOutputFile is not None: - hOutputFile.write(sLineOut + "\n") - if bToConsole is True: - print(sLineOut) - - return listReturned - -# eof def PrettyPrint(oData=None, hOutputFile=None, bToConsole=True, nIndent=0, sPrefix=None, bHexFormat=False): - -# -------------------------------------------------------------------------------------------------------------- -# TM*** - -class CTypePrint(object): - """ -The class ``CTypePrint`` provides a method (``TypePrint``) to compute the following data: - -* the type -* the total number of elements inside (e.g. the number of keys inside a dictionary) -* the counter number of the current element -* the value - -of simple and composite data types. - -The call of this method is encapsulated within the function ``PrettyPrint`` inside this module. - """ - def __init__(self): - self.listGlobalPrefixes = [] - self.listOutLines = [] - - def __del__(self): - pass - - def _ToHex(self, sString=None): - if ( (sString is None) or (sString == "") ): - return sString - listHex = [] - for sChar in sString: - listHex.append(hex(ord(sChar))) - sStringHex = " ".join(listHex) - return sStringHex - - def TypePrint(self, oData=None, bHexFormat=False): - """ -The method ``TypePrint`` computes details about the input variable ``oData``. - -**Arguments:** - -* ``oData`` - - / *Condition*: required / *Type*: any Python data type / - - Python variable of any data type. - -* ``bHexFormat`` - - / *Condition*: optional / *Type*: bool / *Default*: False / - - If ``True`` the output is provide in hexadecimal format. - -**Returns:** - -* ``listOutLines`` - - / *Type*: list / - - List of lines containing the resolved content of ``oData``. - """ - - if oData is None: - sLocalPrefix = "[NONE]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) - self.listOutLines.append(sOut.strip()) - - elif type(oData) == int: - sLocalPrefix = "[INT]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) - self.listOutLines.append(sOut.strip()) - - elif type(oData) == float: - sLocalPrefix = "[FLOAT]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) - self.listOutLines.append(sOut.strip()) - - elif type(oData) == bool: - sLocalPrefix = "[BOOL]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) - self.listOutLines.append(sOut.strip()) - - elif type(oData) == str: - sLocalPrefix = "[STR]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sData = str(oData) - if bHexFormat is True: - sData = self._ToHex(sData) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : '" + sData + "'" - self.listOutLines.append(sOut.strip()) - - elif type(oData) == list: - nNrOfElements = len(oData) - if nNrOfElements == 0: - # -- indicate empty list - sLocalPrefix = "[LIST]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : []" - self.listOutLines.append(sOut.strip()) - else: - # -- list elements of list - self.listGlobalPrefixes.append("[LIST]") - nCnt = 0 - for oElement in oData: - nCnt = nCnt + 1 - sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" - self.listGlobalPrefixes.append(sCnt) - self.TypePrint(oElement, bHexFormat) # >>>> recursion - del self.listGlobalPrefixes[-1] # remove prefix count - del self.listGlobalPrefixes[-1] # remove prefix name - - elif type(oData) == tuple: - nNrOfElements = len(oData) - if nNrOfElements == 0: - # -- indicate empty tuple - sLocalPrefix = "[TUPLE]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : ()" - self.listOutLines.append(sOut.strip()) - else: - # -- list elements of tuple - self.listGlobalPrefixes.append("[TUPLE]") - nCnt = 0 - for oElement in oData: - nCnt = nCnt + 1 - sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" - self.listGlobalPrefixes.append(sCnt) - self.TypePrint(oElement, bHexFormat) # >>>> recursion - del self.listGlobalPrefixes[-1] # remove prefix count - del self.listGlobalPrefixes[-1] # remove prefix name - - elif type(oData) == set: - nNrOfElements = len(oData) - if nNrOfElements == 0: - # -- indicate empty set - sLocalPrefix = "[SET]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : ()" - self.listOutLines.append(sOut.strip()) - else: - # -- list elements of set - self.listGlobalPrefixes.append("[SET]") - nCnt = 0 - for oElement in oData: - nCnt = nCnt + 1 - sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" - self.listGlobalPrefixes.append(sCnt) - self.TypePrint(oElement, bHexFormat) # >>>> recursion - del self.listGlobalPrefixes[-1] # remove prefix count - del self.listGlobalPrefixes[-1] # remove prefix name - - elif type(oData) == dict: - nNrOfElements = len(oData) - if nNrOfElements == 0: - # -- indicate empty dictionary - sLocalPrefix = "[DICT]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : {}" - self.listOutLines.append(sOut.strip()) - else: - # -- list elements of dictionary - self.listGlobalPrefixes.append("[DICT]") - nCnt = 0 - listKeys = list(oData.keys()) - for sKey in listKeys: - nCnt = nCnt + 1 - oValue = oData[sKey] - sCntAndKey = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") > {" + str(sKey) + "}" - self.listGlobalPrefixes.append(sCntAndKey) - self.TypePrint(oValue, bHexFormat) # >>>> recursion - del self.listGlobalPrefixes[-1] # remove prefix count - del self.listGlobalPrefixes[-1] # remove prefix name - - # elif type(oData) == dotdict: - elif ( (type(oData) == dotdict) or (str(type(oData)) == "") ): - nNrOfElements = len(oData) - if nNrOfElements == 0: - # -- indicate empty dot dictionary - sLocalPrefix = "[DOTDICT]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : {}" - self.listOutLines.append(sOut.strip()) - else: - # -- list elements of dot dictionary - self.listGlobalPrefixes.append("[DOTDICT]") - nCnt = 0 - listKeys = list(oData.keys()) - for sKey in listKeys: - nCnt = nCnt + 1 - oValue = oData[sKey] - sCntAndKey = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") > {" + str(sKey) + "}" - self.listGlobalPrefixes.append(sCntAndKey) - self.TypePrint(oValue, bHexFormat) # >>>> recursion - del self.listGlobalPrefixes[-1] # remove prefix count - del self.listGlobalPrefixes[-1] # remove prefix name - - else: - sLocalPrefix = "[" + str(type(oData)) + "]" - sGlobalPrefix = " ".join(self.listGlobalPrefixes) - sData = str(oData) - if bHexFormat is True: - sData = self._ToHex(sData) - sOut = sGlobalPrefix + " " + sLocalPrefix + " : '" + sData + "'" - self.listOutLines.append(sOut.strip()) - - return self.listOutLines - - # eof def TypePrint(...): - -# eof class CTypePrint(): - -# ************************************************************************************************************** - diff --git a/additions/PythonExtensionsCollection/Utils/__init__.py b/additions/PythonExtensionsCollection/Utils/__init__.py deleted file mode 100644 index 958420af..00000000 --- a/additions/PythonExtensionsCollection/Utils/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/additions/PythonExtensionsCollection/__init__.py b/additions/PythonExtensionsCollection/__init__.py deleted file mode 100644 index 958420af..00000000 --- a/additions/PythonExtensionsCollection/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/additions/PythonExtensionsCollection/version.py b/additions/PythonExtensionsCollection/version.py deleted file mode 100644 index b5b224a4..00000000 --- a/additions/PythonExtensionsCollection/version.py +++ /dev/null @@ -1,23 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# Version and date of PythonExtensionsCollection -# -VERSION = "0.8.0" -VERSION_DATE = "28.06.2022" - From 466f476450eab33bf1d9080d856c9e624d771862 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 15:58:58 +0700 Subject: [PATCH 08/18] Revert "Removed PythonExtensionsCollection" This reverts commit aa29cd2d98ea43126372b2d3e8466fc4dd536a8e. --- .../PythonExtensionsCollection/File/CFile.py | 1072 +++++++++++++++ .../File/__init__.py | 13 + .../Folder/CFolder.py | 460 +++++++ .../Folder/__init__.py | 13 + .../String/CString.py | 1165 +++++++++++++++++ .../String/__init__.py | 13 + .../Utils/CUtils.py | 374 ++++++ .../Utils/__init__.py | 13 + .../PythonExtensionsCollection/__init__.py | 13 + .../PythonExtensionsCollection/version.py | 23 + 10 files changed, 3159 insertions(+) create mode 100644 additions/PythonExtensionsCollection/File/CFile.py create mode 100644 additions/PythonExtensionsCollection/File/__init__.py create mode 100644 additions/PythonExtensionsCollection/Folder/CFolder.py create mode 100644 additions/PythonExtensionsCollection/Folder/__init__.py create mode 100644 additions/PythonExtensionsCollection/String/CString.py create mode 100644 additions/PythonExtensionsCollection/String/__init__.py create mode 100644 additions/PythonExtensionsCollection/Utils/CUtils.py create mode 100644 additions/PythonExtensionsCollection/Utils/__init__.py create mode 100644 additions/PythonExtensionsCollection/__init__.py create mode 100644 additions/PythonExtensionsCollection/version.py diff --git a/additions/PythonExtensionsCollection/File/CFile.py b/additions/PythonExtensionsCollection/File/CFile.py new file mode 100644 index 00000000..320424c6 --- /dev/null +++ b/additions/PythonExtensionsCollection/File/CFile.py @@ -0,0 +1,1072 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# CFile.py +# +# XC-CT/ECA3-Queckenstedt +# +# 27.06.2022 +# +# ************************************************************************************************************** + +# -- import standard Python modules +import os, shutil, platform + +# -- import Bosch Python modules +from PythonExtensionsCollection.String.CString import CString + +# ************************************************************************************************************** + +class enFileStatiType: + """ +The class ``enFileStatiType`` defines the sollowing file states: + +* ``closed`` +* ``openedforwriting`` +* ``openedforappending`` +* ``openedforreading`` + """ + closed = "closed" + openedforwriting = "openedforwriting" + openedforappending = "openedforappending" + openedforreading = "openedforreading" + +# -------------------------------------------------------------------------------------------------------------- + +class CFile(object): + """ +The class ``CFile`` provides a small set of file functions with extended parametrization (like switches +defining if a file is allowed to be overwritten or not). + +Most of the functions at least returns ``bSuccess`` and ``sResult``. + +* ``bSuccess`` is ``True`` in case of no error occurred. +* ``bSuccess`` is ``False`` in case of an error occurred. +* ``bSuccess`` is ``None`` in case of a very fatal error occurred (exceptions). + +* ``sResult`` contains details about what happens during computation. + +Every instance of CFile handles one single file only and forces exclusive access to this file. + +It is not possible to create an instance of this class with a file that is already in use by another instance. + +It is also not possible to use ``CopyTo`` or ``MoveTo`` to overwrite files that are already in use by another instance. +This makes the file handling more save against access violations. + """ + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __init__(self, sFile=None): + self.__sFile = CString.NormalizePath(sFile) + self.__oFileHandle = None + self.__oFileStatus = enFileStatiType.closed + self.__sLastDestination = None + + try: + CFile.__listFilesInUse + except: + CFile.__listFilesInUse = [] + + # exclusive access is required (checked by self.__bIsFreeToUse; relevant for destination in CopyTo and MoveTo) + if self.__sFile in CFile.__listFilesInUse: + raise Exception(f"The file '{self.__sFile}' is already in use by another CFile instance.") + else: + CFile.__listFilesInUse.append(self.__sFile) + + # eof def __init__(self, sFile=None): + + def __del__(self): + self.Close() + if self.__sFile in CFile.__listFilesInUse: + CFile.__listFilesInUse.remove(self.__sFile) + + # eof def __del__(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __bIsFreeToUse(self, sFile=None): + """ +Checks if the file ``sFile`` is free to use, that means: not used by another instance of ``CFile``. + """ + + bIsFreeToUse = False # init + if sFile is None: + bIsFreeToUse = False # error handling + else: + if sFile in CFile.__listFilesInUse: + bIsFreeToUse = False + else: + bIsFreeToUse = True + return bIsFreeToUse + + # eof def __bIsFreeToUse(self, sFile=None): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __OpenForWriting(self): + """ +Opens a text file for writing. + +Returns ``bSuccess`` and ``sResult`` (feedback). + """ + + sMethod = "CFile.__OpenForWriting" + + if self.__sFile is None: + bSuccess = False + sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + try: + self.__oFileHandle = open(self.__sFile, "w", encoding="utf-8") + self.__oFileStatus = enFileStatiType.openedforwriting + bSuccess = True + sResult = f"File '{self.__sFile}' is open for writing" + except Exception as reason: + self.Close() + bSuccess = None + sResult = f"Not possible to open file '{self.__sFile}' for writing.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def __OpenForWriting(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __OpenForAppending(self): + """ +Opens a text file for appending. + +Returns ``bSuccess`` and ``sResult`` (feedback). + """ + + sMethod = "CFile.__OpenForAppending" + + if self.__sFile is None: + bSuccess = False + sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + try: + self.__oFileHandle = open(self.__sFile, "a", encoding="utf-8") + self.__oFileStatus = enFileStatiType.openedforappending + bSuccess = True + sResult = f"File '{self.__sFile}' is open for appending" + except Exception as reason: + self.Close() + bSuccess = None + sResult = f"Not possible to open file '{self.__sFile}' for appending.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def __OpenForAppending(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __OpenForReading(self): + """ +Opens a text file for reading. + +Returns ``bSuccess`` and ``sResult`` (feedback). + """ + + sMethod = "CFile.__OpenForReading" + + if self.__sFile is None: + bSuccess = False + sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + try: + self.__oFileHandle = open(self.__sFile, "r", encoding="utf-8") + self.__oFileStatus = enFileStatiType.openedforreading + bSuccess = True + sResult = f"File '{self.__sFile}' is open for reading" + except Exception as reason: + self.Close() + bSuccess = None + sResult = f"Not possible to open file '{self.__sFile}' for reading.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def __OpenForReading(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Close(self): + """ +Closes the opened file. + +**Arguments:** + +(no args) + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFile.Close" + + if self.__oFileHandle is not None: + try: + self.__oFileHandle.flush() + self.__oFileHandle.close() + bSuccess = True + sResult = f"File '{self.__sFile}' closed" + except Exception as reason: + bSuccess = None + sResult = f"Exception while closing file '{self.__sFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + self.__oFileHandle = None + else: + bSuccess = True + sResult = "Done" + + self.__oFileStatus = enFileStatiType.closed + + return bSuccess, sResult + + # eof def Close(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Delete(self, bConfirmDelete=True): + """ +Deletes the current file. + +**Arguments:** + +* ``bConfirmDelete`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + Defines if it will be handled as error if the file does not exist. + + If ``True``: If the file does not exist, the method indicates an error (``bSuccess = False``). + + If ``False``: It doesn't matter if the file exists or not. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + + sMethod = "CFile.Delete" + + if self.__sFile is None: + bSuccess = False + sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isfile(self.__sFile) is False: + if bConfirmDelete is True: + bSuccess = False + else: + bSuccess = True + sResult = f"Nothing to delete. The file '{self.__sFile}' does not exist." + return bSuccess, sResult + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + try: + os.remove(self.__sFile) + bSuccess = True + sResult = f"File '{self.__sFile}' deleted." + except Exception as reason: + bSuccess = None + sResult = f"Exception while deleting file '{self.__sFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def Delete(self, bConfirmDelete=True): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __PrepareOutput(self, Content=""): + """ +Helper for ``Write`` and ``Append`` (consideration of composite data types). + +Returns a list of strings (that will be written to file). + """ + + listOut = [] + + if type(Content) == list: + for element in Content: + listOut.append(str(element)) + elif type(Content) == tuple: + for element in Content: + listOut.append(str(element)) + elif type(Content) == set: + for element in Content: + listOut.append(str(element)) + elif type(Content) == dict: + listKeys = Content.keys() + nRJust = 0 + for key in listKeys: + sKey = str(key) # because also numerical values can be keys + if len(sKey) > nRJust: + nRJust = len(sKey) + for key in listKeys: + sKey = str(key) # because also numerical values can be keys + sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) + listOut.append(sOut) + elif str(type(Content)).lower().find('dotdict') >=0: + try: + listKeys = Content.keys() + nRJust = 0 + for key in listKeys: + sKey = str(key) # because also numerical values can be keys + if len(sKey) > nRJust: + nRJust = len(sKey) + for key in listKeys: + sKey = str(key) # because also numerical values can be keys + sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) + listOut.append(sOut) + except Exception as reason: + listOut.append(str(Content)) + else: + listOut.append(str(Content)) + + return listOut + + # eof def __PrepareOutput(self, Content=""): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): + """ +Writes the content of a variable ``Content`` to file. + +**Arguments:** + +* ``Content`` + + / *Condition*: required / *Type*: one of: str, list, tuple, set, dict, dotdict / + + If ``Content`` is not a string, the ``Write`` method resolves the data structure before writing the content to file. + +* ``nVSpaceAfter`` + + / *Condition*: optional / *Type*: int / *Default*: 0 / + + Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. + +* ``sPrefix`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + `sPrefix`` is added to every line of output (in case of ``sPrefix`` is not ``None``). + +* ``bToScreen`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + Prints ``Content`` also to screen (in case of ``bToScreen`` is ``True``). + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + + sMethod = "CFile.Write" + + if self.__oFileStatus != enFileStatiType.openedforwriting: + bSuccess, sResult = self.__OpenForWriting() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + listOut = self.__PrepareOutput(Content) + + for nCnt in range(nVSpaceAfter): + listOut.append("") + + if bToScreen is True: + for sOut in listOut: + if ( (sPrefix is not None) and (sOut != '') ): + sOut = f"{sPrefix}{sOut}" + print(sOut) + + bSuccess = True + sResult = "Done" + try: + for sOut in listOut: + if ( (sPrefix is not None) and (sOut != '') ): + sOut = f"{sPrefix}{sOut}" + self.__oFileHandle.write(sOut + "\n") + except Exception as reason: + bSuccess = None + sResult = f"Not possible to write to file '{self.__sFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): + """ +Appends the content of a variable ``Content`` to file. + +**Arguments:** + +* ``Content`` + + / *Condition*: required / *Type*: one of: str, list, tuple, set, dict, dotdict / + + If ``Content`` is not a string, the ``Write`` method resolves the data structure before writing the content to file. + +* ``nVSpaceAfter`` + + / *Condition*: optional / *Type*: int / *Default*: 0 / + + Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. + +* ``sPrefix`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + `sPrefix`` is added to every line of output (in case of ``sPrefix`` is not ``None``). + +* ``bToScreen`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + Prints ``Content`` also to screen (in case of ``bToScreen`` is ``True``). + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFile.Append" + + if self.__oFileStatus != enFileStatiType.openedforappending: + bSuccess, sResult = self.__OpenForAppending() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + listOut = self.__PrepareOutput(Content) + + for nCnt in range(nVSpaceAfter): + listOut.append("") + + if bToScreen is True: + for sOut in listOut: + if ( (sPrefix is not None) and (sOut != '') ): + sOut = f"{sPrefix}{sOut}" + print(sOut) + + bSuccess = True + sResult = "Done" + try: + for sOut in listOut: + if ( (sPrefix is not None) and (sOut != '') ): + sOut = f"{sPrefix}{sOut}" + self.__oFileHandle.write(sOut + "\n") + except Exception as reason: + bSuccess = None + sResult = f"Not possible to append to file '{self.__sFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def ReadLines(self, + bCaseSensitive = True, + bSkipBlankLines = False, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None, + bLStrip = False, + bRStrip = True, + bToScreen = False): + """ +Reads content from current file. Returns an array of lines together with ``bSuccess`` and ``sResult`` (feedback). + +The method takes care of opening and closing the file. The complete file content is read by ``ReadLines`` in one step, +but with the help of further parameters it is possible to reduce the content by including and excluding lines. + +The logical join of all filter is: ``AND``. + +**Arguments:** + +* ``bCaseSensitive`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + * If ``True``, the standard filters work case sensitive, otherwise not. + * This has no effect to the regular expression based filters ``sInclRegEx`` and ``sExclRegEx``. + +* ``bSkipBlankLines`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True``, blank lines will be skipped, otherwise not. + +* ``sComment`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + In case of a line starts with the string ``sComment``, this line is skipped. + +* ``sStartsWith`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string starts with the string ``sStartsWith`` + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +* ``sEndsWith`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string ends with the string ``sEndsWith`` + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +* ``sStartsNotWith`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string starts not with the string ``sStartsNotWith`` + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +* ``sEndsNotWith`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string ends not with the string ``sEndsNotWith`` + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +* ``sContains`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string contains the string ``sContains`` at any position + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +* ``sContainsNot`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * The criterion of this filter is fulfilled in case of the input string does **not** contain the string ``sContainsNot`` at any position + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +* ``sInclRegEx`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * *Include* filter based on regular expressions (consider the syntax of regular expressions!) + * The criterion of this filter is fulfilled in case of the regular expression ``sInclRegEx`` matches the input string + * Leading and trailing blanks within the input string are considered + * ``bCaseSensitive`` has no effect + * A semicolon separated list of several regular expressions is **not** supported + +* ``sExclRegEx`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + * *Exclude* filter based on regular expressions (consider the syntax of regular expressions!) + * The criterion of this filter is fulfilled in case of the regular expression ``sExclRegEx`` does **not** match the input string + * Leading and trailing blanks within the input string are considered + * ``bCaseSensitive`` has no effect + * A semicolon separated list of several regular expressions is **not** supported + +* ``bLStrip`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True``, leading spaces are removed from line before the filters are used, otherwise not. + +* ``bRStrip`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + If ``True``, trailing spaces are removed from line before the filters are used, otherwise not. + +* ``bToScreen`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True``, the content read from file is also printed to screen, otherwise not. + """ + + sMethod = "CFile.ReadLines" + + listLines = [] + + if os.path.isfile(self.__sFile) is False: + bSuccess = False + sResult = f"The file '{self.__sFile}' does not exist." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return listLines, bSuccess, sResult + + # !!! independend from: self.__oFileStatus != enFileStatiType.openedforreading: !!! + # Reason: Repeated call of ReadLines needs to have the read pointer at the beginning of the file. + bSuccess, sResult = self.__OpenForReading() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return listLines, bSuccess, sResult + + try: + sFileContent = self.__oFileHandle.read() + except Exception as reason: + bSuccess = None + sResult = f"Not possible to read from file '{self.__sFile}'.\nReason: " + str(reason) + return listLines, bSuccess, sResult + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return listLines, bSuccess, sResult + + listFileContent = sFileContent.splitlines() # in opposite to readlines this is OS independend! + + for sLine in listFileContent: + if CString.StringFilter(sString = sLine, + bCaseSensitive = bCaseSensitive, + bSkipBlankStrings = bSkipBlankLines, + sComment = sComment, + sStartsWith = sStartsWith, + sEndsWith = sEndsWith, + sStartsNotWith = sStartsNotWith, + sEndsNotWith = sEndsNotWith, + sContains = sContains, + sContainsNot = sContainsNot, + sInclRegEx = sInclRegEx, + sExclRegEx = sExclRegEx, + bDebug = False) is True: + if bLStrip is True: + sLine = sLine.lstrip(" \t\r\n") + + if bRStrip is True: + sLine = sLine.rstrip(" \t\r\n") + + if bToScreen is True: + print(sLine) + + listLines.append(sLine) + + # eof for sLine in listFileContent: + + del listFileContent + + nNrOfLines = len(listLines) + + bSuccess = True + sResult = f"Read {nNrOfLines} lines from '{self.__sFile}'." + return listLines, bSuccess, sResult + + # eof def ReadLines(...) + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def GetFileInfo(self): + """ +Returns the following informations about the file (encapsulated within a dictionary ``dFileInfo``): + +**Returns:** + +* Key ``sFile`` + + / *Type*: str / + + Path and name of current file + + +* Key ``bFileIsExisting`` + + / *Type*: bool / + + ``True`` if file is existing, otherwise ``False`` + +* Key ``sFileName`` + + / *Type*: str / + + The name of the current file (incl. extension) + +* Key ``sFileExtension`` + + / *Type*: str / + + The extension of the current file + +* Key ``sFileNameOnly`` + + / *Type*: str / + + The pure name of the current file (without extension) + +* Key ``sFilePath`` + + / *Type*: str / + + The the path to current file + +* Key ``bFilePathIsExisting`` + + / *Type*: bool / + + ``True`` if file path is existing, otherwise ``False`` + """ + + sMethod = "CFile.GetFileInfo" + + dFileInfo = {} + dFileInfo['sFile'] = None + dFileInfo['bFileIsExisting'] = None + dFileInfo['sFileName'] = None + dFileInfo['sFileExtension'] = None + dFileInfo['sFileNameOnly'] = None + dFileInfo['sFilePath'] = None + dFileInfo['bFilePathIsExisting'] = None + + if self.__sFile is None: + return None + + dFileInfo['sFile'] = self.__sFile + dFileInfo['bFileIsExisting'] = os.path.isfile(self.__sFile) + + sFileName = os.path.basename(self.__sFile) + dFileInfo['sFileName'] = sFileName + + sFileExtension = "" + sFileNameOnly = "" + listParts = sFileName.split('.') + if len(listParts) > 1: + sFileExtension = listParts[len(listParts)-1] + sFileNameOnly = sFileName[:-len(sFileExtension)-1] + else: + sFileExtension = "" + sFileNameOnly = sFileName + + dFileInfo['sFileExtension'] = sFileExtension + dFileInfo['sFileNameOnly'] = sFileNameOnly + dFileInfo['sFilePath'] = os.path.dirname(self.__sFile) + dFileInfo['bFilePathIsExisting'] = os.path.isdir(dFileInfo['sFilePath']) + + return dFileInfo + + # eof def GetFileInfo(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def CopyTo(self, sDestination=None, bOverwrite=False): + """ +Copies the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. + +In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. + +In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten +and ``CopyTo`` returns ``bSuccess = False``. + +**Arguments:** + +* ``sDestination`` + + / *Condition*: required / *Type*: string / + + The path to destination file (either incl. file name or without file name) + +* ``bOverwrite`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + * In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. + * In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten + and ``CopyTo`` returns ``bSuccess = False``. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFile.CopyTo" + + if self.__sFile is None: + bSuccess = False + sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isfile(self.__sFile) is False: + bSuccess = False + sResult = f"The file '{self.__sFile}' does not exist, therefore nothing can be copied." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if sDestination is None: + bSuccess = False + sResult = "sDestination is None; please provide path and name of destination file. Or at least the destination path. In this case the file name will be taken over." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + sDestination = CString.NormalizePath(sDestination) + + bDeleteDestFile = False + + sDestFile = sDestination # default + + if os.path.isdir(sDestination) is True: + sFileName = os.path.basename(self.__sFile) + sDestFile = f"{sDestination}/{sFileName}" # file name in destination is required for: shutil.copyfile + + if self.__bIsFreeToUse(sDestFile) is False: + bSuccess = False + sResult = f"The destination file '{sDestFile}' is already in use by another CFile instance." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + self.__sLastDestination = sDestFile + + if os.path.isfile(sDestFile) is True: + # destination file already exists + if sDestFile == self.__sFile: + bSuccess = False + sResult = f"Source file and destination file are the same: '{self.__sFile}'. Therefore nothing to do." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if bOverwrite is True: + bDeleteDestFile = True + else: + bSuccess = False + sResult = f"Not allowed to overwrite existing destination file '{sDestFile}'. Therefore nothing to do." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + else: + # destination file not yet exists + # (we assume here that the destination shall be a file because we already have figured out that the destination is not a folder) + # => we have to check if the path to the file exists + sDestFilePath = os.path.dirname(sDestFile) + if os.path.isdir(sDestFilePath) is True: + bDeleteDestFile = False + else: + bSuccess = False + sResult = f"The destination path '{sDestFilePath}' does not exist. The file '{self.__sFile}' cannot be copied." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + # eof else - if os.path.isfile(sDestFile) is True: + + # analysis done, now the action + + bSuccess, sResult = self.Close() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if bDeleteDestFile is True: + # To delete the destination file explicitely before executing any copy-function is an addon here in this library. + # The purpose is to be independend from the way the used copy function is handling existing destination files. + # But this makes only sense under Windows and not under Linux, because Windows is much more strict with access + # violations than Linux. Therefore we avoid such kind of additional steps in case of the platform is not Windows. + if platform.system() == "Windows": + try: + os.remove(sDestFile) + bSuccess = True + sResult = f"File '{sDestFile}' deleted." + except Exception as reason: + bSuccess = None + sResult = f"Exception while deleting destination file '{sDestFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + # eof if bDeleteDestFile is True: + + try: + shutil.copyfile(self.__sFile, sDestFile) + bSuccess = True + sResult = f"File '{self.__sFile}' copied to '{sDestFile}'." + except Exception as reason: + bSuccess = None + sResult = f"Exception while copying file '{self.__sFile}' to '{sDestFile}'.\nReason: " + str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def CopyTo(self, sDestination=None, bOverwrite=False): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def MoveTo(self, sDestination=None, bOverwrite=False): + """ +Moves the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. + +**Arguments:** + +* ``sDestination`` + + / *Condition*: required / *Type*: string / + + The path to destination file (either incl. file name or without file name) + +* ``bOverwrite`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + * In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. + * In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten + and ``MoveTo`` returns ``bSuccess = False``. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation was successful or not + +* ``sResult`` + + / *Type*: str / + + Contains details about what happens during computation + """ + sMethod = "CFile.MoveTo" + + bSuccess, sResult = self.CopyTo(sDestination, bOverwrite) + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isfile(self.__sLastDestination) is False: + # the copied file should exist at new location + bSuccess = None + sResult = f"Someting went wrong while copying the file '{self.__sFile}' to '{self.__sLastDestination}'. Aborting." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + else: + bSuccess, sResult = self.Delete() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + bSuccess = True + sResult = f"File moved from '{self.__sFile}' to '{self.__sLastDestination}'" + return bSuccess, sResult + + # eof def MoveTo(self, sDestination=None, bOverwrite=False): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + +# eof class CFile(object): + +# ************************************************************************************************************** + + diff --git a/additions/PythonExtensionsCollection/File/__init__.py b/additions/PythonExtensionsCollection/File/__init__.py new file mode 100644 index 00000000..958420af --- /dev/null +++ b/additions/PythonExtensionsCollection/File/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/additions/PythonExtensionsCollection/Folder/CFolder.py b/additions/PythonExtensionsCollection/Folder/CFolder.py new file mode 100644 index 00000000..451c32f9 --- /dev/null +++ b/additions/PythonExtensionsCollection/Folder/CFolder.py @@ -0,0 +1,460 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# CFolder.py +# +# XC-CT/ECA3-Queckenstedt +# +# 28.06.2022 +# +# ************************************************************************************************************** + +# -- import standard Python modules +import os, shutil, time, stat + +# -- import Bosch Python modules +from PythonExtensionsCollection.String.CString import CString + +# -------------------------------------------------------------------------------------------------------------- + +# little helper to delete folders containing files that are write protected +def rm_dir_readonly(func, path, excinfo): + """ +Calls ``os.chmod`` in case of ``shutil.rmtree`` (within ``Delete()``) throws an exception (making files writable). + """ + # print(f"{excinfo}") # debug only + os.chmod(path, stat.S_IWRITE) + func(path) + +# -------------------------------------------------------------------------------------------------------------- + +class CFolder(object): + """ +The class ``CFolder`` provides a small set of folder functions with extended parametrization (like switches +defining if a folder is allowed to be overwritten or not). + +Most of the functions at least returns ``bSuccess`` and ``sResult``. + +* ``bSuccess`` is ``True`` in case of no error occurred. +* ``bSuccess`` is ``False`` in case of an error occurred. +* ``bSuccess`` is ``None`` in case of a very fatal error occurred (exceptions). + +* ``sResult`` contains details about what happens during computation. + +Every instance of CFolder handles one single folder only and forces exclusive access to this folder. + +It is not possible to create an instance of this class with a folder that is already in use by another instance. + +The constructor of ``CFolder`` requires the input parameter ``sFolder``, that is the path and the name of a folder +that is handled by the current class instance. + """ + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __init__(self, sFolder=None): + self.__sFolder = CString.NormalizePath(sFolder) + + try: + CFolder.__listFoldersInUse + except: + CFolder.__listFoldersInUse = [] + + # exclusive access is required (checked by self.__bIsFreeToUse; relevant for destination in CopyTo and MoveTo) + if self.__sFolder in CFolder.__listFoldersInUse: + raise Exception(f"The folder '{self.__sFolder}' is already in use by another CFolder instance.") + else: + CFolder.__listFoldersInUse.append(self.__sFolder) + + # eof def __init__(self, sFolder=None): + + def __del__(self): + if self.__sFolder in CFolder.__listFoldersInUse: + CFolder.__listFoldersInUse.remove(self.__sFolder) + + # eof def __del__(self): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __bIsFreeToUse(self, sFolder=None): + """ +Checks if the folder ``sFolder`` is free to use, that means: not used by another instance of ``CFolder``. + """ + + bIsFreeToUse = False # init + if sFolder is None: + bIsFreeToUse = False # error handling + else: + if sFolder in CFolder.__listFoldersInUse: + bIsFreeToUse = False + else: + bIsFreeToUse = True + return bIsFreeToUse + + # eof def __bIsFreeToUse(self, sFolder=None): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def __Delete(self, sFolder=None, bConfirmDelete=True): + """ +Deletes the folder ``sFolder``. + +**Arguments:** + +* ``sFolder`` + + / *Condition*: required / *Type*: str / + + Path and name of folder to be deleted + +* ``bConfirmDelete`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + Defines if it will be handled as error if the folder does not exist. + + If ``True``: If the folder does not exist, the method indicates an error (``bSuccess = False``). + + If ``False``: It doesn't matter if the folder exists or not. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFolder.__Delete" + + if sFolder is None: + bSuccess = False + sResult = "sFolder is None; please provide path and name of a folder when creating a CFolder object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isdir(sFolder) is False: + sResult = f"Nothing to delete. The folder '{sFolder}' does not exist." + if bConfirmDelete is True: + bSuccess = False + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + else: + bSuccess = True + return bSuccess, sResult + # eof if os.path.isdir(sFolder) is False: + + bSuccess = False + sResult = "UNKNOWN" + nCntTries = 1 + nTriesMax = 4 + nDelay = 2 # sec + listResults = [] + while nCntTries <= nTriesMax: + try: + print(f"Trying to delete '{sFolder}'") + print() + shutil.rmtree(sFolder, ignore_errors=False, onerror=rm_dir_readonly) + except Exception as reason: + listResults.append(str(reason)) + if os.path.isdir(sFolder) is True: + sResult = f"({nCntTries}/{nTriesMax}) Problem with deleting the folder '{sFolder}'. Folder still present." + listResults.append(sResult) + time.sleep(nDelay) # delay before next try + else: + bSuccess = True + sResult = f"Folder '{sFolder}' deleted." + break + nCntTries = nCntTries + 1 + # eof while nCntTries <= nTriesMax: + + if bSuccess is False: + sResult = "\n".join(listResults) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def __Delete(self, sFolder=None, bConfirmDelete=True): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Delete(self, bConfirmDelete=True): + """ +Deletes the folder the current class instance contains. + +**Arguments:** + +* ``bConfirmDelete`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + Defines if it will be handled as error if the folder does not exist. + + If ``True``: If the folder does not exist, the method indicates an error (``bSuccess = False``). + + If ``False``: It doesn't matter if the folder exists or not. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFolder.Delete" + bSuccess, sResult = self.__Delete(self.__sFolder, bConfirmDelete) + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + # eof def Delete(self, bConfirmDelete=True): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def Create(self, bOverwrite=False, bRecursive=False): + """ +Creates the current folder ``sFolder``. + +**Arguments:** + +* ``bOverwrite`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + * In case of the folder already exists and ``bOverwrite`` is ``True``, than the folder will be deleted before creation. + * In case of the folder already exists and ``bOverwrite`` is ``False`` (default), than the folder will not be touched. + + In both cases the return value ``bSuccess`` is ``True`` - because the folder exists. + +* ``bRecursive`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + * In case of ``bRecursive`` is ``True``, than the complete destination path will be created (including all intermediate subfolders). + * In case of ``bRecursive`` is ``False``, than it is expected that the parent folder of the new folder already exists. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFolder.Create" + + if self.__sFolder is None: + bSuccess = False + sResult = "self.__sFolder is None; please provide path and name of a folder when creating a CFolder object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + bCreateFolder = False + if os.path.isdir(self.__sFolder) is True: + if bOverwrite is True: + bSuccess, sResult = self.Delete() + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + bCreateFolder = True + else: + bSuccess = True + sResult = f"Folder '{self.__sFolder}' already exists." + return bSuccess, sResult + else: + bCreateFolder = True + + bSuccess = False + sResult = "UNKNOWN" + + if bCreateFolder is True: + nCntTries = 1 + nTriesMax = 3 + nDelay = 2 # sec + listResults = [] + while nCntTries <= nTriesMax: + try: + print(f"Trying to create '{self.__sFolder}'") + print() + if bRecursive is True: + os.makedirs(self.__sFolder) + else: + os.mkdir(self.__sFolder) + except Exception as reason: + listResults.append(str(reason)) + if os.path.isdir(self.__sFolder) is False: + sResult = f"({nCntTries}/{nTriesMax}) Problem with creating the folder '{self.__sFolder}'." + listResults.append(sResult) + time.sleep(nDelay) # delay before next try + else: + bSuccess = True + sResult = f"Folder '{self.__sFolder}' created." + break + nCntTries = nCntTries + 1 + # eof while nCntTries <= nTriesMax: + + if bSuccess is False: + sResult = "\n".join(listResults) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + # eof if bCreateFolder is True: + + return bSuccess, sResult + + # eof def Create(self, bOverwrite=False, bRecursive=False): + + # -------------------------------------------------------------------------------------------------------------- + # TM*** + + def CopyTo(self, sDestination=None, bOverwrite=False): + """ +Copies the current folder to ``sDestination``, that has to be a path to a folder **within** the source folder will be copied to +(with it's original name), + +In case of the destination folder already exists and ``bOverwrite`` is ``True``, than the destination folder will be overwritten. + +In case of the destination folder already exists and ``bOverwrite`` is ``False`` (default), than the destination folder will not be overwritten +and ``CopyTo`` returns ``bSuccess = False``. + +**Arguments:** + +* ``sDestination`` + + / *Condition*: required / *Type*: string / + + The path to destination folder + +* ``bOverwrite`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + * In case of the destination folder already exists and ``bOverwrite`` is ``True``, than the destination folder will be overwritten. + * In case of the destination folder already exists and ``bOverwrite`` is ``False`` (default), than the destination folder will not be overwritten + and ``CopyTo`` returns ``bSuccess = False``. + +**Returns:** + +* ``bSuccess`` + + / *Type*: bool / + + Indicates if the computation of the method was successful or not. + +* ``sResult`` + + / *Type*: str / + + The result of the computation of the method. + """ + sMethod = "CFolder.CopyTo" + + if self.__sFolder is None: + bSuccess = False + sResult = "self.__sFolder is None; please provide path and name of a folder when creating a CFolder object." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isdir(self.__sFolder) is False: + bSuccess = False + sResult = f"The folder '{self.__sFolder}' does not exist, therefore nothing can be copied." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if sDestination is None: + bSuccess = False + sResult = "sDestination is None; please provide a path to a destination folder." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + sDestination = CString.NormalizePath(sDestination) + + if os.path.isdir(sDestination) is False: + # the folder to be copied will be created within the destination folder, therefore we expect that the destination folder already exists + bSuccess = False + sResult = f"The destination folder '{sDestination}' does not exist." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + sSourceFolderName = os.path.basename(self.__sFolder) + sDestFolder = f"{sDestination}/{sSourceFolderName}" + + if sDestFolder == self.__sFolder: + bSuccess = False + sResult = f"Source folder and destination folder are the same: '{self.__sFolder}'. Therefore nothing to do." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if self.__bIsFreeToUse(sDestFolder) is False: + bSuccess = False + sResult = f"The destination folder '{sDestFolder}' is already in use by another CFolder instance." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + + if os.path.isdir(sDestFolder) is True: + # destination folder already exists + if bOverwrite is True: + bSuccess, sResult = self.__Delete(sDestFolder) + if bSuccess is not True: + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + else: + bSuccess = False + sResult = f"Not allowed to overwrite existing destination folder '{sDestFolder}'. Therefore nothing to do." + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + return bSuccess, sResult + # eof if os.path.isdir(sDestFolder) is True: + + # analysis and preconditions done, now the action + + try: + shutil.copytree(self.__sFolder, sDestFolder) + bSuccess = True + sResult = "Folder copied from\n> '" + self.__sFolder + "'\nto\n> '" + sDestFolder + "'" + except Exception as reason: + bSuccess = None + sResult = str(reason) + sResult = CString.FormatResult(sMethod, bSuccess, sResult) + + return bSuccess, sResult + + # eof def CopyTo(self, sDestination=None, bOverwrite=False): + +# -------------------------------------------------------------------------------------------------------------- + + diff --git a/additions/PythonExtensionsCollection/Folder/__init__.py b/additions/PythonExtensionsCollection/Folder/__init__.py new file mode 100644 index 00000000..958420af --- /dev/null +++ b/additions/PythonExtensionsCollection/Folder/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/additions/PythonExtensionsCollection/String/CString.py b/additions/PythonExtensionsCollection/String/CString.py new file mode 100644 index 00000000..82aeb204 --- /dev/null +++ b/additions/PythonExtensionsCollection/String/CString.py @@ -0,0 +1,1165 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# CString.py +# +# XC-CT/ECA3-Queckenstedt +# +# 02.06.2022 +# +# ************************************************************************************************************** + +# -- import standard Python modules +import os, ntpath, re + +# ************************************************************************************************************** + +class CString(object): + """ +The class ``CString`` contains some string computation methods like e.g. normalizing a path. + """ + + # -------------------------------------------------------------------------------------------------------------- + #TM*** + + def NormalizePath(sPath=None, bWin=False, sReferencePathAbs=None, bConsiderBlanks=False, bExpandEnvVars=True, bMask=True): + """ +Normalizes local paths, paths to local network resources and internet addresses + +**Arguments:** + +* ``sPath`` + + / *Condition*: required / *Type*: str / + + The path to be normalized + +* ``bWin`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True`` then returned path contains masked backslashes as separator, otherwise slashes + +* ``sReferencePathAbs`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + In case of ``sPath`` is relative and ``sReferencePathAbs`` (expected to be absolute) is given, then + the returned absolute path is a join of both input paths + +* ``bConsiderBlanks`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True`` then the returned path is encapsulated in quotes - in case of the path contains blanks + +* ``bExpandEnvVars`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + If ``True`` then in the returned path environment variables are resolved, otherwise not. + +* ``bMask`` + + / *Condition*: optional / *Type*: bool / *Default*: True (requires ``bWin=True``)/ + + * If ``bWin`` is ``True`` and ``bMask`` is ``True`` then the returned path contains masked backslashes as separator. + * If ``bWin`` is ``True`` and ``bMask`` is ``False`` then the returned path contains single backslashes only - this might be + required for applications, that are not able to handle masked backslashes. + * In case of ``bWin`` is ``False`` ``bMask`` has no effect. + +**Returns:** + +* ``sPath`` + + / *Type*: str / + + The normalized path (is ``None`` in case of ``sPath`` is ``None``) + """ + + if sPath is not None: + + # -- expand Windows environment variables + if bExpandEnvVars is True: + sPath = os.path.expandvars(sPath) + + # - remove leading and trailing horizontal space + sPath = sPath.strip(" \t\r\n") + + # - remove leading and trailing quotes + sPath = sPath.strip("\"'") + + # - remove once more leading and trailing horizontal space + # (after the removal of leading and trailing quotes further horizontal space might be there, that has to be removed; + # but further levels of nesting are not considered) + sPath = sPath.strip(" \t") + + if sPath == "": + return sPath + + # - remove trailing slash or backslash (maybe at end of path to folder) + sPath = sPath.rstrip("/\\") + + # -------------------------------------------------------------------------------------------------------------- + # consider internet addresses and local network resources + # -------------------------------------------------------------------------------------------------------------- + # -- local network resource / file server + # (prepare for Windows explorer) + # either (default) + # //server.com/abc/xyz + # or (with bWin=True); bMask must be False because \\server.com\\abc\\xyz is not allowed + # \\server.com\abc\xyz + # (=> user is allowed to select bWin but not bMask) + # + # -- local network resource / file server + # (prepare for web browser) + # after 'file://///' only single slashes allowed; bWin and bMask must be False + # file://///server.com/abc/xyz + # (=> user is NOT allowed to select bWin and bMask) + # + # -- internet address + # after server name only single slashes allowed; bWin and bMask must be False + # http://server.com/abc/xyz + # https://server.com/abc/xyz + # (=> user is NOT allowed to select bWin and bMask) + # + # - not allowed (=> this method must not return this format): + # http:\\server.com + # https:\\server.com + # -------------------------------------------------------------------------------------------------------------- + + sPathPrefix = None + + # In case there is any prefix, we remove this prefix, we compute the remaining part of the path separately, + # we also modify this prefix manually, and at the end we put the new prefix back to the path. + + if ( (sPath[:2] == "\\\\") or (sPath[:2] == "//") ): + sPath = sPath[2:] + if bWin is True: + sPathPrefix = "\\\\" + else: + sPathPrefix = "//" + bMask = False # !!! this overrules the input parameter value, because masked backslashes are not allowed in remaining path !!! + elif sPath[:10] == "file://///": # exactly this must be given; all other combinations of slashes and backslashes are not handled + sPath = sPath[10:] + sPathPrefix = "file://///" + bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + elif ( (sPath[:7] == "http://") or (sPath[:7] == "http:\\\\") ): + sPath = sPath[7:] + sPathPrefix = "http://" + bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + elif ( (sPath[:8] == "https://") or (sPath[:8] == "https:\\\\") ): + sPath = sPath[8:] + sPathPrefix = "https://" + bWin = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + bMask = False # !!! this overrules the input parameter value, because only single slashes allowed in remaining path !!! + else: + # Internet addresses and local network resources handled, now checking for relative paths: + # In case of sPath is a relative path AND an absolute reference path is provided + # merge them to an absolute path; without reference path use standard function to + # convert relative path to absolute path + if ( (sPath[0] != "%") and (sPath[0] != "$") ): + # If sPath starts with '%' or with '$' it is assumed that the path starts with an environment variable (Windows or Linux). + # But in this case 'os.path.isabs(sPath)' will not detect this to be an absolute path and will call + # 'sPath = os.path.abspath(sPath)' (depending on sReferencePathAbs). This will accidently merge + # the root path together with the path starting with the environment variable and cause invalid results. + if os.path.isabs(sPath) is False: + if sReferencePathAbs is not None: + sPath = os.path.join(sReferencePathAbs, sPath) + else: + sPath = os.path.abspath(sPath) + + # eof computation of sPathPrefix + + # - normalize the path (collapse redundant separators and up-level references) + # on Windows this converts slashes to backward slashes + # sPath = os.path.normpath(sPath) # under Linux this unfortunately keeps redundant separators (in opposite to Windows) + # -- alternative + sPath = ntpath.normpath(sPath) + + # - exchange single backslashes by single slashes (= partly we have to repair the outcome of normpath) + if bWin is False: + sPath = sPath.replace("\\", "/") + else: + if bMask is True: + sPath = sPath.replace("\\", "\\\\") + + # - restore the path prefix + if sPathPrefix is not None: + sPath = f"{sPathPrefix}{sPath}" + + # - consider blanks (prepare path for usage in Windows command line) + if bConsiderBlanks is True: + if sPath.find(" ") >= 0: + sPath = f"\"{sPath}\"" + + # eof if sPath is not None: + + return sPath + + # eof NormalizePath(sPath=None, bWin=False, sReferencePathAbs=None, bConsiderBlanks=False, bExpandEnvVars=True, bMask=True) + + # -------------------------------------------------------------------------------------------------------------- + #TM*** + + def DetectParentPath(sStartPath=None, sFolderName=None, sFileName=None): + """ +Computes the path to any parent folder inside a given path. Optionally DetectParentPath is able +to search for files inside the parent folder. + +**Arguments:** + +* ``sStartPath`` + + / *Condition*: required / *Type*: str / + + The path in which to search for a parent folder + +* ``sFolderName`` + + / *Condition*: required / *Type*: str / + + The name of the folder to search for within ``sStartPath``. It is possible to provide more than one folder name separated by semicolon + +* ``sFileName`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + The name of a file to search within the detected parent folder + +**Returns:** + +* ``sDestPath`` + + / *Type*: str / + + Path and name of parent folder found inside ``sStartPath``, ``None`` in case of ``sFolderName`` is not found inside ``sStartPath``. + In case of more than one parent folder is found ``sDestPath`` contains the first result and ``listDestPaths`` contains all results. + +* ``listDestPaths`` + + / *Type*: list / + + If ``sFolderName`` contains a single folder name this list contains only one element that is ``sDestPath``. + In case of ``FolderName`` contains a semicolon separated list of several folder names this list contains all found paths of the given folder names. + ``listDestPaths`` is ``None`` (and not an empty list!) in case of ``sFolderName`` is not found inside ``sStartPath``. + +* ``sDestFile`` + + / *Type*: str / + + Path and name of ``sFileName``, in case of ``sFileName`` is given and found inside ``listDestPaths``. + In case of more than one file is found ``sDestFile`` contains the first result and ``listDestFiles`` contains all results. + ``sDestFile`` is ``None`` in case of ``sFileName`` is ``None`` and also in case of ``sFileName`` is not found inside ``listDestPaths`` + (and therefore also in case of ``sFolderName`` is not found inside ``sStartPath``). + +* ``listDestFiles`` + + / *Type*: list / + + Contains all positions of ``sFileName`` found inside ``listDestPaths``. + + ``listDestFiles`` is ``None`` (and not an empty list!) in case of ``sFileName`` is ``None`` and also in case of ``sFileName`` + is not found inside ``listDestPaths`` (and therefore also in case of ``sFolderName`` is not found inside ``sStartPath``). + +* ``sDestPathParent`` + + / *Type*: str / + + The parent folder of ``sDestPath``, ``None`` in case of ``sFolderName`` is not found inside ``sStartPath`` (``sDestPath`` is ``None``). + """ + + sDestPath = None + listDestPaths = None + sDestFile = None + listDestFiles = None + sDestPathParent = None + + if sStartPath is None: + return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent + + if sFolderName is None: + return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent + + sStartPath = sStartPath.strip() + if sStartPath == "": + return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent + + sFolderName = sFolderName.strip() + if sFolderName == "": + return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent + + listSplit = sFolderName.split(';') + + listTopLevelFolders = [] + for sFolder in listSplit: + # removing duplicates + sFolder = sFolder.strip() + if sFolder != "": + if sFolder not in listTopLevelFolders: + listTopLevelFolders.append(sFolder) + # eof for sFolder in listSplit: + + nNrOfFolders = len(listTopLevelFolders) + sStartPath = CString.NormalizePath(sStartPath) + listLevels = sStartPath.split("/") + + listDestPaths = [] + + while len(listLevels) > 0: + # -- merging paths with folder names and search for existing combinations + sPathParent = "/".join(listLevels) + for sTLFolder in listTopLevelFolders: + sSubPath = sPathParent + "/" + sTLFolder + if os.path.isdir(sSubPath) is True: + listDestPaths.append(sSubPath) + if len(listTopLevelFolders) == len(listDestPaths): + # all folders found + break + else: + listLevels.pop() + # eof while len(listLevels) > 0: + + sDestPath = None + sDestPathParent = None + if len(listDestPaths) > 0: + # -- returning sDestPath and sDestPathParent related to first entry in list; just to return anything else than None + sDestPath = listDestPaths[0] + sDestPathParent = CString.NormalizePath(os.path.dirname(sDestPath)) + + # -- optionally searching also for a single file + # Input: file name + # Output: full path of file and list of full paths of files (!!! limited to 'listDestPaths' !!!) + + listDestFiles = [] + + if ( (sFileName is not None) and (len(listDestPaths) > 0) ): + for sDestPathToWalk in listDestPaths: + for sLocalRootPath, listFolderNames, listFileNames in os.walk(sDestPathToWalk): + for sFileNameTmp in listFileNames: + if sFileNameTmp == sFileName: + sFile = CString.NormalizePath(os.path.join(sLocalRootPath, sFileName)) + listDestFiles.append(sFile) + # eof for sLocalRootPath, listFolderNames, listFileNames in os.walk(sDestPathToWalk): + # eof for sDestPathToWalk in listDestPaths: + # eof if ( (sFileName is not None) and (len(listDestPaths) > 0) ): + + if len(listDestFiles) > 0: + listDestFiles.sort() + sDestFile = listDestFiles[0] # just to return anything else than None + + # -- preparing output (setting empty lists to None, to have unique criteria for results not available) + if listDestPaths is not None: + if len(listDestPaths) == 0: + listDestPaths = None + if listDestFiles is not None: + if len(listDestFiles) == 0: + listDestFiles = None + + return sDestPath, listDestPaths, sDestFile, listDestFiles, sDestPathParent + + # eof def DetectParentPath(sStartPath=None, sFolderName=None, sFileName=None): + + # -------------------------------------------------------------------------------------------------------------- + #TM*** + + def StringFilter(sString = None, + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None, + bDebug = False): + """ +During the computation of strings there might occur the need to get to know if this string fulfils certain criteria or not. +Such a criterion can e.g. be that the string contains a certain substring. Also an inverse logic might be required: +In this case the criterion is that the string does **not** contain this substring. + +It might also be required to combine several criteria to a final conclusion if in total the criterion for a string is fulfilled or not. +For example: The string must start with the string *prefix* and must also contain either the string *substring1* or the string *substring2* +but must also **not** end with the string *suffix*. + +This method provides a bunch of predefined filters that can be used singly or combined to come to a final conclusion if the string fulfils all criteria or not. + +The filters are divided into three different types: + +1. Filters that are interpreted as raw strings (called 'standard filters'; no wild cards supported) +2. Filters that are interpreted as regular expressions (called 'regular expression based filters'; the syntax of regular expressions has to be considered) +3. Boolean switches (e.g. indicating if also an empty string is accepted or not) + +The input string might contain leading and trailing blanks and tabs. This kind of horizontal space is removed from the input string +before the standard filters start their work (except the regular expression based filters). + +The regular expression based filters consider the original input string (including the leading and trailing space). + +The outcome is that in case of the leading and trailing space shall be part of the criterion, the regular expression based filters can be used only. + +It is possible to decide if the standard filters shall work case sensitive or not. This decision has no effect on the regular expression based filters. + +The regular expression based filters always work with the original input string that is not modified in any way. + +Except the regular expression based filters it is possible to provide more than one string for every standard filter (must be a semikolon separated list in this case). +A semicolon that shall be part of the search string, has to be masked in this way: ``\;``. + +This method returns a boolean value that is ``True`` in case of all criteria are fulfilled, and ``False`` in case of some or all of them are not fulfilled. + +The default value for all filters is ``None`` (except ``bSkipBlankStrings``). In case of a filter value is ``None`` this filter has no influence on the result. + +In case of all filters are ``None`` (default) the return value is ``True`` (except the string itself is ``None`` +or the string is empty and ``bSkipBlankStrings`` is ``True``). + +In case of the string is ``None``, the return value is ``False``, because nothing concrete can be done with ``None`` strings. + +Internally every filter has his own individual acknowledge that indicates if the criterion of this filter is fulfilled or not. + +The meaning of *criterion fulfilled* of a filter is that the filter supports the final return value ``bAck`` of this method with ``True``. + +The final return value ``bAck`` of this method is a logical join (``AND``) of all individual acknowledges (except ``bSkipBlankStrings`` and ``sComment``; +in case of their criteria are **not** fulfilled, immediately ``False`` is returned). + +Summarized: + +* Filters are used to define *criteria* +* The return value of this method provides the *conclusion* - indicating if all criteria are fulfilled or not + +The following filters are available: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**bSkipBlankStrings** + + * Like already mentioned above leading and trailing spaces are removed from the input string at the beginning + * In case of the result is an empty string and ``bSkipBlankStrings`` is ``True``, the method immediately returns ``False`` + and all other filters are ignored + +**sComment** + + * In case of the input string starts with the string ``sComment``, the method immediately returns ``False`` + and all other filters are ignored + * Leading blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * The idea behind this decision is: Ignore a string that is commented out + +**sStartsWith** + + * The criterion of this filter is fulfilled in case of the input string starts with the string ``sStartsWith`` + * Leading blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +**sEndsWith** + + * The criterion of this filter is fulfilled in case of the input string ends with the string ``sEndsWith`` + * Trailing blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +**sStartsNotWith** + + * The criterion of this filter is fulfilled in case of the input string does **not** start with the string ``sStartsNotWith`` + * Leading blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +**sEndsNotWith** + + * The criterion of this filter is fulfilled in case of the input string does **not** end with the string ``sEndsNotWith`` + * Trailing blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +**sContains** + + * The criterion of this filter is fulfilled in case of the input string contains the string ``sContains`` at any position + * Leading and trailing blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``OR``) + +**sContainsNot** + + * The criterion of this filter is fulfilled in case of the input string does **not** contain the string ``sContainsNot`` at any position + * Leading and trailing blanks within the input string have no effect + * The decision also depends on ``bCaseSensitive`` + * More than one string can be provided (semicolon separated; logical join: ``AND``) + +**sInclRegEx** + + * *Include* filter based on regular expressions (consider the syntax of regular expressions!) + * The criterion of this filter is fulfilled in case of the regular expression ``sInclRegEx`` matches the input string + * Leading and trailing blanks within the input string are considered + * ``bCaseSensitive`` has no effect + * A semicolon separated list of several regular expressions is **not** supported + +**sExclRegEx** + + * *Exclude* filter based on regular expressions (consider the syntax of regular expressions!) + * The criterion of this filter is fulfilled in case of the regular expression ``sExclRegEx`` does **not** match the input string + * Leading and trailing blanks within the input string are considered + * ``bCaseSensitive`` has no effect + * A semicolon separated list of several regular expressions is **not** supported + +**Further arguments:** + +* ``sString`` + + / *Condition*: required / *Type*: str / + + The input string that has to be investigated. + +* ``bCaseSensitive`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + If ``True``, the standard filters work case sensitive, otherwise not. + +* ``bDebug`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True``, additional output is printed to console (e.g. the decision of every single filter), otherwise not. + +**Returns:** + +* ``bAck`` + + / *Type*: bool / + + Final statement about the input string ``sString`` after filter computation + +Examples: +~~~~~~~~~ + +1. Returns ``True``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = "Sp", + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = "beats", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + +2. Returns ``False``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = "Sp", + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = "minute", + sContains = "beats", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + +3. Returns ``True``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = "Beats", + sInclRegEx = None, + sExclRegEx = None) + +4. Returns ``True``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = r"\d{2}", + sExclRegEx = None) + +5. Returns ``False``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = "Speed", + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = r"\d{3}", + sExclRegEx = None) + +6. Returns ``False``: + +.. code:: python + + StringFilter(sString = "Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = "Speed", + sEndsWith = "minute", + sStartsNotWith = "speed", + sEndsNotWith = None, + sContains = "beats", + sContainsNot = None, + sInclRegEx = r"\d{2}", + sExclRegEx = r"\d{2}") + +7. Returns ``False``: + +.. code:: python + + StringFilter(sString = " ", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + +8. Returns ``False``: + +.. code:: python + + StringFilter(sString = "# Speed is 25 beats per minute", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = "#", + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = "beats", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + + +9. Returns ``False``: + +.. code:: python + + StringFilter(sString = " Alpha is not beta; and beta is not gamma ", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = " Alpha ", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + +Because blanks around search strings (here ``" Alpha "``) are considered, whereas the blanks around the input string are removed before computation. +Therefore ``" Alpha "`` cannot be found within the (shortened) input string. + + +10. This alternative solution returns ``True``: + +.. code:: python + + StringFilter(sString = " Alpha is not beta; and beta is not gamma ", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = None, + sContainsNot = None, + sInclRegEx = r"\s{3}Alpha", + sExclRegEx = None) + + +11. Returns ``True``: + +.. code:: python + + StringFilter(sString = "Alpha is not beta; and beta is not gamma", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = "beta; and", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + +The meaning of ``"beta; and"`` is: The criterion is fulfilled in case of either ``"beta"`` or ``" and"`` can be found. That's ``True`` in this example - but this +has nothing to do with the fact, that also this string ``"beta; and"`` can be found. Here the semikolon is a separator character and therefore part of the syntax. + +A semicolon that shall be part of the search string, has to be masked with '``\;``'! + +The meaning of ``"beta\; not"`` in the following example is: The criterion is fulfilled in case of ``"beta; not"`` can be found. + +That's **not** ``True``. Therefore the method returns ``False``: + +.. code:: python + + StringFilter(sString = "Alpha is not beta; and beta is not gamma", + bCaseSensitive = True, + bSkipBlankStrings = True, + sComment = None, + sStartsWith = None, + sEndsWith = None, + sStartsNotWith = None, + sEndsNotWith = None, + sContains = r"beta\; not", + sContainsNot = None, + sInclRegEx = None, + sExclRegEx = None) + """ + + if sString is None: + return False # hard coded here; no separate filter for that decision + + # The original string 'sString' is used by regular expression filters sInclRegEx and sExclRegEx. + # The stripped string 'sStringStripped' is used by all other filters. + sStringStripped = sString.strip(" \t\r\n") + + # -- skipping blank strings or strings commented out; other filters will not be considered any more in this case + + if bSkipBlankStrings is True: + if sStringStripped == "": + return False + + if sComment is not None: + if sComment != "": + if bCaseSensitive is True: + if sStringStripped.startswith(sComment) is True: + return False + else: + if sStringStripped.upper().startswith(sComment.upper()) is True: + return False + + # -- consider further filters + # + # No filter set (= no criteria defined) => use this string (bAck is True). + # + # At least one filter set (except sExclRegEx), at least one set filter fits (except sExclRegEx) => use this string. + # Filter sExclRegEx is set and fits => skip this string (final veto). + # At least one filter does not fit (except sExclRegEx) => skip this string. + # + # All filters (except sExclRegEx) are include filter (bAck is True in case of all set filters fit, also the 'not' filters) + # The filter sExclRegEx is an exclude filter and has final veto right (can revoke the True from other filters). + # + # All filters (except sInclRegEx and sExclRegEx) are handled as 'raw strings': no wild cards, just strings, considering bCaseSensitive. + # The filters sInclRegEx and sExclRegEx are handled as regular expressions; bCaseSensitive is not considered here. + + # -- filter specific flags (containing the names of the criteria within their names) + bStartsWith = None + bEndsWith = None + bStartsNotWith = None + bEndsNotWith = None + bContains = None + bContainsNot = None + bInclRegEx = None + bExclRegEx = None + + # Meaning: + # - Flag is None : filter not set => filter has no effect + # - Flag is True : filter set => result: use the input string (from this single filter flag point of view) + # - Flag is False: filter set => result: do not use the input string (from this single filter flag point of view) + # The results of all flags will be merged at the end of this function to one final conclusion to use the input string + # (bAck is True) or not (bAck is False). + # Logical join between all set filters: AND + + # substitute for the masked filter separator '\n' (hopefully the input string does not contain this substitute) + sSeparatorSubstitute = "#|S#|E#|P#|A#|R#|A#|T#|O#|R#" + + # -- filter: starts with + # > several filter strings possible (separated by semicolon; logical join: OR) + if sStartsWith is not None: + if sStartsWith != "": + sStartsWithModified = sStartsWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listStartsWith = [] + if sStartsWith.find(";") >= 0: + listParts = sStartsWithModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + listStartsWith.append(sPart) + else: + sStartsWithModified = sStartsWith.replace(r"\;", ";") # convert to unmasked version + listStartsWith.append(sStartsWithModified) + + bStartsWith = False + for sStartsWith in listStartsWith: + if bCaseSensitive is True: + if sStringStripped.startswith(sStartsWith) is True: + bStartsWith = True + break + else: + if sStringStripped.upper().startswith(sStartsWith.upper()) is True: + bStartsWith = True + break + + # -- filter: ends with + # > several filter strings possible (separated by semicolon; logical join: OR) + if sEndsWith is not None: + if sEndsWith != "": + sEndsWithModified = sEndsWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listEndsWith = [] + if sEndsWith.find(";") >= 0: + listParts = sEndsWithModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + listEndsWith.append(sPart) + else: + sEndsWithModified = sEndsWith.replace(r"\;", ";") # convert to unmasked version + listEndsWith.append(sEndsWithModified) + + bEndsWith = False + for sEndsWith in listEndsWith: + if bCaseSensitive is True: + if sStringStripped.endswith(sEndsWith) is True: + bEndsWith = True + break + else: + if sStringStripped.upper().endswith(sEndsWith.upper()) is True: + bEndsWith = True + break + + # -- filter: starts not with + # > several filter strings possible (separated by semicolon; logical join: AND) + if sStartsNotWith is not None: + if sStartsNotWith != "": + sStartsNotWithModified = sStartsNotWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listStartsNotWith = [] + if sStartsNotWith.find(";") >= 0: + listParts = sStartsNotWithModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + listStartsNotWith.append(sPart) + else: + sStartsNotWithModified = sStartsNotWith.replace(r"\;", ";") # convert to unmasked version + listStartsNotWith.append(sStartsNotWithModified) + + bStartsNotWith = True + for sStartsNotWith in listStartsNotWith: + if bCaseSensitive is True: + if sStringStripped.startswith(sStartsNotWith) is True: + bStartsNotWith = False + break + else: + if sStringStripped.upper().startswith(sStartsNotWith.upper()) is True: + bStartsNotWith = False + break + + # -- filter: ends not with + # > several filter strings possible (separated by semicolon; logical join: AND) + if sEndsNotWith is not None: + if sEndsNotWith != "": + sEndsNotWithModified = sEndsNotWith.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listEndsNotWith = [] + if sEndsNotWith.find(";") >= 0: + listParts = sEndsNotWithModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + listEndsNotWith.append(sPart) + else: + sEndsNotWithModified = sEndsNotWith.replace(r"\;", ";") # convert to unmasked version + listEndsNotWith.append(sEndsNotWithModified) + + bEndsNotWith = True + for sEndsNotWith in listEndsNotWith: + if bCaseSensitive is True: + if sStringStripped.endswith(sEndsNotWith) is True: + bEndsNotWith = False + break + else: + if sStringStripped.upper().endswith(sEndsNotWith.upper()) is True: + bEndsNotWith = False + break + + # -- filter: contains + # > several filter strings possible (separated by semicolon; logical join: OR) + if sContains is not None: + if sContains != "": + sContainsModified = sContains.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listContains = [] + if sContainsModified.find(";") >= 0: + listParts = sContainsModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + print(f"- Part: '{sPart}'") + listContains.append(sPart) + else: + sContainsModified = sContains.replace(r"\;", ";") # convert to unmasked version + listContains.append(sContainsModified) + + bContains = False + for sContains in listContains: + if bCaseSensitive is True: + if sStringStripped.find(sContains) >= 0: + bContains = True + break + else: + if sStringStripped.upper().find(sContains.upper()) >= 0: + bContains = True + break + + # -- filter: contains not + # > several filter strings possible (separated by semicolon; logical join: AND) + if sContainsNot is not None: + if sContainsNot != "": + sContainsNotModified = sContainsNot.replace(r"\;", sSeparatorSubstitute) # replace the masked separator by a substitute separator + listContainsNot = [] + if sContainsNot.find(";") >= 0: + listParts = sContainsNotModified.split(";") + for sPart in listParts: + sPart = sPart.replace(sSeparatorSubstitute , ";") # recover the original version + listContainsNot.append(sPart) + else: + sContainsNotModified = sContainsNot.replace(r"\;", ";") # convert to unmasked version + listContainsNot.append(sContainsNotModified) + + bContainsNot = True + for sContainsNot in listContainsNot: + if bCaseSensitive is True: + if sStringStripped.find(sContainsNot) >= 0: + bContainsNot = False + break + else: + if sStringStripped.upper().find(sContainsNot.upper()) >= 0: + bContainsNot = False + break + + # -- filter: sInclRegEx + # > (take care to mask special characters that are part of the syntax of regular expressions!) + # > bCaseSensitive not considered here + if sInclRegEx is not None: + if sInclRegEx != "": + bInclRegEx = False + if re.search(sInclRegEx, sString) is not None: + bInclRegEx = True + + # -- last filter: sExclRegEx (final veto right) + # > (take care to mask special characters that are part of the syntax of regular expressions!) + # > bCaseSensitive not considered here + if sExclRegEx is not None: + if sExclRegEx != "": + bExclRegEx = True + if re.search(sExclRegEx, sString) is not None: + bExclRegEx = False + + # -- debug info + if bDebug is True: + print("\n* [sString] : '" + str(sString) + "'\n") + print(" -> [bStartsWith] : '" + str(bStartsWith) + "'") + print(" -> [bEndsWith] : '" + str(bEndsWith) + "'") + print(" -> [bStartsNotWith] : '" + str(bStartsNotWith) + "'") + print(" -> [bEndsNotWith] : '" + str(bEndsNotWith) + "'") + print(" -> [bContains] : '" + str(bContains) + "'") + print(" -> [bContainsNot] : '" + str(bContainsNot) + "'") + print(" -> [bInclRegEx] : '" + str(bInclRegEx) + "'") + print(" -> [bExclRegEx] : '" + str(bExclRegEx) + "'\n") + + # -- final conclusion (AND condition between filters) + + listDecisions = [] + listDecisions.append(bStartsWith) + listDecisions.append(bEndsWith) + listDecisions.append(bStartsNotWith) + listDecisions.append(bEndsNotWith) + listDecisions.append(bContains) + listDecisions.append(bContainsNot) + listDecisions.append(bInclRegEx) + listDecisions.append(bExclRegEx) + + bAck = False # initial + + # -- 1.) no filter set (all None) + nCntDecisions = 0 + for bDecision in listDecisions: + if bDecision is None: + nCntDecisions = nCntDecisions + 1 + if nCntDecisions == len(listDecisions): + bAck = True + if bDebug is True: + print(" > case [1] - bAck: " + str(bAck)) + + # -- 2.) final veto from exclude filter + if bExclRegEx is False: + bAck = False + if bDebug is True: + print(" > case [2] - bAck: " + str(bAck)) + + # -- 3.) exclude filter not set; decision only made by other filters (include) + if bExclRegEx is None: + bAck = True + for bDecision in listDecisions: + if bDecision is False: + bAck = False + break + if bDebug is True: + print(" > case [3] - bAck: " + str(bAck)) + + # -- 4.) exclude filter is True (only relevant in case of all other filters are not set; otherwise decision only made by other filters (include)) + if bExclRegEx is True: + if ( (bStartsWith is None) and + (bEndsWith is None) and + (bStartsNotWith is None) and + (bEndsNotWith is None) and + (bContains is None) and + (bContainsNot is None) and + (bInclRegEx is None) ): + bAck = True + if bDebug is True: + print(" > case [4.1] - bAck: " + str(bAck)) + else: + bAck = True + for bDecision in listDecisions: + if bDecision is False: + bAck = False + break + if bDebug is True: + print(" > case [4.2] - bAck: " + str(bAck)) + + if bDebug is True: + print() + + return bAck + + # eof def StringFilter(...) + + # -------------------------------------------------------------------------------------------------------------- + #TM*** + + def FormatResult(sMethod="", bSuccess=True, sResult=""): + """ +Formats the result string ``sResult`` depending on ``bSuccess``: + +* ``bSuccess`` is ``True`` indicates *success* +* ``bSuccess`` is ``False`` indicates an *error* +* ``bSuccess`` is ``None`` indicates an *exception* + +Additionally the name of the method that causes the result, can be provided (*optional*). +This is useful for debugging. + +**Arguments:** + +* ``sMethod`` + + / *Condition*: optional / *Type*: str / *Default*: (empty string) / + + Name of the method that causes the result. + +* ``bSuccess`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + Indicates if the computation of the method ``sMethod`` was successful or not. + +* ``sResult`` + + / *Condition*: optional / *Type*: str / *Default*: (empty string) / + + The result of the computation of the method ``sMethod``. + +**Returns:** + +* ``sResult`` + + / *Type*: str / + + The formatted result string. + """ + + if sMethod is None: + sMethod = str(sMethod) + if sResult is None: + sResult = str(sResult) + if bSuccess is True: + if sMethod != "": + sResult = f"[{sMethod}] : {sResult}" + elif bSuccess is False: + sError = "!!! ERROR !!!" + if sMethod != "": + sResult = f"{sError}\n[{sMethod}] : {sResult}" + else: + sResult = f"{sError}\n{sResult}" + else: + sException = "!!! EXCEPTION !!!" + if sMethod != "": + sResult = f"{sException}\n[{sMethod}] : {sResult}" + else: + sResult = f"{sException}\n{sResult}" + return sResult + + # eof def FormatResult(sMethod="", bSuccess=True, sResult=""): + + # -------------------------------------------------------------------------------------------------------------- + #TM*** + + # - make the methods static + + NormalizePath = staticmethod(NormalizePath) + DetectParentPath = staticmethod(DetectParentPath) + StringFilter = staticmethod(StringFilter) + FormatResult = staticmethod(FormatResult) + +# eof class CString(object): + +# ************************************************************************************************************** + + + diff --git a/additions/PythonExtensionsCollection/String/__init__.py b/additions/PythonExtensionsCollection/String/__init__.py new file mode 100644 index 00000000..958420af --- /dev/null +++ b/additions/PythonExtensionsCollection/String/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/additions/PythonExtensionsCollection/Utils/CUtils.py b/additions/PythonExtensionsCollection/Utils/CUtils.py new file mode 100644 index 00000000..20447501 --- /dev/null +++ b/additions/PythonExtensionsCollection/Utils/CUtils.py @@ -0,0 +1,374 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# CUtils.py +# +# XC-CT/ECA3-Queckenstedt +# +# 20.05.2022 +# +# ************************************************************************************************************** + +# -- import standard Python modules +from dotdict import dotdict + +# ************************************************************************************************************** +# wrapper +# ************************************************************************************************************** + +def PrettyPrint(oData=None, hOutputFile=None, bToConsole=True, nIndent=0, sPrefix=None, bHexFormat=False): + """ +Wrapper function to create and use a ``CTypePrint`` object. This wrapper function is responsible for +printing out the content to console and to a file (depending on input parameter). + +The content itself is prepared by the method ``TypePrint`` of class ``CTypePrint``. This happens ``PrettyPrint`` internally. + +The idea behind the ``PrettyPrint`` function is to resolve also the content of composite data types and provide for every parameter inside: + +* the type +* the total number of elements inside (e.g. the number of keys inside a dictionary) +* the counter number of the current element +* the value + +Example call: + +.. code:: python + + PrettyPrint(oData) + +(*with oData is a Python variable of any type*) + +The output can e.g. look like this: + +.. code:: python + + [DICT] (3/1) > {K1} [STR] : 'Val1' + [DICT] (3/2) > {K2} [LIST] (4/1) > [INT] : 1 + [DICT] (3/2) > {K2} [LIST] (4/2) > [STR] : 'A' + [DICT] (3/2) > {K2} [LIST] (4/3) > [INT] : 2 + [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/1) > [INT] : 9 + [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/2) > [STR] : 'Z' + [DICT] (3/3) > {K3} [INT] : 5 + +Every line of output has to be interpreted strictly from left to right. + +For example the meaning of the fifth line of output + +.. code:: python + + [DICT] (3/2) > {K2} [LIST] (4/4) > [TUPLE] (2/1) > [INT] : 9 + +is: + +* The type of input parameter (``oData``) is ``dict`` +* The dictionary contains 3 keys +* The current line gives information about the second key of the dictionary +* The name of the second key is 'K2' +* The value of the second key is of type ``list`` +* The list contains 4 elements +* The current line gives information about the fourth element of the list +* The fourth element of the list is of type ``tuple`` +* The tuple contains 2 elements +* The current line gives information about the first element of the tuple +* The first element of the tuple is of type ``int`` and has the value 9 + +Types are encapsulated in square brackets, counter in round brackets and key names are encapsulated in curly brackets. + +**Arguments:** + +* ``oData`` + + / *Condition*: required / *Type*: (*any Python data type*) / + + A variable of any Python data type. + +* ``hOutputFile`` + + / *Condition*: optional / *Type*: file handle / *Default*: None / + + If handle is not ``None`` the content is written to this file, otherwise not. + +* ``bToConsole`` + + / *Condition*: optional / *Type*: bool / *Default*: True / + + If ``True`` the content is written to console, otherwise not. + +* ``nIndent`` + + / *Condition*: optional / *Type*: int / *Default*: 0 / + + Sets the number of additional blanks at the beginning of every line of output (indentation). + +* ``sPrefix`` + + / *Condition*: optional / *Type*: str / *Default*: None / + + Sets a prefix string that is added at the beginning of every line of output. + +* ``bHexFormat`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True`` the output is printed in hexadecimal format (but valid for strings only). + +**Returns:** + +* ``listOutLines`` (*list*) + + / *Type*: list / + + List of lines containing the prepared output + """ + + oTypePrint = CTypePrint() + listOutLines = oTypePrint.TypePrint(oData, bHexFormat) + + listReturned = [] + for sLine in listOutLines: + # if requested add indentation and prefix + sLineOut = "" + if sPrefix is not None: + sLineOut = nIndent*" " + sPrefix + " " + sLine + else: + sLineOut = nIndent*" " + sLine + listReturned.append(sLineOut) + + if hOutputFile is not None: + hOutputFile.write(sLineOut + "\n") + if bToConsole is True: + print(sLineOut) + + return listReturned + +# eof def PrettyPrint(oData=None, hOutputFile=None, bToConsole=True, nIndent=0, sPrefix=None, bHexFormat=False): + +# -------------------------------------------------------------------------------------------------------------- +# TM*** + +class CTypePrint(object): + """ +The class ``CTypePrint`` provides a method (``TypePrint``) to compute the following data: + +* the type +* the total number of elements inside (e.g. the number of keys inside a dictionary) +* the counter number of the current element +* the value + +of simple and composite data types. + +The call of this method is encapsulated within the function ``PrettyPrint`` inside this module. + """ + def __init__(self): + self.listGlobalPrefixes = [] + self.listOutLines = [] + + def __del__(self): + pass + + def _ToHex(self, sString=None): + if ( (sString is None) or (sString == "") ): + return sString + listHex = [] + for sChar in sString: + listHex.append(hex(ord(sChar))) + sStringHex = " ".join(listHex) + return sStringHex + + def TypePrint(self, oData=None, bHexFormat=False): + """ +The method ``TypePrint`` computes details about the input variable ``oData``. + +**Arguments:** + +* ``oData`` + + / *Condition*: required / *Type*: any Python data type / + + Python variable of any data type. + +* ``bHexFormat`` + + / *Condition*: optional / *Type*: bool / *Default*: False / + + If ``True`` the output is provide in hexadecimal format. + +**Returns:** + +* ``listOutLines`` + + / *Type*: list / + + List of lines containing the resolved content of ``oData``. + """ + + if oData is None: + sLocalPrefix = "[NONE]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) + self.listOutLines.append(sOut.strip()) + + elif type(oData) == int: + sLocalPrefix = "[INT]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) + self.listOutLines.append(sOut.strip()) + + elif type(oData) == float: + sLocalPrefix = "[FLOAT]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) + self.listOutLines.append(sOut.strip()) + + elif type(oData) == bool: + sLocalPrefix = "[BOOL]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : " + str(oData) + self.listOutLines.append(sOut.strip()) + + elif type(oData) == str: + sLocalPrefix = "[STR]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sData = str(oData) + if bHexFormat is True: + sData = self._ToHex(sData) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : '" + sData + "'" + self.listOutLines.append(sOut.strip()) + + elif type(oData) == list: + nNrOfElements = len(oData) + if nNrOfElements == 0: + # -- indicate empty list + sLocalPrefix = "[LIST]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : []" + self.listOutLines.append(sOut.strip()) + else: + # -- list elements of list + self.listGlobalPrefixes.append("[LIST]") + nCnt = 0 + for oElement in oData: + nCnt = nCnt + 1 + sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" + self.listGlobalPrefixes.append(sCnt) + self.TypePrint(oElement, bHexFormat) # >>>> recursion + del self.listGlobalPrefixes[-1] # remove prefix count + del self.listGlobalPrefixes[-1] # remove prefix name + + elif type(oData) == tuple: + nNrOfElements = len(oData) + if nNrOfElements == 0: + # -- indicate empty tuple + sLocalPrefix = "[TUPLE]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : ()" + self.listOutLines.append(sOut.strip()) + else: + # -- list elements of tuple + self.listGlobalPrefixes.append("[TUPLE]") + nCnt = 0 + for oElement in oData: + nCnt = nCnt + 1 + sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" + self.listGlobalPrefixes.append(sCnt) + self.TypePrint(oElement, bHexFormat) # >>>> recursion + del self.listGlobalPrefixes[-1] # remove prefix count + del self.listGlobalPrefixes[-1] # remove prefix name + + elif type(oData) == set: + nNrOfElements = len(oData) + if nNrOfElements == 0: + # -- indicate empty set + sLocalPrefix = "[SET]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : ()" + self.listOutLines.append(sOut.strip()) + else: + # -- list elements of set + self.listGlobalPrefixes.append("[SET]") + nCnt = 0 + for oElement in oData: + nCnt = nCnt + 1 + sCnt = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") >" + self.listGlobalPrefixes.append(sCnt) + self.TypePrint(oElement, bHexFormat) # >>>> recursion + del self.listGlobalPrefixes[-1] # remove prefix count + del self.listGlobalPrefixes[-1] # remove prefix name + + elif type(oData) == dict: + nNrOfElements = len(oData) + if nNrOfElements == 0: + # -- indicate empty dictionary + sLocalPrefix = "[DICT]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : {}" + self.listOutLines.append(sOut.strip()) + else: + # -- list elements of dictionary + self.listGlobalPrefixes.append("[DICT]") + nCnt = 0 + listKeys = list(oData.keys()) + for sKey in listKeys: + nCnt = nCnt + 1 + oValue = oData[sKey] + sCntAndKey = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") > {" + str(sKey) + "}" + self.listGlobalPrefixes.append(sCntAndKey) + self.TypePrint(oValue, bHexFormat) # >>>> recursion + del self.listGlobalPrefixes[-1] # remove prefix count + del self.listGlobalPrefixes[-1] # remove prefix name + + # elif type(oData) == dotdict: + elif ( (type(oData) == dotdict) or (str(type(oData)) == "") ): + nNrOfElements = len(oData) + if nNrOfElements == 0: + # -- indicate empty dot dictionary + sLocalPrefix = "[DOTDICT]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : {}" + self.listOutLines.append(sOut.strip()) + else: + # -- list elements of dot dictionary + self.listGlobalPrefixes.append("[DOTDICT]") + nCnt = 0 + listKeys = list(oData.keys()) + for sKey in listKeys: + nCnt = nCnt + 1 + oValue = oData[sKey] + sCntAndKey = "(" + str(nNrOfElements) + "/" + str(nCnt) + ") > {" + str(sKey) + "}" + self.listGlobalPrefixes.append(sCntAndKey) + self.TypePrint(oValue, bHexFormat) # >>>> recursion + del self.listGlobalPrefixes[-1] # remove prefix count + del self.listGlobalPrefixes[-1] # remove prefix name + + else: + sLocalPrefix = "[" + str(type(oData)) + "]" + sGlobalPrefix = " ".join(self.listGlobalPrefixes) + sData = str(oData) + if bHexFormat is True: + sData = self._ToHex(sData) + sOut = sGlobalPrefix + " " + sLocalPrefix + " : '" + sData + "'" + self.listOutLines.append(sOut.strip()) + + return self.listOutLines + + # eof def TypePrint(...): + +# eof class CTypePrint(): + +# ************************************************************************************************************** + diff --git a/additions/PythonExtensionsCollection/Utils/__init__.py b/additions/PythonExtensionsCollection/Utils/__init__.py new file mode 100644 index 00000000..958420af --- /dev/null +++ b/additions/PythonExtensionsCollection/Utils/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/additions/PythonExtensionsCollection/__init__.py b/additions/PythonExtensionsCollection/__init__.py new file mode 100644 index 00000000..958420af --- /dev/null +++ b/additions/PythonExtensionsCollection/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/additions/PythonExtensionsCollection/version.py b/additions/PythonExtensionsCollection/version.py new file mode 100644 index 00000000..b5b224a4 --- /dev/null +++ b/additions/PythonExtensionsCollection/version.py @@ -0,0 +1,23 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# Version and date of PythonExtensionsCollection +# +VERSION = "0.8.0" +VERSION_DATE = "28.06.2022" + From fe0b0ba63b11303c9cf74c62e60b8980b227dd3d Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 15:59:16 +0700 Subject: [PATCH 09/18] Revert "ignore build stuff and logfiles" This reverts commit 33a0826b12d021882b8675431220aa447289f8e8. --- .gitignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 58c2bad0..ba0430d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -__pycache__/ -build/ -aiotestlogfiles/ -logfiles/ \ No newline at end of file +__pycache__/ \ No newline at end of file From 77bf8c90667ca1348a3aaf61e6cb8a1905b06026 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 15:59:32 +0700 Subject: [PATCH 10/18] Revert "rename test file so that pytest can collect it for execution" This reverts commit f855691e2cc3c49fd2c87bcd4aefebe51846c116. --- .../{test_jsonpreprocessor.py => jsonpreprocessor_unittest.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename atest/jsonpreprocessor/{test_jsonpreprocessor.py => jsonpreprocessor_unittest.py} (100%) diff --git a/atest/jsonpreprocessor/test_jsonpreprocessor.py b/atest/jsonpreprocessor/jsonpreprocessor_unittest.py similarity index 100% rename from atest/jsonpreprocessor/test_jsonpreprocessor.py rename to atest/jsonpreprocessor/jsonpreprocessor_unittest.py From e7fb189a97eef2c43a965a849a678f6eb55e60ef Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 15:59:47 +0700 Subject: [PATCH 11/18] Revert "tiny fixes" This reverts commit 41708dd95fc35a19d8214ee2b19a1bf6f5be2350. --- README.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.rst b/README.rst index a27af4ab..059d94b5 100644 --- a/README.rst +++ b/README.rst @@ -35,16 +35,9 @@ How to install 1. Installation via PyPi (recommended for users) -<<<<<<< HEAD .. code:: pip install JsonPreprocessor -======= -.. code:: - - setup.py build will build the package underneath 'build/' - setup.py install will install the package ->>>>>>> 6218d44 (tiny fixes) `JsonPreprocessor in PyPi `_ From bf92c6e8de2311f0fe1f162e1c67bcf3a9936c75 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 16:00:08 +0700 Subject: [PATCH 12/18] Revert "Replaced executerobottest.py by executepytest.py (because of planned rework of the atest)" This reverts commit 89ac8aeec319a59ae9550e440af17e009aa28bc0. --- atest/executerobottest.py | 176 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 atest/executerobottest.py diff --git a/atest/executerobottest.py b/atest/executerobottest.py new file mode 100644 index 00000000..2e5fdd3a --- /dev/null +++ b/atest/executerobottest.py @@ -0,0 +1,176 @@ +# ************************************************************************************************************** +# +# Copyright 2020-2022 Robert Bosch GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ************************************************************************************************************** +# +# executerobottest.py +# +# XC-CT/ECA3-Queckenstedt +# +# Executes robot tests recursively in current folder. +# Log file can be set in command line. If not, default log is written. +# Additional command line for involved framework can also be set in command line (of this script). +# +# -------------------------------------------------------------------------------------------------------------- +# +# 09.11.2022 +# +# -------------------------------------------------------------------------------------------------------------- + +import os, sys, platform, shlex, subprocess, shutil, argparse + +import colorama as col + +from PythonExtensionsCollection.String.CString import CString +from PythonExtensionsCollection.File.CFile import CFile +from PythonExtensionsCollection.Folder.CFolder import CFolder + +col.init(autoreset=True) + +COLBR = col.Style.BRIGHT + col.Fore.RED +COLBY = col.Style.BRIGHT + col.Fore.YELLOW +COLBG = col.Style.BRIGHT + col.Fore.GREEN + +SUCCESS = 0 +ERROR = 1 + +# -------------------------------------------------------------------------------------------------------------- + +def printerror(sMsg): + sys.stderr.write(COLBR + f"Error: {sMsg}!\n") + +def printexception(sMsg): + sys.stderr.write(COLBR + f"Exception: {sMsg}!\n") + +# -------------------------------------------------------------------------------------------------------------- + +# -- some informations about the environment of this script + +sThisScript = sys.argv[0] +sThisScript = CString.NormalizePath(sThisScript) +sThisScriptPath = os.path.dirname(sThisScript) +sThisScriptName = os.path.basename(sThisScript) + +sOSName = os.name +sPlatformSystem = platform.system() +sPythonPath = CString.NormalizePath(os.path.dirname(sys.executable)) +sPython = CString.NormalizePath(sys.executable) +sPythonVersion = sys.version + +if sPlatformSystem == "Windows": + # nothing specific to do + pass +elif sPlatformSystem == "Linux": + # nothing specific to do + pass +else: + bSuccess = False + sResult = f"Operating system {sPlatformSystem} ({sOSName}) not supported" + printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) + sys.exit(ERROR) + +print() +print(f"{sThisScriptName} is running under {sPlatformSystem} ({sOSName})") +print() + +# -- parse the command line of this script (optional path and name of robot xml log file) + +oCmdLineParser = argparse.ArgumentParser() +oCmdLineParser.add_argument('--logfile', type=str, help='Path and name of XML log file (optional).') +oCmdLineParser.add_argument('--robotcommandline', type=str, help='Command line for RobotFramework AIO (optional).') +oCmdLineArgs = oCmdLineParser.parse_args() + +sLogFile = None +if oCmdLineArgs.logfile is not None: + sLogFile = CString.NormalizePath(oCmdLineArgs.logfile, sReferencePathAbs=sThisScriptPath) +else: + # default log + sLogFile = f"{sThisScriptPath}/logfiles/RobotTestLog.xml" + +sRobotCommandLine = None +if oCmdLineArgs.robotcommandline is not None: + sRobotCommandLine = oCmdLineArgs.robotcommandline + +# -- create the log file folder + +oLogFile = CFile(sLogFile) +dLogFileInfo = oLogFile.GetFileInfo() +sLogFilePath = dLogFileInfo['sFilePath'] +sLogFileName = dLogFileInfo['sFileName'] +sLogFileNameOnly = dLogFileInfo['sFileNameOnly'] + +oLogFilePath = CFolder(sLogFilePath) +bSuccess, sResult = oLogFilePath.Create(bOverwrite=False, bRecursive=True) +del oLogFilePath +if bSuccess is not True: + printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) + sys.exit(ERROR) +print(sResult) +print() + +# -- prepare the command line for the test execution + +listCmdLineParts = [] +listCmdLineParts.append(f"\"{sPython}\"") +listCmdLineParts.append("-m robot") +if sRobotCommandLine is not None: + listCmdLineParts.append(f"{sRobotCommandLine}") +listCmdLineParts.append(f"-d \"{sLogFilePath}\"") +listCmdLineParts.append(f"-o \"{sLogFileName}\"") +listCmdLineParts.append(f"-l \"{sLogFileNameOnly}_log.html\"") +listCmdLineParts.append(f"-r \"{sLogFileNameOnly}_report.html\"") +listCmdLineParts.append(f"-b \"{sLogFileNameOnly}.log\"") +listCmdLineParts.append(f"\"{sThisScriptPath}\"") +sCmdLine = " ".join(listCmdLineParts) +del listCmdLineParts + +# -- execute the tests + +print(f"Now executing command line:\n{sCmdLine}") +print() + +listCmdLineParts = shlex.split(sCmdLine) + +nReturn = ERROR +try: + nReturn = subprocess.call(listCmdLineParts) + print() + print(f"[{sThisScriptName}] : Subprocess ROBOT returned {nReturn}") +except Exception as ex: + print() + printexception(str(ex)) + print() + sys.exit(ERROR) +print() + +if nReturn == SUCCESS: + print(f"Test results in '{sLogFile}'") + print() + print(COLBG + f"{sThisScriptName} done") +else: + printerror(f"[{sThisScriptName}] : Subprocess ROBOT has not returned expected value {SUCCESS}") + nReturn = -nReturn + +print() + +# nReturn: +# > 0 : internal error of this script +# < 0 : return value (!= 0) from subprocess +# == 0 : no internal error of this script and no error from subprocess + +sys.exit(nReturn) + +# -------------------------------------------------------------------------------------------------------------- From 085cf1f8c5f681d6fdd5a79a746246cd321b4708 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 16:00:30 +0700 Subject: [PATCH 13/18] Revert "Added executerobottest.py to support the TestTrigger" This reverts commit 733e89364cd264aa93a556b8c253a1ec856006f9. --- atest/executerobottest.py | 176 -------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 atest/executerobottest.py diff --git a/atest/executerobottest.py b/atest/executerobottest.py deleted file mode 100644 index 2e5fdd3a..00000000 --- a/atest/executerobottest.py +++ /dev/null @@ -1,176 +0,0 @@ -# ************************************************************************************************************** -# -# Copyright 2020-2022 Robert Bosch GmbH -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# ************************************************************************************************************** -# -# executerobottest.py -# -# XC-CT/ECA3-Queckenstedt -# -# Executes robot tests recursively in current folder. -# Log file can be set in command line. If not, default log is written. -# Additional command line for involved framework can also be set in command line (of this script). -# -# -------------------------------------------------------------------------------------------------------------- -# -# 09.11.2022 -# -# -------------------------------------------------------------------------------------------------------------- - -import os, sys, platform, shlex, subprocess, shutil, argparse - -import colorama as col - -from PythonExtensionsCollection.String.CString import CString -from PythonExtensionsCollection.File.CFile import CFile -from PythonExtensionsCollection.Folder.CFolder import CFolder - -col.init(autoreset=True) - -COLBR = col.Style.BRIGHT + col.Fore.RED -COLBY = col.Style.BRIGHT + col.Fore.YELLOW -COLBG = col.Style.BRIGHT + col.Fore.GREEN - -SUCCESS = 0 -ERROR = 1 - -# -------------------------------------------------------------------------------------------------------------- - -def printerror(sMsg): - sys.stderr.write(COLBR + f"Error: {sMsg}!\n") - -def printexception(sMsg): - sys.stderr.write(COLBR + f"Exception: {sMsg}!\n") - -# -------------------------------------------------------------------------------------------------------------- - -# -- some informations about the environment of this script - -sThisScript = sys.argv[0] -sThisScript = CString.NormalizePath(sThisScript) -sThisScriptPath = os.path.dirname(sThisScript) -sThisScriptName = os.path.basename(sThisScript) - -sOSName = os.name -sPlatformSystem = platform.system() -sPythonPath = CString.NormalizePath(os.path.dirname(sys.executable)) -sPython = CString.NormalizePath(sys.executable) -sPythonVersion = sys.version - -if sPlatformSystem == "Windows": - # nothing specific to do - pass -elif sPlatformSystem == "Linux": - # nothing specific to do - pass -else: - bSuccess = False - sResult = f"Operating system {sPlatformSystem} ({sOSName}) not supported" - printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) - sys.exit(ERROR) - -print() -print(f"{sThisScriptName} is running under {sPlatformSystem} ({sOSName})") -print() - -# -- parse the command line of this script (optional path and name of robot xml log file) - -oCmdLineParser = argparse.ArgumentParser() -oCmdLineParser.add_argument('--logfile', type=str, help='Path and name of XML log file (optional).') -oCmdLineParser.add_argument('--robotcommandline', type=str, help='Command line for RobotFramework AIO (optional).') -oCmdLineArgs = oCmdLineParser.parse_args() - -sLogFile = None -if oCmdLineArgs.logfile is not None: - sLogFile = CString.NormalizePath(oCmdLineArgs.logfile, sReferencePathAbs=sThisScriptPath) -else: - # default log - sLogFile = f"{sThisScriptPath}/logfiles/RobotTestLog.xml" - -sRobotCommandLine = None -if oCmdLineArgs.robotcommandline is not None: - sRobotCommandLine = oCmdLineArgs.robotcommandline - -# -- create the log file folder - -oLogFile = CFile(sLogFile) -dLogFileInfo = oLogFile.GetFileInfo() -sLogFilePath = dLogFileInfo['sFilePath'] -sLogFileName = dLogFileInfo['sFileName'] -sLogFileNameOnly = dLogFileInfo['sFileNameOnly'] - -oLogFilePath = CFolder(sLogFilePath) -bSuccess, sResult = oLogFilePath.Create(bOverwrite=False, bRecursive=True) -del oLogFilePath -if bSuccess is not True: - printerror(CString.FormatResult(sThisScriptName, bSuccess, sResult)) - sys.exit(ERROR) -print(sResult) -print() - -# -- prepare the command line for the test execution - -listCmdLineParts = [] -listCmdLineParts.append(f"\"{sPython}\"") -listCmdLineParts.append("-m robot") -if sRobotCommandLine is not None: - listCmdLineParts.append(f"{sRobotCommandLine}") -listCmdLineParts.append(f"-d \"{sLogFilePath}\"") -listCmdLineParts.append(f"-o \"{sLogFileName}\"") -listCmdLineParts.append(f"-l \"{sLogFileNameOnly}_log.html\"") -listCmdLineParts.append(f"-r \"{sLogFileNameOnly}_report.html\"") -listCmdLineParts.append(f"-b \"{sLogFileNameOnly}.log\"") -listCmdLineParts.append(f"\"{sThisScriptPath}\"") -sCmdLine = " ".join(listCmdLineParts) -del listCmdLineParts - -# -- execute the tests - -print(f"Now executing command line:\n{sCmdLine}") -print() - -listCmdLineParts = shlex.split(sCmdLine) - -nReturn = ERROR -try: - nReturn = subprocess.call(listCmdLineParts) - print() - print(f"[{sThisScriptName}] : Subprocess ROBOT returned {nReturn}") -except Exception as ex: - print() - printexception(str(ex)) - print() - sys.exit(ERROR) -print() - -if nReturn == SUCCESS: - print(f"Test results in '{sLogFile}'") - print() - print(COLBG + f"{sThisScriptName} done") -else: - printerror(f"[{sThisScriptName}] : Subprocess ROBOT has not returned expected value {SUCCESS}") - nReturn = -nReturn - -print() - -# nReturn: -# > 0 : internal error of this script -# < 0 : return value (!= 0) from subprocess -# == 0 : no internal error of this script and no error from subprocess - -sys.exit(nReturn) - -# -------------------------------------------------------------------------------------------------------------- From 32ae51ebc241d271f2dd78acc202d83195662f49 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Mon, 6 Feb 2023 16:04:10 +0700 Subject: [PATCH 14/18] Add more unittest cases for utf8 format. --- .../jsonpreprocessor_unittest.py | 109 +++++++++++++++++- .../import_file_utf8_format_01.json | 27 +++++ .../import_file_utf8_format_02.json | 30 +++++ .../import_file_utf8_format_03.json | 30 +++++ .../import_file_utf8_format_04.json | 33 ++++++ .../08_utf8_encoding/utf8_format_01.json | 33 ++++++ .../08_utf8_encoding/utf8_format_02.json | 32 +++++ .../08_utf8_encoding/utf8_format_03.json | 35 ++++++ .../config/import/utf8_format_data.json | 23 ++++ .../config/import/utf8_format_data_01.json | 20 ++++ .../config/import/utf8_format_data_02.json | 23 ++++ .../config/import/utf8_format_data_03.json | 21 ++++ 12 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 atest/testdata/config/02_import_json/import_file_utf8_format_01.json create mode 100644 atest/testdata/config/02_import_json/import_file_utf8_format_02.json create mode 100644 atest/testdata/config/02_import_json/import_file_utf8_format_03.json create mode 100644 atest/testdata/config/02_import_json/import_file_utf8_format_04.json create mode 100644 atest/testdata/config/08_utf8_encoding/utf8_format_01.json create mode 100644 atest/testdata/config/08_utf8_encoding/utf8_format_02.json create mode 100644 atest/testdata/config/08_utf8_encoding/utf8_format_03.json create mode 100644 atest/testdata/config/import/utf8_format_data.json create mode 100644 atest/testdata/config/import/utf8_format_data_01.json create mode 100644 atest/testdata/config/import/utf8_format_data_02.json create mode 100644 atest/testdata/config/import/utf8_format_data_03.json diff --git a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py b/atest/jsonpreprocessor/jsonpreprocessor_unittest.py index 40a50b5c..4d6d0054 100644 --- a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py +++ b/atest/jsonpreprocessor/jsonpreprocessor_unittest.py @@ -375,4 +375,111 @@ def test_utf8_encoding(self): assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" - assert oJsonData['Chinese'] == "這是 UTF-8 自測" \ No newline at end of file + assert oJsonData['Chinese'] == "這是 UTF-8 自測" + + def test_utf8_encoding_both_key_and_value(self): + ''' + Test utf-8 encoding in both key and value in json configuration file. + ''' + sJsonfile = os.path.abspath("../testdata/config/08_utf8_encoding/utf8_format_02.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['Tiếng Đức'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Tiếng Việt'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['日本'] == "これは UTF-8 セルフテストです" + assert oJsonData['हिंदी'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['แบบไทย'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['한국인'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['中國人'] == "這是 UTF-8 自測" + + def test_utf8_encoding_imported_01(self): + ''' + Test utf-8 encoding file is imported to other normal json config file + ''' + sJsonfile = os.path.abspath("../testdata/config/02_import_json/import_file_utf8_format_01.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Vietnamese'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['Japanese'] == "これは UTF-8 セルフテストです" + assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['Chinese'] == "這是 UTF-8 自測" + + def test_utf8_encoding_imported_02(self): + ''' + Test utf-8 encoding file is imported to other utf8 json config file + ''' + sJsonfile = os.path.abspath("../testdata/config/02_import_json/import_file_utf8_format_02.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Vietnamese'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['Japanese'] == "これは UTF-8 セルフテストです" + assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['Chinese'] == "這是 UTF-8 自測" + + def test_utf8_encoding_imported_03(self): + ''' + Test utf-8 encoding file is imported and overried by utf8 values to other json config file + ''' + sJsonfile = os.path.abspath("../testdata/config/02_import_json/import_file_utf8_format_03.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Vietnamese'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['Japanese'] == "これは UTF-8 セルフテストです" + assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['Chinese'] == "這是 UTF-8 自測" + + def test_utf8_encoding_imported_04(self): + ''' + Test utf-8 encoding - Override utf8 data by normal data + ''' + sJsonfile = os.path.abspath("../testdata/config/02_import_json/import_file_utf8_format_04.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "This is German" + assert oJsonData['Vietnamese'] == 84 + assert oJsonData['Japanese'] == "Đây là tiếng Nhật" + assert oJsonData['Hindi'] == True + assert oJsonData['Thai'] == False + assert oJsonData['Korean'] == [1, 2, "List", 4, 5] + assert oJsonData['Chinese'] == None + + def test_utf8_encoding_imported_05(self): + ''' + Test utf-8 encoding - import normal json configuration file into utf8 file. + ''' + sJsonfile = os.path.abspath("../testdata/config/08_utf8_encoding/utf8_format_01.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['German'] == "Dies ist der UTF-8 SälfTest" + assert oJsonData['Vietnamese'] == "Đây là bản tự kiểm tra UTF-8" + assert oJsonData['Japanese'] == "これは UTF-8 セルフテストです" + assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" + assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" + assert oJsonData['Chinese'] == "這是 UTF-8 自測" + assert oJsonData['gPreprolIntParam'] == 1 + assert oJsonData['gPreproFloatParam'] == 1.332 + assert oJsonData['gPreproString'] == "This is a string" + assert oJsonData['gPreproStructure']['general'] == "general" + + def test_utf8_encoding_imported_06(self): + ''' + Test utf-8 encoding - Override parameters have utf8 format in keys. + ''' + sJsonfile = os.path.abspath("../testdata/config/08_utf8_encoding/utf8_format_03.json") + oJsonPreprocessor = CJsonPreprocessor(syntax="python") + oJsonData = oJsonPreprocessor.jsonLoad(sJsonfile) + assert oJsonData['utf8']['Tiếng Đức'] == "This is German" + assert oJsonData['utf8']['Tiếng Việt'] == 84 + assert oJsonData['utf8']['日本'] == 1.987 + assert oJsonData['utf8']['हिंदी'] == "นี่คือการทดสอบตัวเอง UTF-8" + assert oJsonData['utf8']['한국인'] == "1" \ No newline at end of file diff --git a/atest/testdata/config/02_import_json/import_file_utf8_format_01.json b/atest/testdata/config/02_import_json/import_file_utf8_format_01.json new file mode 100644 index 00000000..ddf84ef7 --- /dev/null +++ b/atest/testdata/config/02_import_json/import_file_utf8_format_01.json @@ -0,0 +1,27 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "WelcomeString": "Hello... JsonPreprocessor selftest is running now!", + "[import]": "../import/utf8_format_data.json", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" +} \ No newline at end of file diff --git a/atest/testdata/config/02_import_json/import_file_utf8_format_02.json b/atest/testdata/config/02_import_json/import_file_utf8_format_02.json new file mode 100644 index 00000000..470ce24d --- /dev/null +++ b/atest/testdata/config/02_import_json/import_file_utf8_format_02.json @@ -0,0 +1,30 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "WelcomeString": "Hello... JsonPreprocessor selftest is running now!", + "German": "Dies ist der UTF-8 SälfTest", + "Vietnamese": "Đây là bản tự kiểm tra UTF-8", + "Japanese": "これは UTF-8 セルフテストです", + "[import]": "../import/utf8_format_data_01.json", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" +} \ No newline at end of file diff --git a/atest/testdata/config/02_import_json/import_file_utf8_format_03.json b/atest/testdata/config/02_import_json/import_file_utf8_format_03.json new file mode 100644 index 00000000..224497fb --- /dev/null +++ b/atest/testdata/config/02_import_json/import_file_utf8_format_03.json @@ -0,0 +1,30 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "WelcomeString": "Hello... JsonPreprocessor selftest is running now!", + "German": "Will be overridden by German", + "Vietnamese": "Sẽ được ghi đè bằng câu tiếng Việt khác", + "Japanese": "Будет переопределен японским", + "[import]": "../import/utf8_format_data.json", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" +} \ No newline at end of file diff --git a/atest/testdata/config/02_import_json/import_file_utf8_format_04.json b/atest/testdata/config/02_import_json/import_file_utf8_format_04.json new file mode 100644 index 00000000..1d684084 --- /dev/null +++ b/atest/testdata/config/02_import_json/import_file_utf8_format_04.json @@ -0,0 +1,33 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "German": "Dies ist der UTF-8 SälfTest", + "Vietnamese": "Đây là bản tự kiểm tra UTF-8", + "Japanese": "これは UTF-8 セルフテストです", + "Hindi": "यह UTF-8 सेल्फ़टेस्ट है", + "Thai": "นี่คือการทดสอบตัวเอง UTF-8", + "Korean": "이것은 UTF-8 자체 테스트입니다", + "Chinese": "這是 UTF-8 自測", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01", + "[import]": "../import/utf8_format_data_02.json" + } \ No newline at end of file diff --git a/atest/testdata/config/08_utf8_encoding/utf8_format_01.json b/atest/testdata/config/08_utf8_encoding/utf8_format_01.json new file mode 100644 index 00000000..7e538e48 --- /dev/null +++ b/atest/testdata/config/08_utf8_encoding/utf8_format_01.json @@ -0,0 +1,33 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "German": "Dies ist der UTF-8 SälfTest", + "Vietnamese": "Đây là bản tự kiểm tra UTF-8", + "Japanese": "これは UTF-8 セルフテストです", + "Hindi": "यह UTF-8 सेल्फ़टेस्ट है", + "Thai": "นี่คือการทดสอบตัวเอง UTF-8", + "Korean": "이것은 UTF-8 자체 테스트입니다", + "Chinese": "這是 UTF-8 自測", + "[import]" : "../import/imported_file01_config.json", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" + } \ No newline at end of file diff --git a/atest/testdata/config/08_utf8_encoding/utf8_format_02.json b/atest/testdata/config/08_utf8_encoding/utf8_format_02.json new file mode 100644 index 00000000..59fa330d --- /dev/null +++ b/atest/testdata/config/08_utf8_encoding/utf8_format_02.json @@ -0,0 +1,32 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "Tiếng Đức": "Dies ist der UTF-8 SälfTest", + "Tiếng Việt": "Đây là bản tự kiểm tra UTF-8", + "日本": "これは UTF-8 セルフテストです", + "हिंदी": "यह UTF-8 सेल्फ़टेस्ट है", + "แบบไทย": "นี่คือการทดสอบตัวเอง UTF-8", + "한국인": "이것은 UTF-8 자체 테스트입니다", + "中國人": "這是 UTF-8 自測", + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01" + } \ No newline at end of file diff --git a/atest/testdata/config/08_utf8_encoding/utf8_format_03.json b/atest/testdata/config/08_utf8_encoding/utf8_format_03.json new file mode 100644 index 00000000..3d38e616 --- /dev/null +++ b/atest/testdata/config/08_utf8_encoding/utf8_format_03.json @@ -0,0 +1,35 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//***************************************************************************** + +{ + "Project": "JsonPreprocessor", + "utf8": { + "Tiếng Đức": "Dies ist der UTF-8 SälfTest", + "Tiếng Việt": "Đây là bản tự kiểm tra UTF-8", + "日本": "これは UTF-8 セルフテストです", + "हिंदी": "यह UTF-8 सेल्फ़टेस्ट है", + "แบบไทย": "นี่คือการทดสอบตัวเอง UTF-8", + "한국인": "이것은 UTF-8 자체 테스트입니다", + "中國人": "這是 UTF-8 自測" + }, + // Version control information. + "version": { + "majorversion": "0", + "minorversion": "1", + "patchversion": "1" + }, + "TargetName" : "Device@01", + "[import]": "../import/utf8_format_data_03.json" + } \ No newline at end of file diff --git a/atest/testdata/config/import/utf8_format_data.json b/atest/testdata/config/import/utf8_format_data.json new file mode 100644 index 00000000..240b1bd5 --- /dev/null +++ b/atest/testdata/config/import/utf8_format_data.json @@ -0,0 +1,23 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ + "German": "Dies ist der UTF-8 SälfTest", + "Vietnamese": "Đây là bản tự kiểm tra UTF-8", + "Japanese": "これは UTF-8 セルフテストです", + "Hindi": "यह UTF-8 सेल्फ़टेस्ट है", + "Thai": "นี่คือการทดสอบตัวเอง UTF-8", + "Korean": "이것은 UTF-8 자체 테스트입니다", + "Chinese": "這是 UTF-8 自測" +} \ No newline at end of file diff --git a/atest/testdata/config/import/utf8_format_data_01.json b/atest/testdata/config/import/utf8_format_data_01.json new file mode 100644 index 00000000..21e42f04 --- /dev/null +++ b/atest/testdata/config/import/utf8_format_data_01.json @@ -0,0 +1,20 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ + "Hindi": "यह UTF-8 सेल्फ़टेस्ट है", + "Thai": "นี่คือการทดสอบตัวเอง UTF-8", + "Korean": "이것은 UTF-8 자체 테스트입니다", + "Chinese": "這是 UTF-8 自測" +} \ No newline at end of file diff --git a/atest/testdata/config/import/utf8_format_data_02.json b/atest/testdata/config/import/utf8_format_data_02.json new file mode 100644 index 00000000..28a95c47 --- /dev/null +++ b/atest/testdata/config/import/utf8_format_data_02.json @@ -0,0 +1,23 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ + "German": "This is German", + "Vietnamese": 84, + "Japanese": "Đây là tiếng Nhật", + "Hindi": True, + "Thai": False, + "Korean": [1, 2, "List", 4, 5], + "Chinese": None +} \ No newline at end of file diff --git a/atest/testdata/config/import/utf8_format_data_03.json b/atest/testdata/config/import/utf8_format_data_03.json new file mode 100644 index 00000000..7080e725 --- /dev/null +++ b/atest/testdata/config/import/utf8_format_data_03.json @@ -0,0 +1,21 @@ +// Copyright 2020-2022 Robert Bosch Car Multimedia GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +{ + ${utf8}['Tiếng Đức']: "This is German", + ${utf8}['Tiếng Việt']: 84, + ${utf8}['日本']: 1.987, + ${utf8}['हिंदी']: "${utf8}['แบบไทย']", + ${utf8}['한국인']: "${version}['patchversion']" +} From caae030b62b895882aebe81d0084a387622aab61 Mon Sep 17 00:00:00 2001 From: ngoan1608 Date: Tue, 17 Jan 2023 16:41:32 +0700 Subject: [PATCH 15/18] rename test file so that pytest can collect it for execution --- .../{jsonpreprocessor_unittest.py => test_jsonpreprocessor.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename atest/jsonpreprocessor/{jsonpreprocessor_unittest.py => test_jsonpreprocessor.py} (100%) diff --git a/atest/jsonpreprocessor/jsonpreprocessor_unittest.py b/atest/jsonpreprocessor/test_jsonpreprocessor.py similarity index 100% rename from atest/jsonpreprocessor/jsonpreprocessor_unittest.py rename to atest/jsonpreprocessor/test_jsonpreprocessor.py From a3933aa12ae3ca4728e6f391a968449bd068ca71 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Wed, 15 Feb 2023 10:13:23 +0700 Subject: [PATCH 16/18] Update nested parameters feature --- JsonPreprocessor/CJsonPreprocessor.py | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/JsonPreprocessor/CJsonPreprocessor.py b/JsonPreprocessor/CJsonPreprocessor.py index 61e6e752..bc1ff4f8 100644 --- a/JsonPreprocessor/CJsonPreprocessor.py +++ b/JsonPreprocessor/CJsonPreprocessor.py @@ -50,6 +50,7 @@ import re import sys import platform +import copy class CSyntaxType(): python = "python" @@ -413,36 +414,35 @@ def __updateAndReplaceNestedParam(self, oJson : dict, recursive : bool = False): Output Json object as dictionary with all variables resolved. ''' - def __tmpJsonUpdated(k, v, tmpJson, bNested): - if bNested: - if '[' in k: - sExec = k + " = \"" + v + "\"" if isinstance(v, str) else k + " = " + str(v) - try: - exec(sExec, globals()) - except: - raise Exception(f"Could not set variable '{k}' with value '{v}'!") - else: - tmpJson[k] = v + def __jsonUpdated(k, v, oJson): + if '[' in k: + sExec = k + " = \"" + v + "\"" if isinstance(v, str) else k + " = " + str(v) + try: + exec(sExec, globals()) + except: + raise Exception(f"Could not set variable '{k}' with value '{v}'!") else: - tmpJson[k] = v + oJson[k] = v + if bool(self.currentCfg) and not recursive: for k, v in self.currentCfg.items(): globals().update({k:v}) - tmpJson = {} - for k, v in oJson.items(): + tmpJson = copy.deepcopy(oJson) + for k, v in tmpJson.items(): bNested = False if re.match('.*\${\s*', k.lower()): if re.match("str\(\s*\${.+", k.lower()): k = re.sub("str\(\s*(\${.+)\s*\)", "\\1", k) keyAfterProcessed = self.__nestedParamHandler(k) k = re.sub('^\s*\${\s*(.*?)\s*}', '\\1', keyAfterProcessed) + self.lUpdatedParams.append(k) bNested = True if isinstance(v, dict): v = self.__updateAndReplaceNestedParam(v, recursive=True) - __tmpJsonUpdated(k, v, tmpJson, bNested) + __jsonUpdated(k, v, tmpJson) bNested = False elif isinstance(v, list): @@ -472,7 +472,7 @@ def __tmpJsonUpdated(k, v, tmpJson, bNested): raise Exception(f"The variable '{tmpItemAfterProcessed}' is not available!") tmpValue.append(item) - __tmpJsonUpdated(k, tmpValue, tmpJson, bNested) + __jsonUpdated(k, v, oJson) bNested = False elif isinstance(v, str): @@ -502,15 +502,15 @@ def __tmpJsonUpdated(k, v, tmpJson, bNested): if isinstance(v, str) and re.match('^\s*none|true|false\s*$', v.lower()): v = '\"' + v + '\"' - __tmpJsonUpdated(k, v, tmpJson, bNested) + __jsonUpdated(k, v, oJson) bNested = False else: - __tmpJsonUpdated(k, v, tmpJson, bNested) + __jsonUpdated(k, v, oJson) bNested = False else: - __tmpJsonUpdated(k, v, tmpJson, bNested) - - oJson.update(tmpJson) + if bNested: + __jsonUpdated(k, v, oJson) + bNested = False return oJson def __checkAndUpdateKeyValue(self, sInputStr: str) -> str: From 029b727fae93e53e5604cedf6136bbf767e31a56 Mon Sep 17 00:00:00 2001 From: mas2hc Date: Wed, 11 Jan 2023 18:09:20 +0700 Subject: [PATCH 17/18] Adds selftest for utf-8 encoding. --- atest/jsonpreprocessor/test_jsonpreprocessor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/atest/jsonpreprocessor/test_jsonpreprocessor.py b/atest/jsonpreprocessor/test_jsonpreprocessor.py index 4d6d0054..09efbb81 100644 --- a/atest/jsonpreprocessor/test_jsonpreprocessor.py +++ b/atest/jsonpreprocessor/test_jsonpreprocessor.py @@ -375,6 +375,7 @@ def test_utf8_encoding(self): assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" +<<<<<<< HEAD assert oJsonData['Chinese'] == "這是 UTF-8 自測" def test_utf8_encoding_both_key_and_value(self): @@ -482,4 +483,7 @@ def test_utf8_encoding_imported_06(self): assert oJsonData['utf8']['Tiếng Việt'] == 84 assert oJsonData['utf8']['日本'] == 1.987 assert oJsonData['utf8']['हिंदी'] == "นี่คือการทดสอบตัวเอง UTF-8" - assert oJsonData['utf8']['한국인'] == "1" \ No newline at end of file + assert oJsonData['utf8']['한국인'] == "1" +======= + assert oJsonData['Chinese'] == "這是 UTF-8 自測" +>>>>>>> 385e4d9 (Adds selftest for utf-8 encoding.) From 41ffb29a9fb85cbe85cc8ee22816a2a4426f65be Mon Sep 17 00:00:00 2001 From: mas2hc Date: Wed, 15 Feb 2023 22:33:22 +0700 Subject: [PATCH 18/18] Fix merge conflict --- atest/jsonpreprocessor/test_jsonpreprocessor.py | 4 ---- atest/run.bat | 2 +- atest/run.sh | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/atest/jsonpreprocessor/test_jsonpreprocessor.py b/atest/jsonpreprocessor/test_jsonpreprocessor.py index 09efbb81..6b6cd25f 100644 --- a/atest/jsonpreprocessor/test_jsonpreprocessor.py +++ b/atest/jsonpreprocessor/test_jsonpreprocessor.py @@ -375,7 +375,6 @@ def test_utf8_encoding(self): assert oJsonData['Hindi'] == "यह UTF-8 सेल्फ़टेस्ट है" assert oJsonData['Thai'] == "นี่คือการทดสอบตัวเอง UTF-8" assert oJsonData['Korean'] == "이것은 UTF-8 자체 테스트입니다" -<<<<<<< HEAD assert oJsonData['Chinese'] == "這是 UTF-8 自測" def test_utf8_encoding_both_key_and_value(self): @@ -484,6 +483,3 @@ def test_utf8_encoding_imported_06(self): assert oJsonData['utf8']['日本'] == 1.987 assert oJsonData['utf8']['हिंदी'] == "นี่คือการทดสอบตัวเอง UTF-8" assert oJsonData['utf8']['한국인'] == "1" -======= - assert oJsonData['Chinese'] == "這是 UTF-8 自測" ->>>>>>> 385e4d9 (Adds selftest for utf-8 encoding.) diff --git a/atest/run.bat b/atest/run.bat index b343d1cf..88d7f8c5 100644 --- a/atest/run.bat +++ b/atest/run.bat @@ -16,7 +16,7 @@ @ECHO OFF set current_dir=%CD% cd %~dp0\jsonpreprocessor -"%RobotPythonPath%"\python -m pytest jsonpreprocessor_unittest.py --junit-xml=..\logs\windows_jsonpreprocessor_unittest.xml && ( +"%RobotPythonPath%"\python -m pytest test_jsonpreprocessor.py --junit-xml=..\logs\windows_test_jsonpreprocessor.xml && ( echo Run JsonPreprocessor acceptance test sussessful! ) || ( echo Run JsonPreprocessor acceptance test failed! diff --git a/atest/run.sh b/atest/run.sh index 9bb6b21e..37ad3ebb 100644 --- a/atest/run.sh +++ b/atest/run.sh @@ -20,7 +20,7 @@ atest_dir=`dirname $bash_file_path` cd $atest_dir/jsonpreprocessor export PYTHONPATH="$atest_dir/../" echo $PYTHONPATH - $RobotPythonPath/python3 -m pytest jsonpreprocessor_unittest.py --junit-xml=../logs/linux_jsonpreprocessor_unittest.xml && ( + $RobotPythonPath/python3 -m pytest test_jsonpreprocessor.py --junit-xml=../logs/linux_test_jsonpreprocessor.xml && ( echo "Run JsonPreprocessor acceptance test successful!" ) || ( echo "Run JsonPreprocessor acceptance test failed!"