Sets up initial project structure

Initializes the project with core files including:

- Editor configuration (.editorconfig, .gitattributes, .gitignore, .vscode/settings.json)
- Log.gd addon for enhanced debugging
- Loggie addon for advanced logging
- Project assets folder
This commit is contained in:
Dan Baker 2025-04-29 17:35:39 +01:00
parent f8140c83ff
commit 02b3be35b0
144 changed files with 7399 additions and 0 deletions

21
addons/log/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Russell Matney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

621
addons/log/log.gd Normal file
View file

@ -0,0 +1,621 @@
## Log.gd - colorized pretty printing functions
##
## [code]Log.pr(...)[/code] and [code]Log.prn(...)[/code] are drop-in replacements for [code]print(...)[/code].
##
## [br][br]
## You can also [code]Log.warn(...)[/code] or [code]Log.error(...)[/code] to both print and push_warn/push_error.
##
## [br][br]
## Custom object output is supported by implementing [code]to_pretty()[/code] on the object.
##
## [br][br]
## For objects you don't own (built-ins or addons you don't want to edit),
## there is a [code]register_type_overwrite(key, handler)[/code] helper.
##
## [br][br]
## You can find up to date docs and examples in the Log.gd repo and docs site:
## [br]
## - https://github.com/russmatney/log.gd
## [br]
## - https://russmatney.github.io/log.gd
##
@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 ####################################
const KEY_PREFIX = "log_gd/config"
static var is_config_setup = false
const KEY_COLOR_THEME = "%s/color_theme" % KEY_PREFIX
const KEY_DISABLE_COLORS = "%s/disable_colors" % KEY_PREFIX
const KEY_MAX_ARRAY_SIZE = "%s/max_array_size" % KEY_PREFIX
const KEY_SKIP_KEYS = "%s/dictionary_skip_keys" % KEY_PREFIX
static func setup_config(opts={}):
var keys = opts.get("keys", [
KEY_COLOR_THEME,
KEY_DISABLE_COLORS,
KEY_MAX_ARRAY_SIZE,
KEY_SKIP_KEYS,
])
for key in keys:
if ProjectSettings.has_setting(key):
Log.config[key] = ProjectSettings.get_setting(key)
else:
var val = Log.config[key]
if val != null:
ProjectSettings.set_setting(key, val)
ProjectSettings.set_initial_value(key, val)
Log.is_config_setup = true
static var config = {
KEY_COLOR_THEME: LOG_THEME_TERMSAFE,
KEY_DISABLE_COLORS: false,
KEY_MAX_ARRAY_SIZE: 20,
KEY_SKIP_KEYS: [
"layer_0/tile_data", # skip huge tilemap arrays
],
}
# config getters ###################################################################
static func get_max_array_size():
return Log.config.get(KEY_MAX_ARRAY_SIZE, 20)
static func get_dictionary_skip_keys():
return Log.config.get(KEY_SKIP_KEYS, [])
static func get_disable_colors():
return Log.config.get(KEY_DISABLE_COLORS, false)
static func get_config_color_theme():
var theme_id = Log.config.get(KEY_COLOR_THEME, LOG_THEME_TERMSAFE)
match theme_id:
LOG_THEME_TERMSAFE:
return Log.COLORS_TERMINAL_SAFE
LOG_THEME_PRETTY_V1:
return Log.COLORS_PRETTY_V1
_:
print("Unknown LOG_THEME '%s', using fallback" % theme_id)
return Log.COLORS_TERMINAL_SAFE
# config setters ###################################################################
## Disable color-wrapping output.
##
## [br][br]
## Useful to declutter the output if the environment does not support colors.
## Note that some environments support only a subset of colors - you may prefer
## [code]set_colors_termsafe()[/code] or otherwise setting the theme to 'TERMSAFE'.
static func disable_colors():
Log.config[KEY_DISABLE_COLORS] = true
## Re-enable color-wrapping output.
static func enable_colors():
Log.config[KEY_DISABLE_COLORS] = false
## DEPRECATED
static func set_color_scheme(theme):
set_color_theme(theme)
static func set_color_theme(theme):
Log.config[KEY_COLOR_THEME] = theme
## colors ###########################################################################
# terminal safe colors:
# - black
# - red
# - green
# - yellow
# - blue
# - magenta
# - pink
# - purple
# - cyan
# - white
# - orange
# - gray
const LOG_THEME_TERMSAFE = "TERMSAFE"
const LOG_THEME_PRETTY_V1 = "PRETTY_V1"
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 theme ####################################
## Use the terminal safe color scheme, which should handle colors in most tty-like environments.
static func set_colors_termsafe():
set_color_theme(LOG_THEME_TERMSAFE)
## Use prettier colors - looks nice in most dark godot themes.
##
## [br][br]
## Hopefully we'll support more themes (including light themes) soon!
static func set_colors_pretty():
set_color_theme(LOG_THEME_PRETTY_V1)
static var theme_overwrites = {}
## Merge per type color adjustments.
##
## [br][br]
## Expects a Dictionary from [code]{typeof(obj): Color}[/code].
## See [code]COLORS_TERMINAL_SAFE[/code] for an example.
static func merge_theme_overwrites(colors):
theme_overwrites.merge(colors, true)
static func clear_theme_overwrites():
theme_overwrites = {}
static func get_color_theme(opts={}):
var theme = opts.get("color_theme", {})
# fill in any missing vals with the set theme, then the term-safe fallbacks
theme.merge(Log.theme_overwrites)
theme.merge(Log.get_config_color_theme())
theme.merge(Log.COLORS_TERMINAL_SAFE)
return theme
static func should_use_color(opts={}):
if Log.get_disable_colors():
return false
# supports per-print color skipping
if opts.get("disable_colors", false):
return false
return true
static func color_wrap(s, opts={}):
# don't rebuild the theme every time
var colors = opts.get("built_color_theme", get_color_theme(opts))
if not should_use_color(opts):
return str(s)
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]
## overwrites ###########################################################################
static var type_overwrites = {}
## Register a single type overwrite.
##
## [br][br]
## The key should be either obj.get_class() or typeof(var). (Note that using typeof(var) may overwrite more broadly than expected).
##
## [br][br]
## The handler is called with the object and an options dict.
## [code]func(obj, _opts): return {name=obj.name}[/code]
static func register_type_overwrite(key, handler):
# TODO warning on key exists? support multiple handlers for same type?
# validate the key/handler somehow?
type_overwrites[key] = handler
## Register a dictionary of type overwrite.
##
## [br][br]
## Expects a Dictionary like [code]{obj.get_class(): func(obj, opts): return {key=obj.get_key()}}[/code].
##
## [br][br]
## It depends on [code]obj.get_class()[/code] then [code]typeof(obj)[/code] for the key.
## The handler is called with the object and an options dict (e.g. [code]func(obj, _opts): return {name=obj.name}[/code]).
static func register_type_overwrites(overwrites):
type_overwrites.merge(overwrites, true)
static func clear_type_overwrites(overwrites):
type_overwrites = {}
## to_pretty ###########################################################################
## Returns the passed object as a bb-colorized string.
##
## [br][br]
## Useful for feeding directly into a RichTextLabel, but also the core
## of Log.gd's functionality.
static func to_pretty(msg, opts={}) -> String:
var newlines = opts.get("newlines", false)
var indent_level = opts.get("indent_level", 0)
if not "indent_level" in opts:
opts["indent_level"] = indent_level
var theme = opts.get("built_color_theme", get_color_theme(opts))
if not "built_color_theme" in opts:
opts["built_color_theme"] = theme
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 type_overwrites:
# TODO support single arity (no opts) impls?
return type_overwrites.get(msg.get_class()).call(msg, opts)
elif typeof(msg) in type_overwrites:
return type_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)
# DEPRECATED
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), "")
var key = Log.color_wrap('"%s"' % k, Log.assoc(opts, "typeof", "dict_key"))
tmp += "%s: %s" % [key, 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 '%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),
]
elif msg is Vector3 or msg is Vector3i:
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),
]
elif msg is Vector4 or msg is Vector4i:
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),
]
# 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) -> String:
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 + "]: "
return ""
static func to_printable(msgs, opts={}) -> String:
if not Log.is_config_setup:
setup_config()
if not msgs is Array:
msgs = [msgs]
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) -> bool:
return not v is String or (v is String and v != "ZZZDEF")
## Pretty-print the passed arguments in a single line.
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)
## Pretty-print the passed arguments in a single line.
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)
## Pretty-print the passed arguments in a single line.
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)
## Pretty-print the passed arguments, expanding dictionaries and arrays with newlines and indentation.
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)
## Like [code]Log.prn()[/code], but also calls push_warning() with the pretty string.
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)
## Like [code]Log.prn()[/code], but also calls push_error() with the pretty string.
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)
## Like [code]Log.prn()[/code], but also calls push_error() with the pretty string.
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)

1
addons/log/log.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dav5jaue8fbfo

14
addons/log/plugin.cfg Normal file
View file

@ -0,0 +1,14 @@
[plugin]
name="Log.gd"
description="A pretty-printing debug logger.
Log.pr(\"some str\", some_object)
- Colorizes printed data based on datatype
- Handles nested data structures (Arrays and Dictionaries)
- Prefixes logs with the callsite's source file
- Opt-in to pretty printing via duck-typing (implement a `to_pretty()` method on the object)"
author="Russell Matney"
version="v0.0.6"
script="plugin.gd"

8
addons/log/plugin.gd Normal file
View file

@ -0,0 +1,8 @@
@tool
extends EditorPlugin
func _enter_tree():
ProjectSettings.settings_changed.connect(on_settings_changed)
func on_settings_changed():
Log.setup_config()

1
addons/log/plugin.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://dnhb0udygmnot