#const ROOIBOS_ERROR_LOGS = true
#const ROOIBOS_WARNING_LOGS = false
#const ROOIBOS_INFO_LOGS = true
#const ROOIBOS_DEBUG_LOGS = false
#const ROOIBOS_TRACE_LOGS = false
namespace rooibos.common
' ******************
' Common utility functions
' ******************
' check if value contains XMLElement interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains XMLElement interface, else return false
function isXmlElement(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifXMLElement") <> invalid
end function
' check if value contains Function interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains Function interface, else return false
function isFunction(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifFunction") <> invalid
end function
' looks up the function by name, for the function map
' @param {filename} string - name of the file where the function was found
' @param {String} functionName - name of the function to locate
' @returns {Function} - function pointer or invalid
function getFunction(filename, functionName) as object
if not rooibos.common.isNotEmptyString(functionName)
return invalid
end if
if not rooibos.common.isNotEmptyString(filename)
return invalid
end if
'bs:disable-next-line
mapFunction = RBSFM_getFunctionsForFile(filename)
'bs:disable-next-line
if mapFunction <> invalid
'bs:disable-next-line
map = mapFunction()
'bs:disable-next-line
if type(map) = "roAssociativeArray"
'bs:disable-next-line
functionPointer = map[functionName]
'bs:disable-next-linenrb
return functionPointer
else
return invalid
end if
end if
return invalid
end function
' looks up the function by name, from any function map
' in future
' @param {filename} string - name of the file where the function was found
' @param {String} functionName - name of the function to locate
' @returns {Function} - function pointer or invalid
function getFunctionBruteForce(functionName) as object
if not rooibos.common.isNotEmptyString(functionName)
return invalid
end if
'bs:disable-next-line
filenames = RBSFM_getFilenames()
for i = 0 to filenames.count() - 1
filename = filenames[i]
'bs:disable-next-line
mapFunction = RBSFM_getFunctionsForFile(filename)
'bs:disable-next-line
if mapFunction <> invalid
'bs:disable-next-line
map = mapFunction()
'bs:disable-next-line
if type(map) = "roAssociativeArray"
'bs:disable-next-line
functionPointer = map[functionName]
'bs:disable-next-line
if functionPointer <> invalid
'bs:disable-next-line
return functionPointer
end if
end if
end if
end for
return invalid
end function
' check if value contains Boolean interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains Boolean interface, else return false
function isBoolean(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifBoolean") <> invalid
end function
' check if value type equals Integer
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value type equals Integer, else return false
function isInteger(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifInt") <> invalid and (Type(value) = "roInt" or Type(value) = "roInteger" or Type(value) = "Integer")
end function
' check if value contains Float interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains Float interface, else return false
function isFloat(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifFloat") <> invalid
end function
' check if value contains Double interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains Double interface, else return false
function isDouble(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifDouble") <> invalid
end function
' check if value contains LongInteger interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains LongInteger interface, else return false
function isLongInteger(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifLongInt") <> invalid
end function
' check if value contains LongInteger or Integer or Double or Float interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value is number, else return false
function isNumber(value) as boolean
return rooibos.common.isLongInteger(value) or rooibos.common.isDouble(value) or rooibos.common.isInteger(value) or rooibos.common.isFloat(value)
end function
' check if value contains List interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains List interface, else return false
function isList(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifList") <> invalid
end function
' check if value contains Array interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains Array interface, else return false
function isArray(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifArray") <> invalid
end function
' check if value contains AssociativeArray interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains AssociativeArray interface, else return false
function isAssociativeArray(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifAssociativeArray") <> invalid
end function
' check if value contains SGNodeChildren interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains SGNodeChildren interface, else return false
function isSGNode(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifSGNodeChildren") <> invalid
end function
' check if value contains String interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains String interface, else return false
function isString(value) as boolean
return rooibos.common.isValid(value) and GetInterface(value, "ifString") <> invalid
end function
' check if value contains String interface and length more 0
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains String interface and length more 0, else return false
function isNotEmptyString(value) as boolean
return rooibos.common.isString(value) and len(value) > 0
end function
' check if value contains DateTime interface
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value contains DateTime interface, else return false
function isDateTime(value) as boolean
return rooibos.common.isValid(value) and (GetInterface(value, "ifDateTime") <> invalid or Type(value) = "roDateTime")
end function
' check if value initialized and not equal invalid
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value initialized and not equal invalid, else return false
function isValid(value) as boolean
return not rooibos.common.isUndefined(value) and value <> invalid
end function
function isUndefined(value) as boolean
return type(value) = "" or Type(value) = "<uninitialized>"
end function
' return value if his contains String interface else return empty string
' @param {Dynamic} value - value to check
' @returns {String} - value if his contains String interface else return empty string
function validStr(obj) as string
if obj <> invalid and GetInterface(obj, "ifString") <> invalid
return obj
else
return ""
end if
end function
' /**
' convert input to multiline String if this possible, else return empty string
' @param {Dynamic} input - value to check
' @returns {String} - converted string
function asMultilineString(input, includeType = false, indention = 0) as string
indentChr = " "
if rooibos.common.isValid(input) = false
return type(input)
else if rooibos.common.isString(input)
return formatJson(input)
else if rooibos.common.isInteger(input) or rooibos.common.isLongInteger(input) or rooibos.common.isBoolean(input)
if includeType
return input.ToStr() + " (" + rooibos.common.getSafeType(input) + ")"
else
return input.ToStr()
end if
else if rooibos.common.isFloat(input) or rooibos.common.isDouble(input)
if includeType
return Str(input).Trim() + " (" + rooibos.common.getSafeType(input) + ")"
else
return Str(input).Trim()
end if
else if type(input) = "roSGNode"
return "Node(" + input.subType() + ")"
else if type(input) = "roAssociativeArray"
if input.isEmpty()
return "{" + chr(10) + string(indention, indentChr) + "}"
end if
text = "{" + chr(10)
keys = input.ifAssociativeArray.keys()
keys.sort()
for each key in keys
if rooibos.common.canSafelyIterateAAKey(input, key)
text = text + string(indention + 1, indentChr) + formatJson(key) + ": " + rooibos.common.asMultilineString(input[key], includeType, indention + 1) + "," + chr(10)
end if
end for
' remove last comma
if len(text) > 2
text = left(text, len(text) - 2)
end if
text = text + chr(10) + string(indention, indentChr) + "}"
return text
else if rooibos.common.isArray(input)
if input.isEmpty()
return "[" + chr(10) + "]"
end if
text = "[" + chr(10)
for i = 0 to input.count() - 1
v = input[i]
text += string(indention + 1, indentChr) + rooibos.common.asMultilineString(v, includeType, indention + 1)
if i < input.count() - 1
text += ","
end if
text += chr(10)
end for
text = text + string(indention, indentChr) + "]"
return text
else if rooibos.common.isFunction(input)
return input.toStr().mid(10) + " (function)"
else
return ""
end if
end function
' convert input to String if this possible, else return empty string
' @param {Dynamic} input - value to check
' @returns {String} - converted string
function asString(input, includeType = false) as string
if rooibos.common.isValid(input) = false
return "INVALID"
else if rooibos.common.isString(input)
if includeType
return """" + input + """"
else
return input
end if
else if rooibos.common.isInteger(input) or rooibos.common.isLongInteger(input) or rooibos.common.isBoolean(input)
if includeType
return input.ToStr() + " (" + rooibos.common.getSafeType(input) + ")"
else
return input.ToStr()
end if
else if rooibos.common.isFloat(input) or rooibos.common.isDouble(input)
if includeType
return Str(input).Trim() + " (" + rooibos.common.getSafeType(input) + ")"
else
return Str(input).Trim()
end if
else if type(input) = "roSGNode"
return "Node(" + input.subType() + ")"
else if type(input) = "roAssociativeArray"
isFirst = true
text = "{"
if not isFirst
text = text + ","
'bs:disable-next-line
isFirst = false
end if
keys = input.ifAssociativeArray.keys()
keys.sort()
for each key in keys
if rooibos.common.canSafelyIterateAAKey(input, key)
text = text + key + ":" + rooibos.common.asString(input[key], includeType)
end if
end for
text = text + "}"
return text
else if rooibos.common.isArray(input)
text = "["
join = ""
maxLen = 500
for each v in input
if len(text) < maxLen
text += join + rooibos.common.asString(v, includeType)
join = ", "
end if
end for
if len(text) > maxLen
text = left(text, maxLen - 3) + "..."
end if
text = text + "]"
return text
else if rooibos.common.isFunction(input)
return input.toStr() + "(function)"
else
return ""
end if
end function
' convert input to Integer if this possible, else return 0
' @param {Dynamic} input - value to check
' @returns {Integer} - converted Integer
function asInteger(input) as integer
if rooibos.common.isValid(input) = false
return 0
else if rooibos.common.isString(input)
return input.ToInt()
else if rooibos.common.isInteger(input)
return input
else if rooibos.common.isFloat(input) or rooibos.common.isDouble(input) or rooibos.common.isLongInteger(input)
return Int(input)
else
return 0
end if
end function
' convert input to LongInteger if this possible, else return 0
' @param {Dynamic} input - value to check
' @returns {Integer} - converted LongInteger
function asLongInteger(input) as longinteger
if rooibos.common.isValid(input) = false
return 0
else if rooibos.common.isString(input)
return rooibos.common.asInteger(input)
else if rooibos.common.isLongInteger(input) or rooibos.common.isFloat(input) or rooibos.common.isDouble(input) or rooibos.common.isInteger(input)
return input
else
return 0
end if
end function
' convert input to Float if this possible, else return 0.0
' @param {Dynamic} input - value to check
' @returns {Float} - converted Float
function asFloat(input) as float
if rooibos.common.isValid(input) = false
return 0.0
else if rooibos.common.isString(input)
return input.ToFloat()
else if rooibos.common.isInteger(input)
return (input / 1)
else if rooibos.common.isFloat(input) or rooibos.common.isDouble(input) or rooibos.common.isLongInteger(input)
return input
else
return 0.0
end if
end function
' convert input to Double if this possible, else return 0.0
' @param {Dynamic} input - value to check
' @returns {Float} - converted Double
function asDouble(input) as double
if rooibos.common.isValid(input) = false
return 0.0
else if rooibos.common.isString(input)
return rooibos.common.asFloat(input)
else if rooibos.common.isInteger(input) or rooibos.common.isLongInteger(input) or rooibos.common.isFloat(input) or rooibos.common.isDouble(input)
return input
else
return 0.0
end if
end function
' convert input to Boolean if this possible, else return False
' @param {Dynamic} input - value to check
' @returns {Boolean} - converted boolean
function asBoolean(input) as boolean
if rooibos.common.isValid(input) = false
return false
else if rooibos.common.isString(input)
return LCase(input) = "true"
else if rooibos.common.isInteger(input) or rooibos.common.isFloat(input)
return input <> 0
else if rooibos.common.isBoolean(input)
return input
else
return false
end if
end function
' if type of value equals array return value, else return array with one element [value]
' @param {Dynamic} value - value to check
' @returns {Array} - converted array
function asArray(value) as object
if rooibos.common.isValid(value)
if not rooibos.common.isArray(value)
return [value]
else
return value
end if
end if
return []
end function
'=====================
' Strings
'=====================
' check if value is invalid or empty
' @param {Dynamic} value - value to check
' @returns {Boolean} - true if value is null or empty string, else return false
function isNullOrEmpty(value) as boolean
if rooibos.common.isString(value)
return Len(value) = 0
else
return not rooibos.common.isValid(value)
end if
end function
'=====================
' Arrays
'=====================
' find an element index in array
' @param {Dynamic} array - array to search
' @param {Dynamic} value - value to check
' @param {Dynamic} compareAttribute - attribue to use for comparisons
' @param {Boolean} caseSensitive - indicates if comparisons are case sensitive
' @returns {Integer} - element index if array contains a value, else return -1
function findElementIndexInArray(array, value, compareAttribute = invalid, caseSensitive = false, callCount = 0) as integer
if callCount = 0 and not rooibos.common.isArray(array)
array = rooibos.common.asArray(array)
end if
if rooibos.common.isArray(array)
for i = 0 to rooibos.common.asArray(array).Count() - 1
compareValue = array[i]
if compareAttribute <> invalid and rooibos.common.isAssociativeArray(compareValue)
compareValue = compareValue.ifAssociativeArray.lookupCI(compareAttribute)
end if
if rooibos.common.eqValues(compareValue, value, callCount + 1)
return i
end if
next
end if
return -1
end function
' check if array contains specified value
' @param {Dynamic} array - array to search in
' @param {Dynamic} value - value to check
' @param {Dynamic} compareAttribute - attribute to compare on
' @returns {Boolean} - true if array contains a value, else return false
function arrayContains(array, value, compareAttribute = invalid) as boolean
return (rooibos.common.findElementIndexInArray(array, value, compareAttribute) > -1)
end function
'=====================
' NODES
'=====================
' find an element index in node
' @param {Dynamic} node - node to search in
' @param {Dynamic} value - child to search for
' @returns {Integer} - element index if node contains a value, else return -1
function findElementIndexInNode(node, value) as integer
if type(node) = "roSGNode"
if node.isSubType("mc_Node")
for i = 0 to node.length - 1
compareValue = node@.getChild(i)
if type(compareValue) = "roSGNode" and compareValue.isSameNode(value)
return i
end if
end for
else
for i = 0 to node.getChildCount() - 1
compareValue = node.getChild(i)
if type(compareValue) = "roSGNode" and compareValue.isSameNode(value)
return i
end if
end for
end if
end if
return -1
end function
' check if node contains specified child
' @param {Dynamic} node - the node to check on
' @param {Dynamic} value - child to look for
' @returns {Boolean} - true if node contains a value, else return false
function nodeContains(node, value) as boolean
return (rooibos.common.findElementIndexInNode(node, value) > -1)
end function
function getSafeType(v)
t = type(v)
if t = ""
return invalid
else if t = "<uninitialized>"
return "<uninitialized>"
else if t = "roString"
return "String"
else if t = "roInteger"
return "Integer"
else if t = "roBoolean"
return "Boolean"
else if t = "roBool"
return "Boolean"
else if t = "roInt"
return "Integer"
else if t = "roList"
return "List"
else if t = "roFloat"
return "Float"
else if t = "roDouble"
return "Double"
else if t = "roInvalid"
return "Invalid"
else
return t
end if
end function
' Takes a value and if the value is not a primitive it will wrap the type in a Component: tag like the debugger does
' @param {Dynamic} value - value to check the type of
' @param {Boolean} includeSubtype - If true and the value is a node the result will include the node subtype
' @returns {string} - Formatted result. Examples: 'String', 'Integer', '<Component: roDateTime>', '<Component: roSGNode:Node>'
function getTypeWithComponentWrapper(value, includeSubtype = false) as string
if not rooibos.common.isValid(value) or rooibos.common.isNumber(value) or rooibos.common.isString(value) or rooibos.common.isBoolean(value)
return type(value)
else
if includeSubtype and rooibos.common.isSGNode(value)
return `<Component: ${type(value)}:${value.subType()}>`
else
return `<Component: ${type(value)}>`
end if
end if
end function
' Takes a string and formats and truncates a string for more compact printing.
' @param {Dynamic} value - string to format
' @param {Integer} maxLength - the max length of the resulting string
' @param {Boolean} collapseNewlines - Will convert newlines and spaces into a single space
' @returns {String} - Formatted result
function truncateString(value as string, length = 38 as integer, collapseNewlines = true as boolean) as string
if collapseNewlines
value = CreateObject("roRegex", "\n\s*", "g").replaceAll(value, " ")
end if
if len(value) > length
value = value.mid(0, length - 1) + "…"
end if
return value
end function
' Compare two arbitrary values to each-other.
' @param {Dynamic} Value1 - first item to compare
' @param {Dynamic} Value2 - second item to compare
' @returns {boolean} - True if values are equal or False in other case.
function eqValues(Value1, Value2, fuzzy = false, callCount = 0) as dynamic
if callCount > 10
rooibos.common.logError("REACHED MAX ITERATIONS DOING COMPARISON")
return true
end if
' Workaround for bug with string boxing, and box everything else
val1Type = rooibos.common.getSafeType(Value1)
val2Type = rooibos.common.getSafeType(Value2)
if val1Type = invalid or val2Type = invalid
rooibos.common.logError("undefined value passed")
return false
end if
'Upcast int to float, if other is float
if val1Type = "Float" and val2Type = "Integer"
Value2 = cdbl(Value2)
else if val2Type = "Float" and val1Type = "Integer"
Value1 = cdbl(Value1)
end if
if val1Type <> val2Type and (fuzzy <> true or val1Type = "String" or val2Type = "String")
return false
else
valtype = val1Type
if val1Type = "List"
return rooibos.common.eqArray(Value1, Value2, fuzzy, callCount + 1)
else if valtype = "roAssociativeArray"
return rooibos.common.eqAssocArray(Value1, Value2, fuzzy, callCount + 1)
else if valtype = "roArray"
return rooibos.common.eqArray(Value1, Value2, fuzzy, callCount + 1)
else if valtype = "roSGNode"
if val2Type <> "roSGNode"
return false
else
return Value1.isSameNode(Value2)
end if
else if valtype = "<uninitialized>" and val2Type = "<uninitialized>"
' Both values are uninitialized, so they are equal
return true
else if valtype = "<uninitialized>" or val2Type = "<uninitialized>"
' One value is uninitialized, so they are not equal due to passing previous check
return false
else
if fuzzy = true
return rooibos.common.asString(Value1) = rooibos.common.asString(Value2)
else
'If you crashed on this line, then you're trying to compare
'2 things which can't be compared - check what value1 and value2
'are in your debug log
return Value1 = Value2
end if
end if
end if
end function
function eqTypes(Value1, Value2) as dynamic
val1Type = rooibos.common.getSafeType(Value1)
val2Type = rooibos.common.getSafeType(Value2)
if val1Type = invalid or val2Type = invalid
' TODO: this doesn't actually feel like an error, Need to talk about this.
rooibos.common.logError("undefined value passed")
return false
end if
'Upcast int to float, if other is float
if val1Type = "Float" and val2Type = "Integer"
Value2 = cdbl(Value2)
else if val2Type = "Float" and val1Type = "Integer"
Value1 = cdbl(Value1)
end if
return val1Type <> val2Type
end function
' Compare to roAssociativeArray objects for equality.
' @param {Dynamic} Value1 - first associative array
' @param {Dynamic} Value2 - second associative array
' @returns {boolean} - True if arrays are equal or False in other case.
function eqAssocArray(Value1, Value2, fuzzy = false, callCount = 0) as dynamic
if not rooibos.common.isAssociativeArray(Value1) or not rooibos.common.isAssociativeArray(Value2)
return false
end if
l1 = Value1.ifAssociativeArray.Count()
l2 = Value2.ifAssociativeArray.Count()
if not l1 = l2
return false
else
for each k in Value1
if not Value2.ifAssociativeArray.DoesExist(k)
return false
else
if rooibos.common.canSafelyIterateAAKey(Value1, k) and rooibos.common.canSafelyIterateAAKey(Value2, k)
v1 = Value1[k]
v2 = Value2[k]
if not rooibos.common.eqValues(v1, v2, fuzzy, callCount + 1)
return false
end if
end if
end if
end for
return true
end if
end function
function canSafelyIterateAAKey(aa, key) as boolean
if lcase(key) = "__rooibosskipfields" or key = "__mocks" or key = "__stubs" or key = "log" or key = "top" or key = "m" 'fix infinite loop/box crash when doing equals on an aa with a mock
return false
else if aa.__rooibosSkipFields <> invalid and aa.__rooibosSkipFields.doesExist(key)
return false
end if
return true
end function
' Compare to roArray objects for equality.
' @param {Dynamic} Value1 - first array
' @param {Dynamic} Value2 - second array
' @returns {boolean} - True if arrays are equal or False in other case.
function eqArray(Value1, Value2, fuzzy = false, callCount = 0) as dynamic
if callCount > 30
rooibos.common.logError("REACHED MAX ITERATIONS DOING COMPARISON")
return true
end if
if not (rooibos.common.isArray(Value1)) or not rooibos.common.isArray(Value2)
return false
end if
l1 = Value1.Count()
l2 = Value2.Count()
if not l1 = l2
return false
else
for i = 0 to l1 - 1
v1 = Value1[i]
v2 = Value2[i]
if not rooibos.common.eqValues(v1, v2, fuzzy, callCount + 1)
return false
end if
end for
return true
end if
end function
' Fills text with count of fillChars
' @param {string} text - text to fill
' @param {string} fillChar - char to fill with
' @param {integer} numChars - target length
' @returns {string} filled string
function fillText(text as string, fillChar = " ", numChars = 40) as string
if len(text) >= numChars
text = left(text, numChars - 5) + "..." + fillChar + fillChar
else
numToFill = numChars - len(text) - 1
for i = 0 to numToFill
text = text + fillChar
end for
end if
return text
end function
function makePathStubbable(content as dynamic, path as string)
part = invalid
if path <> invalid
parts = path.split(".")
numParts = parts.count()
i = 0
contentName = parts[i]
i++
if type(content) <> "roAssociativeArray"
content = { id: contentName }
end if
part = content
while i < numParts and part <> invalid
isIndexNumber = parts[i] = "0" or (parts[i].toInt() <> 0 and parts[i].toInt().toStr() = parts[i])
index = invalid
if isIndexNumber
index = parts[i].toInt()
else
index = parts[i]
end if
nextPart = invalid
if rooibos.common.isArray(part) and isIndexNumber
nextPart = part[index]
else if type(part) = "roAssociativeArray" and not isIndexNumber
nextPart = part[index]
end if
if nextPart = invalid or type(nextPart) <> "roAssociativeArray"
if (not isIndexNumber and type(part) = "roAssociativeArray") or (isIndexNumber and (rooibos.common.isArray(part)))
nextPart = { id: index }
part[index] = nextPart
else
'index type mismatch, gonna have to bail
return content
end if
end if
part = nextPart
i++
end while
end if
return part
end function
' @ignore
sub logError(value)
#if ROOIBOS_ERROR_LOGS
? "[Rooibos Error]: " value
#end if
end sub
' @ignore
sub logWarning(value)
#if ROOIBOS_WARNING_LOGS
? "[Rooibos Warning]: " value
#end if
end sub
' @ignore
sub logInfo(value)
#if ROOIBOS_INFO_LOGS
? "[Rooibos Info]: " value
#end if
end sub
' @ignore
sub logDebug(value)
#if ROOIBOS_DEBUG_LOGS
? "[Rooibos Debug]: " value
#end if
end sub
' @ignore
sub logTrace(value)
#if ROOIBOS_TRACE_LOGS
? "[Rooibos Trace]: " value
#end if
end sub
end namespace