484 lines
15 KiB
GDScript
484 lines
15 KiB
GDScript
@tool
|
|
extends Object
|
|
class_name Log
|
|
|
|
## helpers ####################################
|
|
|
|
static func assoc(opts: Dictionary, key: String, val):
|
|
var _opts = opts.duplicate(true)
|
|
_opts[key] = val
|
|
return _opts
|
|
|
|
## config ####################################
|
|
|
|
static var config = {
|
|
max_array_size=20,
|
|
dictionary_skip_keys=["layer_0/tile_data"],
|
|
}
|
|
|
|
static func get_max_array_size():
|
|
return Log.config.get("max_array_size", 20)
|
|
|
|
static func get_dictionary_skip_keys():
|
|
return Log.config.get("dictionary_skip_keys", [])
|
|
|
|
static func set_color_scheme(scheme):
|
|
Log.config["color_scheme"] = scheme
|
|
|
|
static func get_config_color_scheme():
|
|
return Log.config.get("color_scheme", {})
|
|
|
|
## colors ###########################################################################
|
|
|
|
# terminal safe colors:
|
|
# - black
|
|
# - red
|
|
# - green
|
|
# - yellow
|
|
# - blue
|
|
# - magenta
|
|
# - pink
|
|
# - purple
|
|
# - cyan
|
|
# - white
|
|
# - orange
|
|
# - gray
|
|
|
|
static var COLORS_TERMINAL_SAFE = {
|
|
"SRC": "cyan",
|
|
"ADDONS": "red",
|
|
"TEST": "green",
|
|
",": "red",
|
|
"(": "red",
|
|
")": "red",
|
|
"[": "red",
|
|
"]": "red",
|
|
"{": "red",
|
|
"}": "red",
|
|
"&": "orange",
|
|
"^": "orange",
|
|
"dict_key": "magenta",
|
|
"vector_value": "green",
|
|
"class_name": "magenta",
|
|
TYPE_NIL: "pink",
|
|
TYPE_BOOL: "pink",
|
|
TYPE_INT: "green",
|
|
TYPE_FLOAT: "green",
|
|
TYPE_STRING: "pink",
|
|
TYPE_VECTOR2: "green",
|
|
TYPE_VECTOR2I: "green",
|
|
TYPE_RECT2: "green",
|
|
TYPE_RECT2I: "green",
|
|
TYPE_VECTOR3: "green",
|
|
TYPE_VECTOR3I: "green",
|
|
TYPE_TRANSFORM2D: "pink",
|
|
TYPE_VECTOR4: "green",
|
|
TYPE_VECTOR4I: "green",
|
|
TYPE_PLANE: "pink",
|
|
TYPE_QUATERNION: "pink",
|
|
TYPE_AABB: "pink",
|
|
TYPE_BASIS: "pink",
|
|
TYPE_TRANSFORM3D: "pink",
|
|
TYPE_PROJECTION: "pink",
|
|
TYPE_COLOR: "pink",
|
|
TYPE_STRING_NAME: "pink",
|
|
TYPE_NODE_PATH: "pink",
|
|
TYPE_RID: "pink",
|
|
TYPE_OBJECT: "pink",
|
|
TYPE_CALLABLE: "pink",
|
|
TYPE_SIGNAL: "pink",
|
|
TYPE_DICTIONARY: "pink",
|
|
TYPE_ARRAY: "pink",
|
|
TYPE_PACKED_BYTE_ARRAY: "pink",
|
|
TYPE_PACKED_INT32_ARRAY: "pink",
|
|
TYPE_PACKED_INT64_ARRAY: "pink",
|
|
TYPE_PACKED_FLOAT32_ARRAY: "pink",
|
|
TYPE_PACKED_FLOAT64_ARRAY: "pink",
|
|
TYPE_PACKED_STRING_ARRAY: "pink",
|
|
TYPE_PACKED_VECTOR2_ARRAY: "pink",
|
|
TYPE_PACKED_VECTOR3_ARRAY: "pink",
|
|
TYPE_PACKED_COLOR_ARRAY: "pink",
|
|
TYPE_MAX: "pink",
|
|
}
|
|
|
|
static var COLORS_PRETTY_V1 = {
|
|
"SRC": "aquamarine",
|
|
"ADDONS": "peru",
|
|
"TEST": "green_yellow",
|
|
",": "crimson",
|
|
"(": "crimson",
|
|
")": "crimson",
|
|
"[": "crimson",
|
|
"]": "crimson",
|
|
"{": "crimson",
|
|
"}": "crimson",
|
|
"&": "coral",
|
|
"^": "coral",
|
|
"dict_key": "cadet_blue",
|
|
"vector_value": "cornflower_blue",
|
|
"class_name": "cadet_blue",
|
|
TYPE_NIL: "coral",
|
|
TYPE_BOOL: "pink",
|
|
TYPE_INT: "cornflower_blue",
|
|
TYPE_FLOAT: "cornflower_blue",
|
|
TYPE_STRING: "dark_gray",
|
|
TYPE_VECTOR2: "cornflower_blue",
|
|
TYPE_VECTOR2I: "cornflower_blue",
|
|
TYPE_RECT2: "cornflower_blue",
|
|
TYPE_RECT2I: "cornflower_blue",
|
|
TYPE_VECTOR3: "cornflower_blue",
|
|
TYPE_VECTOR3I: "cornflower_blue",
|
|
TYPE_TRANSFORM2D: "pink",
|
|
TYPE_VECTOR4: "cornflower_blue",
|
|
TYPE_VECTOR4I: "cornflower_blue",
|
|
TYPE_PLANE: "pink",
|
|
TYPE_QUATERNION: "pink",
|
|
TYPE_AABB: "pink",
|
|
TYPE_BASIS: "pink",
|
|
TYPE_TRANSFORM3D: "pink",
|
|
TYPE_PROJECTION: "pink",
|
|
TYPE_COLOR: "pink",
|
|
TYPE_STRING_NAME: "pink",
|
|
TYPE_NODE_PATH: "pink",
|
|
TYPE_RID: "pink",
|
|
TYPE_OBJECT: "pink",
|
|
TYPE_CALLABLE: "pink",
|
|
TYPE_SIGNAL: "pink",
|
|
TYPE_DICTIONARY: "pink",
|
|
TYPE_ARRAY: "pink",
|
|
TYPE_PACKED_BYTE_ARRAY: "pink",
|
|
TYPE_PACKED_INT32_ARRAY: "pink",
|
|
TYPE_PACKED_INT64_ARRAY: "pink",
|
|
TYPE_PACKED_FLOAT32_ARRAY: "pink",
|
|
TYPE_PACKED_FLOAT64_ARRAY: "pink",
|
|
TYPE_PACKED_STRING_ARRAY: "pink",
|
|
TYPE_PACKED_VECTOR2_ARRAY: "pink",
|
|
TYPE_PACKED_VECTOR3_ARRAY: "pink",
|
|
TYPE_PACKED_COLOR_ARRAY: "pink",
|
|
TYPE_MAX: "pink",
|
|
}
|
|
|
|
## set color scheme ####################################
|
|
|
|
static func set_colors_termsafe():
|
|
set_color_scheme(Log.COLORS_TERMINAL_SAFE)
|
|
|
|
static func set_colors_pretty():
|
|
set_color_scheme(Log.COLORS_PRETTY_V1)
|
|
|
|
static func color_scheme(opts={}):
|
|
var scheme = opts.get("color_scheme", {})
|
|
# fill in any missing vals with the set scheme, then the term-safe fallbacks
|
|
scheme.merge(Log.get_config_color_scheme())
|
|
scheme.merge(Log.COLORS_TERMINAL_SAFE)
|
|
return scheme
|
|
|
|
static func color_wrap(s, opts={}):
|
|
var use_color = opts.get("use_color", true)
|
|
# don't rebuild the color scheme every time
|
|
var colors = opts.get("built_color_scheme", color_scheme(opts))
|
|
|
|
if use_color:
|
|
var color = opts.get("color")
|
|
if not color:
|
|
var s_type = opts.get("typeof", typeof(s))
|
|
if s_type is String:
|
|
# type overwrites
|
|
color = colors.get(s_type)
|
|
elif s_type is int and s_type == TYPE_STRING:
|
|
# specific strings/punctuation
|
|
var s_trimmed = s.strip_edges()
|
|
if s_trimmed in colors:
|
|
color = colors.get(s_trimmed)
|
|
else:
|
|
# fallback string color
|
|
color = colors.get(s_type)
|
|
else:
|
|
# all other types
|
|
color = colors.get(s_type)
|
|
|
|
if color == null:
|
|
print("Log.gd could not determine color for object: %s type: (%s)" % [str(s), typeof(s)])
|
|
|
|
return "[color=%s]%s[/color]" % [color, s]
|
|
else:
|
|
return s
|
|
|
|
## overwrites ###########################################################################
|
|
|
|
static var log_overwrites = {
|
|
"Vector2": func(msg, opts):
|
|
if opts.get("use_color", true):
|
|
return '%s%s%s%s%s' % [
|
|
Log.color_wrap("(", opts),
|
|
Log.color_wrap(msg.x, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.y, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(")", opts),
|
|
]
|
|
else:
|
|
return '(%s,%s)' % [msg.x, msg.y],
|
|
}
|
|
|
|
static func register_overwrite(key, handler):
|
|
# TODO warning on key exists?
|
|
# support multiple handlers?
|
|
# return success/fail?
|
|
# validate the key/handler somehow?
|
|
log_overwrites[key] = handler
|
|
|
|
## to_pretty ###########################################################################
|
|
|
|
# returns the passed object as a decorated string
|
|
static func to_pretty(msg, opts={}):
|
|
var newlines = opts.get("newlines", false)
|
|
var use_color = opts.get("use_color", true)
|
|
var indent_level = opts.get("indent_level", 0)
|
|
if not "indent_level" in opts:
|
|
opts["indent_level"] = indent_level
|
|
|
|
var color_scheme = opts.get("built_color_scheme", color_scheme(opts))
|
|
if not "built_color_scheme" in opts:
|
|
opts["built_color_scheme"] = color_scheme
|
|
|
|
if not is_instance_valid(msg) and typeof(msg) == TYPE_OBJECT:
|
|
return str("invalid instance: ", msg)
|
|
|
|
if msg == null:
|
|
return Log.color_wrap(msg, opts)
|
|
|
|
if msg is Object and msg.get_class() in log_overwrites:
|
|
return log_overwrites.get(msg.get_class()).call(msg, opts)
|
|
elif typeof(msg) in log_overwrites:
|
|
return log_overwrites.get(typeof(msg)).call(msg, opts)
|
|
|
|
# objects
|
|
if msg is Object and msg.has_method("to_pretty"):
|
|
return Log.to_pretty(msg.to_pretty(), opts)
|
|
if msg is Object and msg.has_method("data"):
|
|
return Log.to_pretty(msg.data(), opts)
|
|
if msg is Object and msg.has_method("to_printable"):
|
|
return Log.to_pretty(msg.to_printable(), opts)
|
|
|
|
# arrays
|
|
if msg is Array or msg is PackedStringArray:
|
|
if len(msg) > Log.get_max_array_size():
|
|
pr("[DEBUG]: truncating large array. total:", len(msg))
|
|
msg = msg.slice(0, Log.get_max_array_size() - 1)
|
|
if newlines:
|
|
msg.append("...")
|
|
|
|
var tmp = Log.color_wrap("[ ", opts)
|
|
var last = len(msg) - 1
|
|
for i in range(len(msg)):
|
|
if newlines and last > 1:
|
|
tmp += "\n\t"
|
|
tmp += Log.to_pretty(msg[i],
|
|
# duplicate here to prevent indenting-per-msg
|
|
# e.g. when printing an array of dictionaries
|
|
opts.duplicate(true))
|
|
if i != last:
|
|
tmp += Log.color_wrap(", ", opts)
|
|
tmp += Log.color_wrap(" ]", opts)
|
|
return tmp
|
|
|
|
# dictionary
|
|
elif msg is Dictionary:
|
|
var tmp = Log.color_wrap("{ ", opts)
|
|
var ct = len(msg)
|
|
var last
|
|
if len(msg) > 0:
|
|
last = msg.keys()[-1]
|
|
for k in msg.keys():
|
|
var val
|
|
if k in Log.get_dictionary_skip_keys():
|
|
val = "..."
|
|
else:
|
|
opts.indent_level += 1
|
|
val = Log.to_pretty(msg[k], opts)
|
|
if newlines and ct > 1:
|
|
tmp += "\n\t" \
|
|
+ range(indent_level)\
|
|
.map(func(_i): return "\t")\
|
|
.reduce(func(a, b): return str(a, b), "")
|
|
if use_color:
|
|
var key = Log.color_wrap('"%s"' % k, Log.assoc(opts, "typeof", "dict_key"))
|
|
tmp += "%s: %s" % [key, val]
|
|
else:
|
|
tmp += '"%s": %s' % [k, val]
|
|
if last and str(k) != str(last):
|
|
tmp += Log.color_wrap(", ", opts)
|
|
tmp += Log.color_wrap(" }", opts)
|
|
return tmp
|
|
|
|
# strings
|
|
elif msg is String:
|
|
if msg == "":
|
|
return '""'
|
|
if "[color=" in msg and "[/color]" in msg:
|
|
# assumes the string is already colorized
|
|
# NOT PERFECT! could use a regex for something more robust
|
|
return msg
|
|
return Log.color_wrap(msg, opts)
|
|
elif msg is StringName:
|
|
return str(Log.color_wrap("&", opts), '"%s"' % msg)
|
|
elif msg is NodePath:
|
|
return str(Log.color_wrap("^", opts), '"%s"' % msg)
|
|
|
|
# vectors
|
|
elif msg is Vector2 or msg is Vector2i:
|
|
return log_overwrites.get("Vector2").call(msg, opts)
|
|
|
|
elif msg is Vector3 or msg is Vector3i:
|
|
if use_color:
|
|
return '%s%s%s%s%s%s%s' % [
|
|
Log.color_wrap("(", opts),
|
|
Log.color_wrap(msg.x, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.y, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.z, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(")", opts),
|
|
]
|
|
else:
|
|
return '(%s,%s,%s)' % [msg.x, msg.y, msg.z]
|
|
elif msg is Vector4 or msg is Vector4i:
|
|
if use_color:
|
|
return '%s%s%s%s%s%s%s%s%s' % [
|
|
Log.color_wrap("(", opts),
|
|
Log.color_wrap(msg.x, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.y, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.z, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(",", opts),
|
|
Log.color_wrap(msg.w, Log.assoc(opts, "typeof", "vector_value")),
|
|
Log.color_wrap(")", opts),
|
|
]
|
|
else:
|
|
return '(%s,%s,%s,%s)' % [msg.x, msg.y, msg.z, msg.w]
|
|
|
|
# packed scene
|
|
elif msg is PackedScene:
|
|
if msg.resource_path != "":
|
|
return str(Log.color_wrap("PackedScene:", opts), '%s' % msg.resource_path.get_file())
|
|
elif msg.get_script() != null and msg.get_script().resource_path != "":
|
|
return Log.color_wrap(msg.get_script().resource_path.get_file(), Log.assoc(opts, "typeof", "class_name"))
|
|
else:
|
|
return Log.color_wrap(msg, opts)
|
|
|
|
# resource
|
|
elif msg is Resource:
|
|
if msg.get_script() != null and msg.get_script().resource_path != "":
|
|
return Log.color_wrap(msg.get_script().resource_path.get_file(), Log.assoc(opts, "typeof", "class_name"))
|
|
elif msg.resource_path != "":
|
|
return str(Log.color_wrap("Resource:", opts), '%s' % msg.resource_path.get_file())
|
|
else:
|
|
return Log.color_wrap(msg, opts)
|
|
|
|
# refcounted
|
|
elif msg is RefCounted:
|
|
if msg.get_script() != null and msg.get_script().resource_path != "":
|
|
return Log.color_wrap(msg.get_script().resource_path.get_file(), Log.assoc(opts, "typeof", "class_name"))
|
|
else:
|
|
return Log.color_wrap(msg.get_class(), Log.assoc(opts, "typeof", "class_name"))
|
|
|
|
# fallback to primitive-type lookup
|
|
else:
|
|
return Log.color_wrap(msg, opts)
|
|
|
|
## to_printable ###########################################################################
|
|
|
|
static func log_prefix(stack):
|
|
if len(stack) > 1:
|
|
var call_site = stack[1]
|
|
var basename = call_site["source"].get_file().get_basename()
|
|
var line_num = str(call_site.get("line", 0))
|
|
if call_site["source"].match("*/test/*"):
|
|
return "{" + basename + ":" + line_num + "}: "
|
|
elif call_site["source"].match("*/addons/*"):
|
|
return "<" + basename + ":" + line_num + ">: "
|
|
else:
|
|
return "[" + basename + ":" + line_num + "]: "
|
|
|
|
static func to_printable(msgs, opts={}):
|
|
var stack = opts.get("stack", [])
|
|
var pretty = opts.get("pretty", true)
|
|
var newlines = opts.get("newlines", false)
|
|
var m = ""
|
|
if len(stack) > 0:
|
|
var prefix = Log.log_prefix(stack)
|
|
var prefix_type
|
|
if prefix != null and prefix[0] == "[":
|
|
prefix_type = "SRC"
|
|
elif prefix != null and prefix[0] == "{":
|
|
prefix_type = "TEST"
|
|
elif prefix != null and prefix[0] == "<":
|
|
prefix_type = "ADDONS"
|
|
if pretty:
|
|
m += Log.color_wrap(prefix, Log.assoc(opts, "typeof", prefix_type))
|
|
else:
|
|
m += prefix
|
|
for msg in msgs:
|
|
# add a space between msgs
|
|
if pretty:
|
|
m += "%s " % Log.to_pretty(msg, opts)
|
|
else:
|
|
m += "%s " % str(msg)
|
|
return m.trim_suffix(" ")
|
|
|
|
## public print fns ###########################################################################
|
|
|
|
static func is_not_default(v):
|
|
return not v is String or (v is String and v != "ZZZDEF")
|
|
|
|
static func pr(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var m = Log.to_printable(msgs, {stack=get_stack()})
|
|
print_rich(m)
|
|
|
|
static func info(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var m = Log.to_printable(msgs, {stack=get_stack()})
|
|
print_rich(m)
|
|
|
|
static func log(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var m = Log.to_printable(msgs, {stack=get_stack()})
|
|
print_rich(m)
|
|
|
|
static func prn(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var m = Log.to_printable(msgs, {stack=get_stack(), newlines=true})
|
|
print_rich(m)
|
|
|
|
static func warn(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var rich_msgs = msgs.duplicate()
|
|
rich_msgs.push_front("[color=yellow][WARN][/color]")
|
|
print_rich(Log.to_printable(rich_msgs, {stack=get_stack(), newlines=true}))
|
|
var m = Log.to_printable(msgs, {stack=get_stack(), newlines=true, pretty=false})
|
|
push_warning(m)
|
|
|
|
static func err(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var rich_msgs = msgs.duplicate()
|
|
rich_msgs.push_front("[color=red][ERR][/color]")
|
|
print_rich(Log.to_printable(rich_msgs, {stack=get_stack(), newlines=true}))
|
|
var m = Log.to_printable(msgs, {stack=get_stack(), newlines=true, pretty=false})
|
|
push_error(m)
|
|
|
|
static func error(msg, msg2="ZZZDEF", msg3="ZZZDEF", msg4="ZZZDEF", msg5="ZZZDEF", msg6="ZZZDEF", msg7="ZZZDEF"):
|
|
var msgs = [msg, msg2, msg3, msg4, msg5, msg6, msg7]
|
|
msgs = msgs.filter(Log.is_not_default)
|
|
var rich_msgs = msgs.duplicate()
|
|
rich_msgs.push_front("[color=red][ERR][/color]")
|
|
print_rich(Log.to_printable(rich_msgs, {stack=get_stack(), newlines=true}))
|
|
var m = Log.to_printable(msgs, {stack=get_stack(), newlines=true, pretty=false})
|
|
push_error(m)
|