Make the common module a little simpler
This commit is contained in:
+99
-127
@@ -216,25 +216,25 @@ def formatInt(value):
|
|||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
def formatTimeStamp(theTime, fileSafe=False):
|
def formatTimeStamp(value, fileSafe=False):
|
||||||
"""Take a number (on the format returned by time.time()) and convert
|
"""Take a number (on the format returned by time.time()) and convert
|
||||||
it to a timestamp string.
|
it to a timestamp string.
|
||||||
"""
|
"""
|
||||||
if fileSafe:
|
if fileSafe:
|
||||||
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_FSTAMP)
|
return datetime.fromtimestamp(value).strftime(nwConst.FMT_FSTAMP)
|
||||||
else:
|
else:
|
||||||
return datetime.fromtimestamp(theTime).strftime(nwConst.FMT_TSTAMP)
|
return datetime.fromtimestamp(value).strftime(nwConst.FMT_TSTAMP)
|
||||||
|
|
||||||
|
|
||||||
def formatTime(tS):
|
def formatTime(t):
|
||||||
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
|
"""Format a time in seconds in HH:MM:SS format or d-HH:MM:SS format
|
||||||
if a full day or longer.
|
if a full day or longer.
|
||||||
"""
|
"""
|
||||||
if isinstance(tS, int):
|
if isinstance(t, int):
|
||||||
if tS >= 86400:
|
if t >= 86400:
|
||||||
return f"{tS//86400:d}-{tS%86400//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
|
return f"{t//86400:d}-{t%86400//3600:02d}:{t%3600//60:02d}:{t%60:02d}"
|
||||||
else:
|
else:
|
||||||
return f"{tS//3600:02d}:{tS%3600//60:02d}:{tS%60:02d}"
|
return f"{t//3600:02d}:{t%3600//60:02d}:{t%60:02d}"
|
||||||
return "ERROR"
|
return "ERROR"
|
||||||
|
|
||||||
|
|
||||||
@@ -276,111 +276,111 @@ def splitVersionNumber(value):
|
|||||||
return vMajor, vMinor, vPatch, vInt
|
return vMajor, vMinor, vPatch, vInt
|
||||||
|
|
||||||
|
|
||||||
def transferCase(theSource, theTarget):
|
def transferCase(source, target):
|
||||||
"""Transfers the case of the source word to the target word. This
|
"""Transfers the case of the source word to the target word. This
|
||||||
will consider all upper or lower, and first char capitalisation.
|
will consider all upper or lower, and first char capitalisation.
|
||||||
"""
|
"""
|
||||||
theResult = theTarget
|
theResult = target
|
||||||
|
|
||||||
if not isinstance(theSource, str) or not isinstance(theTarget, str):
|
if not isinstance(source, str) or not isinstance(target, str):
|
||||||
return theResult
|
return theResult
|
||||||
if len(theTarget) < 1 or len(theSource) < 1:
|
if len(target) < 1 or len(source) < 1:
|
||||||
return theResult
|
return theResult
|
||||||
|
|
||||||
if theSource.istitle():
|
if source.istitle():
|
||||||
theResult = theTarget.title()
|
theResult = target.title()
|
||||||
|
|
||||||
if theSource.isupper():
|
if source.isupper():
|
||||||
theResult = theTarget.upper()
|
theResult = target.upper()
|
||||||
elif theSource.islower():
|
elif source.islower():
|
||||||
theResult = theTarget.lower()
|
theResult = target.lower()
|
||||||
|
|
||||||
return theResult
|
return theResult
|
||||||
|
|
||||||
|
|
||||||
def fuzzyTime(secDiff):
|
def fuzzyTime(seconds):
|
||||||
"""Converts a time difference in seconds into a fuzzy time string.
|
"""Converts a time difference in seconds into a fuzzy time string.
|
||||||
"""
|
"""
|
||||||
if secDiff < 0:
|
if seconds < 0:
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "in the future"
|
"Common", "in the future"
|
||||||
)
|
)
|
||||||
elif secDiff < 30:
|
elif seconds < 30:
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "just now"
|
"Common", "just now"
|
||||||
)
|
)
|
||||||
elif secDiff < 90:
|
elif seconds < 90:
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "a minute ago"
|
"Common", "a minute ago"
|
||||||
)
|
)
|
||||||
elif secDiff < 3300: # 55 minutes
|
elif seconds < 3300: # 55 minutes
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} minutes ago"
|
"Common", "{0} minutes ago"
|
||||||
).format(int(round(secDiff/60)))
|
).format(int(round(seconds/60)))
|
||||||
elif secDiff < 5400: # 90 minutes
|
elif seconds < 5400: # 90 minutes
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "an hour ago"
|
"Common", "an hour ago"
|
||||||
)
|
)
|
||||||
elif secDiff < 84600: # 23.5 hours
|
elif seconds < 84600: # 23.5 hours
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} hours ago"
|
"Common", "{0} hours ago"
|
||||||
).format(int(round(secDiff/3600)))
|
).format(int(round(seconds/3600)))
|
||||||
elif secDiff < 129600: # 1.5 days
|
elif seconds < 129600: # 1.5 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "a day ago"
|
"Common", "a day ago"
|
||||||
)
|
)
|
||||||
elif secDiff < 561600: # 6.5 days
|
elif seconds < 561600: # 6.5 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} days ago"
|
"Common", "{0} days ago"
|
||||||
).format(int(round(secDiff/86400)))
|
).format(int(round(seconds/86400)))
|
||||||
elif secDiff < 907200: # 10.5 days
|
elif seconds < 907200: # 10.5 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "a week ago"
|
"Common", "a week ago"
|
||||||
)
|
)
|
||||||
elif secDiff < 2419200: # 28 days
|
elif seconds < 2419200: # 28 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} weeks ago"
|
"Common", "{0} weeks ago"
|
||||||
).format(int(round(secDiff/604800)))
|
).format(int(round(seconds/604800)))
|
||||||
elif secDiff < 3888000: # 45 days
|
elif seconds < 3888000: # 45 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "a month ago"
|
"Common", "a month ago"
|
||||||
)
|
)
|
||||||
elif secDiff < 29808000: # 345 days
|
elif seconds < 29808000: # 345 days
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} months ago"
|
"Common", "{0} months ago"
|
||||||
).format(int(round(secDiff/2592000)))
|
).format(int(round(seconds/2592000)))
|
||||||
elif secDiff < 47336400: # 1.5 years
|
elif seconds < 47336400: # 1.5 years
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "a year ago"
|
"Common", "a year ago"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return QCoreApplication.translate(
|
return QCoreApplication.translate(
|
||||||
"Common", "{0} years ago"
|
"Common", "{0} years ago"
|
||||||
).format(int(round(secDiff/31557600)))
|
).format(int(round(seconds/31557600)))
|
||||||
|
|
||||||
|
|
||||||
def numberToRoman(numVal, toLower=False):
|
def numberToRoman(value, toLower=False):
|
||||||
"""Convert an integer to a Roman number.
|
"""Convert an integer to a Roman number.
|
||||||
"""
|
"""
|
||||||
if not isinstance(numVal, int):
|
if not isinstance(value, int):
|
||||||
return "NAN"
|
return "NAN"
|
||||||
if numVal < 1 or numVal > 4999:
|
if value < 1 or value > 4999:
|
||||||
return "OOR"
|
return "OOR"
|
||||||
|
|
||||||
theValues = [
|
lookup = [
|
||||||
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
|
(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"),
|
||||||
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
|
(50, "L"), (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
|
||||||
]
|
]
|
||||||
|
|
||||||
romNum = ""
|
roman = ""
|
||||||
for theDiv, theSym in theValues:
|
for divisor, symbol in lookup:
|
||||||
n = numVal//theDiv
|
n = value//divisor
|
||||||
romNum += n*theSym
|
roman += n*symbol
|
||||||
numVal -= n*theDiv
|
value -= n*divisor
|
||||||
if numVal <= 0:
|
if value <= 0:
|
||||||
break
|
break
|
||||||
|
|
||||||
return romNum.lower() if toLower else romNum
|
return roman.lower() if toLower else roman
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
@@ -438,70 +438,70 @@ def jsonEncode(data, n=0, nmax=0):
|
|||||||
# File and File System Functions
|
# File and File System Functions
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
|
|
||||||
def readTextFile(filePath):
|
def readTextFile(path):
|
||||||
"""Read the content of a text file in a robust manner.
|
"""Read the content of a text file in a robust manner.
|
||||||
"""
|
"""
|
||||||
if not os.path.isfile(filePath):
|
if not os.path.isfile(path):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
fileText = ""
|
text = ""
|
||||||
try:
|
try:
|
||||||
with open(filePath, mode="r", encoding="utf-8") as inFile:
|
with open(path, mode="r", encoding="utf-8") as inFile:
|
||||||
fileText = inFile.read()
|
text = inFile.read()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Could not read file: %s", filePath)
|
logger.error("Could not read file: %s", path)
|
||||||
logException()
|
logException()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
return fileText
|
return text
|
||||||
|
|
||||||
|
|
||||||
def makeFileNameSafe(value):
|
def makeFileNameSafe(value):
|
||||||
"""Returns a filename safe string of the value.
|
"""Returns a filename safe string of the value.
|
||||||
"""
|
"""
|
||||||
cleanName = ""
|
clean = ""
|
||||||
for c in str(value).strip():
|
for c in str(value).strip():
|
||||||
if c.isalpha() or c.isdigit() or c == " ":
|
if c.isalpha() or c.isdigit() or c == " ":
|
||||||
cleanName += c
|
clean += c
|
||||||
return cleanName
|
return clean
|
||||||
|
|
||||||
|
|
||||||
def ensureFolder(dirPath, parentPath=None, errLog=None):
|
def ensureFolder(path, parent=None, errLog=None):
|
||||||
"""Make sure a folder exists, and if it doesn't, create it.
|
"""Make sure a folder exists, and if it doesn't, create it.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if parentPath:
|
if parent:
|
||||||
dirPath = os.path.join(parentPath, dirPath)
|
path = os.path.join(parent, path)
|
||||||
if not os.path.isdir(dirPath):
|
if not os.path.isdir(path):
|
||||||
os.mkdir(dirPath)
|
os.mkdir(path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Could not create folder: %s", dirPath)
|
logger.error("Could not create folder: %s", path)
|
||||||
logException()
|
logException()
|
||||||
if isinstance(errLog, list):
|
if isinstance(errLog, list):
|
||||||
errLog.append(f"Could not create folder: {dirPath}")
|
errLog.append(f"Could not create folder: {path}")
|
||||||
errLog.append(formatException(exc))
|
errLog.append(formatException(exc))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def sha256sum(filePath):
|
def sha256sum(path):
|
||||||
"""Make a shasum of a file using a buffer.
|
"""Make a shasum of a file using a buffer.
|
||||||
Based on: https://stackoverflow.com/a/44873382/5825851
|
Based on: https://stackoverflow.com/a/44873382/5825851
|
||||||
"""
|
"""
|
||||||
hDigest = hashlib.sha256()
|
digest = hashlib.sha256()
|
||||||
bData = bytearray(65536)
|
bData = bytearray(65536)
|
||||||
mData = memoryview(bData)
|
mData = memoryview(bData)
|
||||||
try:
|
try:
|
||||||
with open(filePath, mode="rb", buffering=0) as inFile:
|
with open(path, mode="rb", buffering=0) as inFile:
|
||||||
for n in iter(lambda: inFile.readinto(mData), 0):
|
for n in iter(lambda: inFile.readinto(mData), 0):
|
||||||
hDigest.update(mData[:n])
|
digest.update(mData[:n])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Could not create sha256sum of: %s", filePath)
|
logger.error("Could not create sha256sum of: %s", path)
|
||||||
logException()
|
logException()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return hDigest.hexdigest()
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
@@ -523,87 +523,59 @@ def getGuiItem(objName):
|
|||||||
|
|
||||||
class NWConfigParser(ConfigParser):
|
class NWConfigParser(ConfigParser):
|
||||||
|
|
||||||
CNF_STR = 0
|
|
||||||
CNF_INT = 1
|
|
||||||
CNF_FLOAT = 2
|
|
||||||
CNF_BOOL = 3
|
|
||||||
CNF_S_LST = 4
|
|
||||||
CNF_I_LST = 5
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def rdStr(self, section, option, default):
|
def rdStr(self, section, option, default):
|
||||||
"""Read string value.
|
"""Read string value.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_STR)
|
return self.get(section, option, fallback=default)
|
||||||
|
|
||||||
def rdInt(self, section, option, default):
|
def rdInt(self, section, option, default):
|
||||||
"""Read integer value.
|
"""Read integer value.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_INT)
|
try:
|
||||||
|
return self.getint(section, option, fallback=default)
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||||
|
return default
|
||||||
|
|
||||||
def rdFlt(self, section, option, default):
|
def rdFlt(self, section, option, default):
|
||||||
"""Read float value.
|
"""Read float value.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_FLOAT)
|
try:
|
||||||
|
return self.getfloat(section, option, fallback=default)
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||||
|
return default
|
||||||
|
|
||||||
def rdBool(self, section, option, default):
|
def rdBool(self, section, option, default):
|
||||||
"""Read boolean value.
|
"""Read boolean value.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_BOOL)
|
try:
|
||||||
|
return self.getboolean(section, option, fallback=default)
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Could not read '%s':'%s' from config", section, option)
|
||||||
|
return default
|
||||||
|
|
||||||
def rdStrList(self, section, option, default):
|
def rdStrList(self, section, option, default):
|
||||||
"""Read string list.
|
"""Read string list.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_S_LST)
|
result = default.copy() if isinstance(default, list) else []
|
||||||
|
if self.has_option(section, option):
|
||||||
|
data = self.get(section, option, fallback="").split(",")
|
||||||
|
for i in range(min(len(data), len(result))):
|
||||||
|
result[i] = data[i].strip()
|
||||||
|
return result
|
||||||
|
|
||||||
def rdIntList(self, section, option, default):
|
def rdIntList(self, section, option, default):
|
||||||
"""Read integer list.
|
"""Read integer list.
|
||||||
"""
|
"""
|
||||||
return self._parseLine(section, option, default, self.CNF_I_LST)
|
result = default.copy() if isinstance(default, list) else []
|
||||||
|
|
||||||
##
|
|
||||||
# Internal Functions
|
|
||||||
##
|
|
||||||
|
|
||||||
def _unpackList(self, value, default, type):
|
|
||||||
"""Unpack a comma-separated string of items into a list.
|
|
||||||
"""
|
|
||||||
inList = value.split(",")
|
|
||||||
outList = []
|
|
||||||
if isinstance(default, list):
|
|
||||||
outList = default.copy()
|
|
||||||
for i in range(min(len(inList), len(outList))):
|
|
||||||
try:
|
|
||||||
if type == self.CNF_S_LST:
|
|
||||||
outList[i] = inList[i].strip()
|
|
||||||
elif type == self.CNF_I_LST:
|
|
||||||
outList[i] = int(inList[i].strip())
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
return outList
|
|
||||||
|
|
||||||
def _parseLine(self, section, option, default, type):
|
|
||||||
"""Parse a line and return the correct datatype.
|
|
||||||
"""
|
|
||||||
if self.has_option(section, option):
|
if self.has_option(section, option):
|
||||||
try:
|
data = self.get(section, option, fallback="").split(",")
|
||||||
if type == self.CNF_STR:
|
for i in range(min(len(data), len(result))):
|
||||||
return self.get(section, option)
|
result[i] = checkInt(data[i].strip(), result[i])
|
||||||
elif type == self.CNF_INT:
|
return result
|
||||||
return self.getint(section, option)
|
|
||||||
elif type == self.CNF_FLOAT:
|
|
||||||
return self.getfloat(section, option)
|
|
||||||
elif type == self.CNF_BOOL:
|
|
||||||
return self.getboolean(section, option)
|
|
||||||
elif type in (self.CNF_I_LST, self.CNF_S_LST):
|
|
||||||
return self._unpackList(self.get(section, option), default, type)
|
|
||||||
except ValueError:
|
|
||||||
logger.error("Could not read '%s':'%s' from config", str(section), str(option))
|
|
||||||
logException()
|
|
||||||
return default
|
|
||||||
|
|
||||||
return default
|
|
||||||
|
|
||||||
# END Class NWConfigParser
|
# END Class NWConfigParser
|
||||||
|
|||||||
@@ -306,8 +306,8 @@ class Config:
|
|||||||
|
|
||||||
# We don't error on these failing since they are not essential
|
# We don't error on these failing since they are not essential
|
||||||
if self.dataPath is not None:
|
if self.dataPath is not None:
|
||||||
ensureFolder("syntax", parentPath=self.dataPath)
|
ensureFolder("syntax", parent=self.dataPath)
|
||||||
ensureFolder("themes", parentPath=self.dataPath)
|
ensureFolder("themes", parent=self.dataPath)
|
||||||
|
|
||||||
# Check if config file exists
|
# Check if config file exists
|
||||||
if self.confPath is not None:
|
if self.confPath is not None:
|
||||||
|
|||||||
@@ -556,13 +556,13 @@ def testBaseCommon_EnsureFolder(monkeypatch, fncDir):
|
|||||||
assert ensureFolder(newDir1) is True
|
assert ensureFolder(newDir1) is True
|
||||||
assert os.path.isdir(newDir1)
|
assert os.path.isdir(newDir1)
|
||||||
|
|
||||||
assert ensureFolder("newDir2", parentPath=fncDir) is True
|
assert ensureFolder("newDir2", parent=fncDir) is True
|
||||||
assert os.path.isdir(newDir2)
|
assert os.path.isdir(newDir2)
|
||||||
|
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr("os.mkdir", causeOSError)
|
mp.setattr("os.mkdir", causeOSError)
|
||||||
errLog = []
|
errLog = []
|
||||||
assert ensureFolder("newDir3", parentPath=fncDir, errLog=errLog) is False
|
assert ensureFolder("newDir3", parent=fncDir, errLog=errLog) is False
|
||||||
assert errLog[0] == f"Could not create folder: {newDir3}"
|
assert errLog[0] == f"Could not create folder: {newDir3}"
|
||||||
assert not os.path.isdir(newDir3)
|
assert not os.path.isdir(newDir3)
|
||||||
|
|
||||||
@@ -673,10 +673,10 @@ def testBaseCommon_NWConfigParser(fncDir):
|
|||||||
# Read Float
|
# Read Float
|
||||||
assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0
|
assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0
|
||||||
assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2
|
assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2
|
||||||
assert cfgParser.rdInt("main", "stropt", 13.0) == 13.0
|
assert cfgParser.rdFlt("main", "stropt", 13.0) == 13.0
|
||||||
|
|
||||||
assert cfgParser.rdInt("nope", "intopt1", 13.0) == 13.0
|
assert cfgParser.rdFlt("nope", "intopt1", 13.0) == 13.0
|
||||||
assert cfgParser.rdInt("main", "blabla", 13.0) == 13.0
|
assert cfgParser.rdFlt("main", "blabla", 13.0) == 13.0
|
||||||
|
|
||||||
# Read String List
|
# Read String List
|
||||||
assert cfgParser.rdStrList("main", "list1", []) == []
|
assert cfgParser.rdStrList("main", "list1", []) == []
|
||||||
@@ -704,9 +704,4 @@ def testBaseCommon_NWConfigParser(fncDir):
|
|||||||
assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
|
assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
|
||||||
assert cfgParser.rdIntList("main", "blabla", [1]) == [1]
|
assert cfgParser.rdIntList("main", "blabla", [1]) == [1]
|
||||||
|
|
||||||
# Internal
|
|
||||||
# ========
|
|
||||||
|
|
||||||
assert cfgParser._parseLine("main", "stropt", None, 999) is None
|
|
||||||
|
|
||||||
# END Test testBaseCommon_NWConfigParser
|
# END Test testBaseCommon_NWConfigParser
|
||||||
|
|||||||
Reference in New Issue
Block a user