Code stuff

This commit is contained in:
Melody Becker 2025-01-26 20:23:56 +01:00
parent cf22890c16
commit e58093b5a5
Signed by: mstar
SSH key fingerprint: SHA256:vkXfS9FG2pVNVfvDrzd1VW9n8VJzqqdKQGljxxX8uK8
153 changed files with 11196 additions and 4 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://2fr6et0qni2y"
path="res://.godot/imported/icon.png-57313fc4d67f18c33a83a3388ad36531.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie/assets/icon.png"
dest_files=["res://.godot/imported/icon.png-57313fc4d67f18c33a83a3388ad36531.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://duuj0ibyreygv"
path="res://.godot/imported/logo.png-2771abf7e361d7f10d5859133bc43562.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/loggie/assets/logo.png"
dest_files=["res://.godot/imported/logo.png-2771abf7e361d7f10d5859133bc43562.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,53 @@
class_name CustomLoggieSettings extends LoggieSettings
func load():
# Omit settings from here to have them use their default value instead.
# Otherwise, directly set the value of the setting to your liking.
# Any variable in [LoggieSettings] is a valid target to alter.
# You could also have them loaded here in some custom way, for example, from a .json or .ini file.
# See the documentation of the [LoggieSettings] class to see all available options and their descriptions.
self.terminal_mode = LoggieEnums.TerminalMode.BBCODE
self.log_level = LoggieEnums.LogLevel.INFO
self.show_loggie_specs = LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL
self.show_system_specs = true
self.enforce_optimal_settings_in_release_build = true
self.output_message_domain = true
self.print_errors_to_console = true
self.print_warnings_to_console = true
self.use_print_debug_for_debug_msg = true
self.derive_and_show_class_names = true
self.nameless_class_name_proxy = LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE
self.output_timestamps = true
self.timestamps_use_utc = true
self.format_timestamp = "[{day}.{month}.{year} {hour}:{minute}:{second}]"
self.format_info_msg = "{msg}"
self.format_notice_msg = "[b][color=cyan][NOTICE]:[/color][/b] {msg}"
self.format_warning_msg = "[b][color=orange][WARN]:[/color][/b] {msg}"
self.format_error_msg = "[b][color=red][ERROR]:[/color][/b] {msg}"
self.format_debug_msg = "[b][color=pink][DEBUG]:[/color][/b] {msg}"
self.format_header = "[b][i]{msg}[/i][/b]"
self.format_domain_prefix = "[b]({domain})[/b] {msg}"
self.h_separator_symbol = "-"
self.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE
self.box_symbols_compatible = {
"top_left" : "-",
"top_right" : "-",
"bottom_left" : "-",
"bottom_right" : "-",
"h_line" : "-",
"v_line" : ":",
}
self.box_symbols_pretty = {
"top_left" : "┌",
"top_right" : "┐",
"bottom_left" : "└",
"bottom_right" : "┘",
"h_line" : "─",
"v_line" : "│",
}

167
addons/loggie/loggie.gd Normal file
View file

@ -0,0 +1,167 @@
@tool
## Loggie is a basic logging utility for those who need common minor improvements and helpers around the basic [method print], [method print_rich]
## and other default Godot printing functions. Loggie creates instances of [LoggieMsg], which are a wrapper around a string that needs to manipulated,
## then uses them to properly format, arrange and present them in the console and .log files. Loggie uses the default Godot logging mechanism under the hood.
extends Node
## Stores a string describing the current version of Loggie.
const VERSION : String = "v1.5"
## Emitted any time Loggie attempts to log a message.
## Useful for capturing the messages that pass through Loggie.
## [br][param msg] is the message Loggie attempted to log (before any preprocessing).
## [br][param preprocessed_content] is what the string content of that message contained after the preprocessing step,
## which is what ultimately gets logged.
## [br][param result] describes the final result of the attempt to log that message.
signal log_attempted(msg : LoggieMsg, preprocessed_content : String, result : LoggieEnums.LogAttemptResult)
## A reference to the settings of this Loggie. Read more about [LoggieSettings].
var settings : LoggieSettings
## Holds a mapping between all registered domains (string keys) and bool values representing whether
## those domains are currently enabled. Enable domains with [method set_domain_enabled].
## You can then place [LoggieMsg] messages into a domain by calling [method LoggieMsg.domain].
## Messages belonging to a disabled domain will never be outputted.
var domains : Dictionary = {}
## Holds a mapping between script paths and the names of the classes defined in those scripts.
var class_names : Dictionary = {}
func _init() -> void:
var uses_original_settings_file = true
var default_settings_path = get_script().get_path().get_base_dir().path_join("loggie_settings.gd")
var custom_settings_path = get_script().get_path().get_base_dir().path_join("custom_settings.gd")
if self.settings == null:
if custom_settings_path != null and custom_settings_path != "" and ResourceLoader.exists(custom_settings_path):
var loaded_successfully = load_settings_from_path(custom_settings_path)
if loaded_successfully:
uses_original_settings_file = false
if uses_original_settings_file:
var _settings = ResourceLoader.load(default_settings_path)
if _settings != null:
self.settings = _settings.new()
self.settings.load()
else:
push_error("Loggie loaded neither a custom nor a default settings file. This will break the plugin. Make sure that a valid loggie_settings.gd is in the same directory where loggie.gd is.")
return
if self.settings.enforce_optimal_settings_in_release_build == true and is_in_production():
self.settings.terminal_mode = LoggieEnums.TerminalMode.PLAIN
self.settings.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE
# Already cache the name of the singleton found at loggie's script path.
class_names[self.get_script().resource_path] = LoggieSettings.loggie_singleton_name
# Prepopulate class data from ProjectSettings to avoid needing to read files.
if self.settings.derive_and_show_class_names == true and OS.has_feature("debug"):
for class_data: Dictionary in ProjectSettings.get_global_class_list():
class_names[class_data.path] = class_data.class
for autoload_setting: String in ProjectSettings.get_property_list().map(func(prop): return prop.name).filter(func(prop): return prop.begins_with("autoload/") and ProjectSettings.has_setting(prop)):
var autoload_class: String = autoload_setting.trim_prefix("autoload/")
var class_path: String = ProjectSettings.get_setting(autoload_setting)
class_path = class_path.trim_prefix("*")
if not class_names.has(class_path):
class_names[class_path] = autoload_class
# Don't print Loggie boot messages if Loggie is running only from the editor.
if Engine.is_editor_hint():
return
if self.settings.show_loggie_specs != LoggieEnums.ShowLoggieSpecsMode.DISABLED:
msg("👀 Loggie {version} booted.".format({"version" : self.VERSION})).color(Color.ORANGE).header().nl().info()
var loggie_specs_msg = LoggieSystemSpecsMsg.new().use_logger(self)
loggie_specs_msg.add(msg("|\t Using Custom Settings File: ").bold(), !uses_original_settings_file).nl().add("|\t ").hseparator(35).nl()
match self.settings.show_loggie_specs:
LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL:
loggie_specs_msg.embed_essential_logger_specs()
LoggieEnums.ShowLoggieSpecsMode.ADVANCED:
loggie_specs_msg.embed_advanced_logger_specs()
loggie_specs_msg.preprocessed(false).info()
if self.settings.show_system_specs:
var system_specs_msg = LoggieSystemSpecsMsg.new().use_logger(self)
system_specs_msg.embed_specs().preprocessed(false).info()
## Attempts to instantiate a LoggieSettings object from the script at the given [param path].
## Returns true if successful, otherwise false and prints an error.
func load_settings_from_path(path : String) -> bool:
var settings_resource = ResourceLoader.load(path)
var settings_instance
if settings_resource != null:
settings_instance = settings_resource.new()
if (settings_instance is LoggieSettings):
self.settings = settings_instance
self.settings.load()
return true
else:
push_error("Unable to instantiate a LoggieSettings object from the script at path {path}. Check that loggie.gd -> custom_settings_path is pointing to a valid .gd script that contains the class definition of a class that either extends LoggieSettings, or is LoggieSettings.".format({"path": path}))
return false
## Checks if Loggie is running in production (release) mode of the game.
## While it is, every [LoggieMsg] will have plain output.
## Uses a sensible default check for most projects, but
## you can rewrite this function to your needs if necessary.
func is_in_production() -> bool:
return OS.has_feature("release")
## Sets whether the domain with the given name is enabled.
func set_domain_enabled(domain_name : String, enabled : bool) -> void:
domains[domain_name] = enabled
## Checks whether the domain with the given name is enabled.
## The domain name "" (empty string) is the default one for all newly created messages,
## and is designed to always be enabled.
func is_domain_enabled(domain_name : String) -> bool:
if domain_name == "":
return true
if domains.has(domain_name) and domains[domain_name] == true:
return true
return false
## Creates a new [LoggieMsg] out of the given [param msg] and extra arguments (by converting them to strings and concatenating them to the msg).
## You may continue to modify the [LoggieMsg] with additional functions from that class, then when you are ready to output it, use methods like:
## [method LoggieMsg.info], [method LoggieMsg.warn], etc.
func msg(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
var loggieMsg = LoggieMsg.new(message, arg1, arg2, arg3, arg4, arg5)
loggieMsg.use_logger(self)
return loggieMsg
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the info level.
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
## For customization, use [method msg] instead.
func info(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
return msg(message, arg1, arg2, arg3, arg4, arg5).info()
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the warn level.
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
## For customization, use [method msg] instead.
func warn(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
return msg(message, arg1, arg2, arg3, arg4, arg5).warn()
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the error level.
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
## For customization, use [method msg] instead.
func error(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
return msg(message, arg1, arg2, arg3, arg4, arg5).error()
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the debug level.
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
## For customization, use [method msg] instead.
func debug(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
return msg(message, arg1, arg2, arg3, arg4, arg5).debug()
## A shortcut method that instantly creates a [LoggieMsg] with the given arguments and outputs it at the notice level.
## Can be used when you have no intention of customizing a LoggieMsg in any way using helper methods.
## For customization, use [method msg] instead.
func notice(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
return msg(message, arg1, arg2, arg3, arg4, arg5).notice()

View file

@ -0,0 +1,344 @@
@tool
## LoggieMsg represents a mutable object that holds an array of strings ([member content]) [i](referred to as 'content segments')[/i], and
## a bunch of helper methods that make it easy to manipulate these segments and chain together additions and changes to them.
## [br][br]For example:
## [codeblock]
### Prints: "Hello world!" at the INFO debug level.
##var msg = LoggieMsg.new("Hello world").color(Color.RED).suffix("!").info()
##[/codeblock]
## [br] You can also use [method Loggie.msg] to quickly construct a message.
## [br] Example of usage:
## [codeblock]Loggie.msg("Hello world").color(Color("#ffffff")).suffix("!").info()[/codeblock]
class_name LoggieMsg extends RefCounted
## The full content of this message. By calling various helper methods in this class, this content is further altered.
## The content is an array of strings which represents segments of the message which are ultimately appended together
## to form the final message. You can start a new segment by calling [method msg] on this class.
## You can then output the whole message with methods like [method info], [method debug], etc.
var content : Array = [""]
## The segment of [member content] that is currently being edited.
var current_segment_index : int = 0
## The (key string) domain this message belongs to.
## "" is the default domain which is always enabled.
## If this message attempts to be outputted, but belongs to a disabled domain, it will not be outputted.
## You can change which domains are enabled in Loggie at any time with [Loggie.set_domain_enabled].
## This is useful for creating blocks of debugging output that you can simply turn off/on with a boolean when you actually need them.
var domain_name : String = ""
## Whether this message should be preprocessed and modified during [method output].
var preprocess : bool = true
## Stores a reference to the logger that generated this message, from which we need to read settings and other data.
## This variable should be set with [method use_logger] before an attempt is made to log this message out.
var _logger : Variant
func _init(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> void:
self.content[current_segment_index] = LoggieTools.concatenate_msg_and_args(message, arg1, arg2, arg3, arg4, arg5)
## Returns a reference to the logger object that created this message.
func get_logger() -> Variant:
return self._logger
## Sets this message to use the given [param logger] as the logger from which it will be reading
## settings. The given logger should be of class [Loggie] or an extension of it.
func use_logger(logger_to_use : Variant) -> LoggieMsg:
self._logger = logger_to_use
return self
## Outputs the given string [param msg] at the given output [param level] to the standard output using either [method print_rich] or [method print].
## The domain from which the message is considered to be coming can be provided via [param target_domain].
## The classification of the message can be provided via [param msg_type], as certain types need extra handling and treatment.
## It also does a number of changes to the given [param msg] based on various Loggie settings.
## Designed to be called internally. You should consider using [method info], [method error], [method warn], [method notice], [method debug] instead.
func output(level : LoggieEnums.LogLevel, message : String, target_domain : String = "", msg_type : LoggieEnums.MsgType = LoggieEnums.MsgType.STANDARD) -> void:
var loggie = get_logger()
if loggie == null:
push_error("Attempt to log output with an invalid _logger. Make sure to call LoggieMsg.use_logger to set the appropriate logger before working with the message.")
return
if loggie.settings == null:
push_error("Attempt to use a _logger with invalid settings.")
return
# We don't output the message if the settings dictate that messages of that level shouldn't be outputted.
if level > loggie.settings.log_level:
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.LOG_LEVEL_INSUFFICIENT)
return
# We don't output the message if the domain from which it comes is not enabled.
if not loggie.is_domain_enabled(target_domain):
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.DOMAIN_DISABLED)
return
# Apply the matching formatting to the message based on the log level.
match level:
LoggieEnums.LogLevel.ERROR:
message = loggie.settings.format_error_msg.format({"msg": message})
LoggieEnums.LogLevel.WARN:
message = loggie.settings.format_warning_msg.format({"msg": message})
LoggieEnums.LogLevel.NOTICE:
message = loggie.settings.format_notice_msg.format({"msg": message})
LoggieEnums.LogLevel.INFO:
message = loggie.settings.format_info_msg.format({"msg": message})
LoggieEnums.LogLevel.DEBUG:
message = loggie.settings.format_debug_msg.format({"msg": message})
# Enter the preprocess tep unless it is disabled.
if self.preprocess:
# We append the name of the domain if that setting is enabled.
if !target_domain.is_empty() and loggie.settings.output_message_domain == true:
message = loggie.settings.format_domain_prefix.format({"domain" : target_domain, "msg" : message})
# We prepend the name of the class that called the function which resulted in this output being generated
# (if Loggie settings are configured to do so).
if loggie.settings.derive_and_show_class_names == true and OS.has_feature("debug"):
var stack_frame : Dictionary = LoggieTools.get_current_stack_frame_data()
var _class_name : String
var scriptPath = stack_frame.source
if loggie.class_names.has(scriptPath):
_class_name = loggie.class_names[scriptPath]
else:
_class_name = LoggieTools.get_class_name_from_script(scriptPath, loggie.settings.nameless_class_name_proxy)
loggie.class_names[scriptPath] = _class_name
if _class_name != "":
message = "[b]({class_name})[/b] {msg}".format({
"class_name" : _class_name,
"msg" : message
})
# We prepend a timestamp to the message (if Loggie settings are configured to do so).
if loggie.settings.output_timestamps == true:
var format_dict : Dictionary = Time.get_datetime_dict_from_system(loggie.settings.timestamps_use_utc)
for field in ["month", "day", "hour", "minute", "second"]:
format_dict[field] = "%02d" % format_dict[field]
message = "{formatted_time} {msg}".format({
"formatted_time" : loggie.settings.format_timestamp.format(format_dict),
"msg" : message
})
# Prepare the preprocessed message to be output in the terminal mode of choice.
message = LoggieTools.get_terminal_ready_string(message, loggie.settings.terminal_mode)
# Output the preprocessed message.
match loggie.settings.terminal_mode:
LoggieEnums.TerminalMode.ANSI, LoggieEnums.TerminalMode.BBCODE:
print_rich(message)
LoggieEnums.TerminalMode.PLAIN, _:
print(message)
# Dump a non-preprocessed terminal-ready version of the message in additional ways if that has been configured.
if msg_type == LoggieEnums.MsgType.ERROR and loggie.settings.print_errors_to_console:
push_error(LoggieTools.get_terminal_ready_string(self.string(), LoggieEnums.TerminalMode.PLAIN))
if msg_type == LoggieEnums.MsgType.WARNING and loggie.settings.print_warnings_to_console:
push_warning(LoggieTools.get_terminal_ready_string(self.string(), LoggieEnums.TerminalMode.PLAIN))
if msg_type == LoggieEnums.MsgType.DEBUG and loggie.settings.use_print_debug_for_debug_msg:
print_debug(LoggieTools.get_terminal_ready_string(self.string(), loggie.settings.terminal_mode))
loggie.log_attempted.emit(self, message, LoggieEnums.LogAttemptResult.SUCCESS)
## Outputs this message from Loggie as an Error type message.
## The [Loggie.settings.log_level] must be equal to or higher to the ERROR level for this to work.
func error() -> LoggieMsg:
output(LoggieEnums.LogLevel.ERROR, self.string(), self.domain_name, LoggieEnums.MsgType.ERROR)
return self
## Outputs this message from Loggie as an Warning type message.
## The [Loggie.settings.log_level] must be equal to or higher to the WARN level for this to work.
func warn() -> LoggieMsg:
output(LoggieEnums.LogLevel.WARN, self.string(), self.domain_name, LoggieEnums.MsgType.WARNING)
return self
## Outputs this message from Loggie as an Notice type message.
## The [Loggie.settings.log_level] must be equal to or higher to the NOTICE level for this to work.
func notice() -> LoggieMsg:
output(LoggieEnums.LogLevel.NOTICE, self.string(), self.domain_name)
return self
## Outputs this message from Loggie as an Info type message.
## The [Loggie.settings.log_level] must be equal to or higher to the INFO level for this to work.
func info() -> LoggieMsg:
output(LoggieEnums.LogLevel.INFO, self.string(), self.domain_name)
return self
## Outputs this message from Loggie as a Debug type message.
## The [Loggie.settings.log_level] must be equal to or higher to the DEBUG level for this to work.
func debug() -> LoggieMsg:
output(LoggieEnums.LogLevel.DEBUG, self.string(), self.domain_name, LoggieEnums.MsgType.DEBUG)
return self
## Returns the string content of this message.
## If [param segment] is provided, it should be an integer indicating which segment of the message to return.
## If its value is -1, all segments are concatenated together and returned.
func string(segment : int = -1) -> String:
if segment == -1:
return "".join(self.content)
else:
if segment < self.content.size():
return self.content[segment]
else:
push_error("Attempt to access a non-existent segment of a LoggieMsg. Make sure to use a valid segment index.")
return ""
## Converts the current content of this message to an ANSI compatible form.
func to_ANSI() -> LoggieMsg:
var new_content : Array = []
for segment in self.content:
new_content.append(LoggieTools.rich_to_ANSI(segment))
self.content = new_content
return self
## Strips all the BBCode in the current content of this message.
func strip_BBCode() -> LoggieMsg:
var new_content : Array = []
for segment in self.content:
new_content.append(LoggieTools.remove_BBCode(segment))
self.content = new_content
return self
## Wraps the content of the current segment of this message in the given color.
## The [param color] can be provided as a [Color], a recognized Godot color name (String, e.g. "red"), or a color hex code (String, e.g. "#ff0000").
func color(_color : Variant) -> LoggieMsg:
if _color is Color:
_color = _color.to_html()
self.content[current_segment_index] = "[color={colorstr}]{msg}[/color]".format({
"colorstr": _color,
"msg": self.content[current_segment_index]
})
return self
## Stylizes the current segment of this message to be bold.
func bold() -> LoggieMsg:
self.content[current_segment_index] = "[b]{msg}[/b]".format({"msg": self.content[current_segment_index]})
return self
## Stylizes the current segment of this message to be italic.
func italic() -> LoggieMsg:
self.content[current_segment_index] = "[i]{msg}[/i]".format({"msg": self.content[current_segment_index]})
return self
## Stylizes the current segment of this message as a header.
func header() -> LoggieMsg:
var loggie = get_logger()
self.content[current_segment_index] = loggie.settings.format_header.format({"msg": self.content[current_segment_index]})
return self
## Constructs a decorative box with the given horizontal padding around the current segment
## of this message. Messages containing a box are not going to be preprocessed, so they are best
## used only as a special header or decoration.
func box(h_padding : int = 4):
var loggie = get_logger()
var stripped_content = LoggieTools.remove_BBCode(self.content[current_segment_index]).strip_edges(true, true)
var content_length = stripped_content.length()
var h_fill_length = content_length + (h_padding * 2)
var box_character_source = loggie.settings.box_symbols_compatible if loggie.settings.box_characters_mode == LoggieEnums.BoxCharactersMode.COMPATIBLE else loggie.settings.box_symbols_pretty
var top_row_design = "{top_left_corner}{h_fill}{top_right_corner}".format({
"top_left_corner" : box_character_source.top_left,
"h_fill" : box_character_source.h_line.repeat(h_fill_length),
"top_right_corner" : box_character_source.top_right
})
var middle_row_design = "{vert_line}{padding}{content}{space_fill}{padding}{vert_line}".format({
"vert_line" : box_character_source.v_line,
"content" : self.content[current_segment_index],
"padding" : " ".repeat(h_padding),
"space_fill" : " ".repeat(h_fill_length - stripped_content.length() - h_padding*2)
})
var bottom_row_design = "{bottom_left_corner}{h_fill}{bottom_right_corner}".format({
"bottom_left_corner" : box_character_source.bottom_left,
"h_fill" : box_character_source.h_line.repeat(h_fill_length),
"bottom_right_corner" : box_character_source.bottom_right
})
self.content[current_segment_index] = "{top_row}\n{middle_row}\n{bottom_row}\n".format({
"top_row" : top_row_design,
"middle_row" : middle_row_design,
"bottom_row" : bottom_row_design
})
self.preprocessed(false)
return self
## Appends additional content to this message at the end of the current content and its stylings.
## This does not create a new message segment, just appends to the current one.
func add(message : Variant = null, arg1 : Variant = null, arg2 : Variant = null, arg3 : Variant = null, arg4 : Variant = null, arg5 : Variant = null) -> LoggieMsg:
self.content[current_segment_index] = self.content[current_segment_index] + LoggieTools.concatenate_msg_and_args(message, arg1, arg2, arg3, arg4, arg5)
return self
## Adds a specified amount of newlines to the end of the current segment of this message.
func nl(amount : int = 1) -> LoggieMsg:
self.content[current_segment_index] += "\n".repeat(amount)
return self
## Adds a specified amount of spaces to the end of the current segment of this message.
func space(amount : int = 1) -> LoggieMsg:
self.content[current_segment_index] += " ".repeat(amount)
return self
## Adds a specified amount of tabs to the end of the current segment of this message.
func tab(amount : int = 1) -> LoggieMsg:
self.content[current_segment_index] += "\t".repeat(amount)
return self
## Sets this message to belong to the domain with the given name.
## If it attempts to be outputted, but the domain is disabled, it won't be outputted.
func domain(_domain_name : String) -> LoggieMsg:
self.domain_name = _domain_name
return self
## Prepends the given prefix string to the start of the message (first segment) with the provided separator.
func prefix(str_prefix : String, separator : String = "") -> LoggieMsg:
self.content[0] = "{prefix}{separator}{content}".format({
"prefix" : str_prefix,
"separator" : separator,
"content" : self.content[0]
})
return self
## Appends the given suffix string to the end of the message (last segment) with the provided separator.
func suffix(str_suffix : String, separator : String = "") -> LoggieMsg:
self.content[self.content.size() - 1] = "{content}{separator}{suffix}".format({
"suffix" : str_suffix,
"separator" : separator,
"content" : self.content[self.content.size() - 1]
})
return self
## Appends a horizontal separator with the given length to the current segment of this message.
## If [param alternative_symbol] is provided, it should be a String, and it will be used as the symbol for the separator instead of the default one.
func hseparator(size : int = 16, alternative_symbol : Variant = null) -> LoggieMsg:
var loggie = get_logger()
var symbol = loggie.settings.h_separator_symbol if alternative_symbol == null else str(alternative_symbol)
self.content[current_segment_index] = self.content[current_segment_index] + (symbol.repeat(size))
return self
## Ends the current segment of the message and starts a new one.
func endseg() -> LoggieMsg:
self.content.push_back("")
self.current_segment_index = self.content.size() - 1
return self
## Creates a new segment in this message and sets its content to the given message.
## Acts as a shortcut for calling [method endseg] + [method add].
func msg(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg:
self.endseg()
self.content[current_segment_index] = LoggieTools.concatenate_msg_and_args(message, arg1, arg2, arg3, arg4, arg5)
return self
## Sets whether this message should be preprocessed and potentially modified with prefixes and suffixes during [method output].
## If turned off, while outputting this message, Loggie will skip the steps where it appends the messaage domain, class name, timestamp, etc.
## Whether preprocess is set to true doesn't affect the final conversion from RICH to ANSI or PLAIN, which always happens
## under some circumstances that are based on other settings.
func preprocessed(shouldPreprocess : bool) -> LoggieMsg:
self.preprocess = shouldPreprocess
return self

View file

@ -0,0 +1,404 @@
@tool
## Defines a set of variables through which all the relevant settings of Loggie can have their
## values set, read and documented. An instance of this class is found in [member Loggie.settings], and that's where Loggie
## ultimately reads from when it's asking for the value of a setting. For user convenience, settings are (by default) exported
## as custom Godot project settings and are loaded from there into these variables during [method load], however,
## you can extend or overwrite this class' [method load] method to define a different way of loading these settings if you prefer.
## [i](e.g. loading from a config.ini file, or a .json file, etc.)[/i].[br][br]
##
## Loggie calls [method load] on this class during its [method _ready] function.
class_name LoggieSettings extends Resource
## The name that will be used for the singleton referring to Loggie.
## [br][br][i][b]Note:[/b] You may change this to something you're more used to, such as "log" or "logger".[/i]
## When doing so, make sure to either do it while the Plugin is enabled, then disable and re-enable the plugin,
## or that you manually clear out the previously created autoload (should be called "Loggie") in Project Settings -> Autoloads.
static var loggie_singleton_name = "Loggie"
## The dictionary which is used to grab the defaults and other values associated with each setting
## relevant to Loggie, particularly important for the default way of loading [LoggieSettings] and
## setting up Godot Project Settings related to Loggie.
const project_settings = {
"remove_settings_if_plugin_disabled" = {
"path": "loggie/general/remove_settings_if_plugin_disabled",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "Choose whether you want Loggie project settings to be wiped from ProjectSettings if the Loggie plugin is disabled.",
},
"terminal_mode" = {
"path": "loggie/general/terminal_mode",
"default_value" : LoggieEnums.TerminalMode.BBCODE,
"type" : TYPE_INT,
"hint" : PROPERTY_HINT_ENUM,
"hint_string" : "Plain:0,ANSI:1,BBCode:2",
"doc" : "Choose the terminal for which loggie should preprocess the output so that it displays as intended.[br][br]Use BBCode for Godot console.[br]Use ANSI for Powershell, Bash, etc.[br]Use PLAIN for log files.",
},
"log_level" = {
"path": "loggie/general/log_level",
"default_value" : LoggieEnums.LogLevel.INFO,
"type" : TYPE_INT,
"hint" : PROPERTY_HINT_ENUM,
"hint_string" : "Error:0,Warn:1,Notice:2,Info:3,Debug:4",
"doc" : "Choose the level of messages which should be displayed. Loggie displays all messages that are outputted at the currently set level (or any lower level).",
},
"show_system_specs" = {
"path": "loggie/general/show_system_specs",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "Should Loggie log the system and device specs of the user as soon as it is booted?",
},
"show_loggie_specs" = {
"path": "loggie/general/show_loggie_specs",
"default_value" : LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL,
"type" : TYPE_INT,
"hint" : PROPERTY_HINT_ENUM,
"hint_string" : "Disabled:0,Essential:1,Advanced:2",
"doc" : "Defines which way Loggie should print its own specs when it is booted.",
},
"enforce_optimal_settings_in_release_build" = {
"path": "loggie/general/enforce_optimal_settings_in_release_build",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "Should Loggie enforce certain settings to automatically change to optimal values in production/release builds?",
},
"output_timestamps" = {
"path": "loggie/timestamps/output_timestamps",
"default_value" : false,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "Should Loggie output a timestamp prefix with each message, showing the exact moment when that log line was produced?",
},
"timestamps_use_utc" = {
"path": "loggie/timestamps/timestamps_use_utc",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If 'Output Timestamps' is true, should those timestamps use the UTC time. If not, local system time is used instead.",
},
"output_message_domain" = {
"path": "loggie/preprocessing/output_message_domain",
"default_value" : false,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If true, logged messages will have the domain they are coming from prepended to them.",
},
"output_errors_to_console" = {
"path": "loggie/preprocessing/output_errors_also_to_console",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If true, errors printed by Loggie will also be visible through an additional print in the main output.",
},
"output_warnings_to_console" = {
"path": "loggie/preprocessing/output_warnings_also_to_console",
"default_value" : true,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If true, warnings printed by Loggie will also be visible through an additional print in the main output.",
},
"use_print_debug_for_debug_msgs" = {
"path": "loggie/preprocessing/use_print_debug_for_debug_msgs",
"default_value" : false,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If true, 'debug' level messages outputted by Loggie will be printed using Godot's 'print_debug' function, which is more verbose.",
},
"derive_and_display_class_names_from_scripts" = {
"path": "loggie/preprocessing/derive_and_display_class_names_from_scripts",
"default_value" : false,
"type" : TYPE_BOOL,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "If true, Loggie will attempt to find out the name of the main class from which the log line is coming and append it in front of the message.",
},
"nameless_class_name_proxy" = {
"path": "loggie/preprocessing/nameless_class_name_proxy",
"default_value" : LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE,
"type" : TYPE_INT,
"hint" : PROPERTY_HINT_ENUM,
"hint_string" : "Nothing:0,ScriptName:1,BaseType:2",
"doc" : "If 'Derive and Display Class Names From Scripts' is enabled, and a script doesn't have a 'class_name', which text should we use as a substitute?",
},
"format_timestamp" = {
"path": "loggie/formats/timestamp",
"default_value" : "[{day}.{month}.{year} {hour}:{minute}:{second}]",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for timestamps which are prepended to the message when output_timestamps is enabled.",
},
"format_debug_msg" = {
"path": "loggie/formats/debug_message",
"default_value" : "[b][color=pink][DEBUG]:[/color][/b] {msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for debug messages.",
},
"format_info_msg" = {
"path": "loggie/formats/info_message",
"default_value" : "{msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for info messages.",
},
"format_notice_msg" = {
"path": "loggie/formats/notice_message",
"default_value" : "[b][color=cyan][NOTICE]:[/color][/b] {msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for notice messages.",
},
"format_warning_msg" = {
"path": "loggie/formats/warning_message",
"default_value" : "[b][color=orange][WARN]:[/color][/b] {msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for warning messages.",
},
"format_error_msg" = {
"path": "loggie/formats/error_message",
"default_value" : "[b][color=red][ERROR]:[/color][/b] {msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for error messages.",
},
"format_domain_prefix" = {
"path": "loggie/formats/domain_prefix",
"default_value" : "[b]({domain})[/b] {msg}",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for domain prefixes.",
},
"format_header" = {
"path": "loggie/formats/header",
"default_value" : "[b][i]{msg}[/i][/b]",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The format used for headers.",
},
"h_separator_symbol" = {
"path": "loggie/formats/h_separator_symbol",
"default_value" : "-",
"type" : TYPE_STRING,
"hint" : PROPERTY_HINT_NONE,
"hint_string" : "",
"doc" : "The symbol used for the horizontal separator.",
},
"box_characters_mode" = {
"path": "loggie/formats/box_characters_mode",
"default_value" : LoggieEnums.BoxCharactersMode.COMPATIBLE,
"type" : TYPE_INT,
"hint" : PROPERTY_HINT_ENUM,
"hint_string" : "Compatible:0,Pretty:1",
"doc" : "There are two sets of box characters defined in LoggieSettings - one set contains prettier characters that produce a nicer looking box, but may not render correctly in the context of various terminals. The other set contains characters that produce a less pretty box, but are compatible with being shown in most terminals.",
},
}
## The current terminal mode of Loggie.
## Terminal mode determines whether BBCode, ANSI or some other type of
## formatting is used to convey text effects, such as bold, italic, colors, etc.
## [br][br]BBCode is compatible with the Godot console.
## [br]ANSI is compatible with consoles like Powershell and Windows CMD.
## [br]PLAIN is used to strip any effects and use plain text instead, which is good for saving raw logs into log files.
var terminal_mode : LoggieEnums.TerminalMode
## The current log level of Loggie.
## It determines which types of messages are allowed to be logged.
## Set this using [method setLogLevel].
var log_level : LoggieEnums.LogLevel
## Whether or not Loggie should log the loggie specs on ready.
var show_loggie_specs : LoggieEnums.ShowLoggieSpecsMode
## Whether or not Loggie should log the system specs on ready.
var show_system_specs : bool
## If true, the domain from which the [LoggieMsg] is coming will be prepended to the output.
## If the domain is default (empty), nothing will change.
var output_message_domain : bool
## Whether to, in addition to logging errors with [method push_error],
## Loggie should also print the error as a message in the standard output.
var print_errors_to_console : bool
## Whether to, in addition to logging errors with [method push_warning],
## Loggie should also print the error as a message in the standard output.
var print_warnings_to_console : bool
## If true, instead of [method print], [method print_debug] will be
## used when printing messages with [method LoggieMsg.debug].
var use_print_debug_for_debug_msg : bool
## Whether Loggie should use the scripts from which it is being called to
## figure out a class name for the class that called a loggie function,
## and display it at the start of each output message.
## This only works in debug builds because it uses [method @GDScript.get_stack].
## See that method's documentation to see why that can't be used in release builds.
var derive_and_show_class_names : bool
## Defines which text will be used as a substitute for the 'class_name' of scripts that do not have a 'class_name'.
## Relevant only if [member derive_and_show_class_names] is enabled.
var nameless_class_name_proxy : LoggieEnums.NamelessClassExtensionNameProxy
## Whether Loggie should prepend a timestamp to each output message.
var output_timestamps : bool
## Whether the outputted timestamps (if [member output_timestamps] is enabled) use UTC or local machine time.
var timestamps_use_utc : bool
## Whether Loggie should enforce optimal values for certain settings when in a Release/Production build.
## [br]If true, Loggie will enforce:
## [br] * [member terminal_mode] to [member LoggieEnums.TerminalMode.PLAIN]
## [br] * [member box_characters_mode] to [member LoggieEnums.BoxCharactersMode.COMPATIBLE]
var enforce_optimal_settings_in_release_build : bool
# ----------------------------------------------- #
#region Formats for prints
# ----------------------------------------------- #
# As per the `print_rich` documentation, supported colors are: black, red, green, yellow, blue, magenta, pink, purple, cyan, white, orange, gray.
# Any other color will be displayed in the Godot console or an ANSI based console, but the color tag (in case of BBCode) won't be properly stripped
# when written to the .log file, resulting in BBCode visible in .log files.
## The format used to decorate a message as a header when using [method LoggieMsg.header].[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
var format_header = "[b][i]{msg}[/i][/b]"
## The format used when appending a domain to a message.[br]
## See: [member output_message_domain]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## The [param {domain}] is a variable that will be replaced with the domain key.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_domain_prefix = "[b]({domain})[/b] {msg}"
## The format used when outputting error messages.[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_error_msg = "[b][color=red][ERROR]:[/color][/b] {msg}"
## The format used when outputting warning messages.[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_warning_msg = "[b][color=orange][WARN]:[/color][/b] {msg}"
## The format used when outputting notice messages.[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_notice_msg = "[b][color=cyan][NOTICE]:[/color][/b] {msg}"
## The format used when outputting info messages.[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_info_msg = "{msg}"
## The format used when outputting debug messages.[br]
## The [param {msg}] is a variable that will be replaced with the contents of the message.[br]
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_debug_msg = "[b][color=pink][DEBUG]:[/color][/b] {msg}"
## The format used for timestamps which are prepended to the message when [member output_timestamps] is enabled.[br]
## The variables [param {day}], [param {month}], [param {year}], [param {hour}], [param {minute}], [param {second}], [param {weekday}], and [param {dst}] are supported.
## You can customize this in your ProjectSettings, or custom_settings.gd (if using it).[br]
var format_timestamp = "[{day}.{month}.{year} {hour}:{minute}:{second}]"
## The symbol which will be used for the HSeparator.
var h_separator_symbol = "-"
## The mode used for drawing boxes.
var box_characters_mode : LoggieEnums.BoxCharactersMode
## The symbols which will be used to construct a box decoration that will properly
## display on any kind of terminal or text reader.
## For a prettier but potentially incompatible box, use [member box_symbols_pretty] instead.
var box_symbols_compatible = {
# ANSI and .log compatible box characters:
"top_left" : "-",
"top_right" : "-",
"bottom_left" : "-",
"bottom_right" : "-",
"h_line" : "-",
"v_line" : ":",
}
## The symbols which will be used to construct pretty box decoration.
## These may not be compatible with some terminals or text readers.
## Use the [member box_symbols_compatible] instead as an alternative.
var box_symbols_pretty = {
"top_left" : "",
"top_right" : "",
"bottom_left" : "",
"bottom_right" : "",
"h_line" : "",
"v_line" : "",
}
#endregion
# ----------------------------------------------- #
## Loads the initial (default) values for all of the LoggieSettings variables.
## (By default, loads them from ProjectSettings (if any modifications there exist),
## or looks in [LoggieEditorPlugin..project_settings] for default values).
## [br][br]Extend this class and override this function to write your own logic for
## how loggie should obtain these settings if you have a need for a different approach.
func load():
terminal_mode = ProjectSettings.get_setting(project_settings.terminal_mode.path, project_settings.terminal_mode.default_value)
log_level = ProjectSettings.get_setting(project_settings.log_level.path, project_settings.log_level.default_value)
show_loggie_specs = ProjectSettings.get_setting(project_settings.show_loggie_specs.path, project_settings.show_loggie_specs.default_value)
show_system_specs = ProjectSettings.get_setting(project_settings.show_system_specs.path, project_settings.show_system_specs.default_value)
output_timestamps = ProjectSettings.get_setting(project_settings.output_timestamps.path, project_settings.output_timestamps.default_value)
timestamps_use_utc = ProjectSettings.get_setting(project_settings.timestamps_use_utc.path, project_settings.timestamps_use_utc.default_value)
enforce_optimal_settings_in_release_build = ProjectSettings.get_setting(project_settings.enforce_optimal_settings_in_release_build.path, project_settings.enforce_optimal_settings_in_release_build.default_value)
print_errors_to_console = ProjectSettings.get_setting(project_settings.output_errors_to_console.path, project_settings.output_errors_to_console.default_value)
print_warnings_to_console = ProjectSettings.get_setting(project_settings.output_warnings_to_console.path, project_settings.output_warnings_to_console.default_value)
use_print_debug_for_debug_msg = ProjectSettings.get_setting(project_settings.use_print_debug_for_debug_msgs.path, project_settings.use_print_debug_for_debug_msgs.default_value)
output_message_domain = ProjectSettings.get_setting(project_settings.output_message_domain.path, project_settings.output_message_domain.default_value)
derive_and_show_class_names = ProjectSettings.get_setting(project_settings.derive_and_display_class_names_from_scripts.path, project_settings.derive_and_display_class_names_from_scripts.default_value)
nameless_class_name_proxy = ProjectSettings.get_setting(project_settings.nameless_class_name_proxy.path, project_settings.nameless_class_name_proxy.default_value)
box_characters_mode = ProjectSettings.get_setting(project_settings.box_characters_mode.path, project_settings.box_characters_mode.default_value)
format_timestamp = ProjectSettings.get_setting(project_settings.format_timestamp.path, project_settings.format_timestamp.default_value)
format_info_msg = ProjectSettings.get_setting(project_settings.format_info_msg.path, project_settings.format_info_msg.default_value)
format_notice_msg = ProjectSettings.get_setting(project_settings.format_notice_msg.path, project_settings.format_notice_msg.default_value)
format_warning_msg = ProjectSettings.get_setting(project_settings.format_warning_msg.path, project_settings.format_warning_msg.default_value)
format_error_msg = ProjectSettings.get_setting(project_settings.format_error_msg.path, project_settings.format_error_msg.default_value)
format_debug_msg = ProjectSettings.get_setting(project_settings.format_debug_msg.path, project_settings.format_debug_msg.default_value)
h_separator_symbol = ProjectSettings.get_setting(project_settings.h_separator_symbol.path, project_settings.h_separator_symbol.default_value)
## Returns a dictionary where the indices are names of relevant variables in the LoggieSettings class,
## and the values are their current values.
func to_dict() -> Dictionary:
var dict = {}
var included = [
"terminal_mode", "log_level", "show_loggie_specs", "show_system_specs", "enforce_optimal_settings_in_release_build",
"output_message_domain", "print_errors_to_console", "print_warnings_to_console",
"use_print_debug_for_debug_msg", "derive_and_show_class_names", "nameless_class_name_proxy",
"output_timestamps", "timestamps_use_utc", "format_header", "format_domain_prefix", "format_error_msg",
"format_warning_msg", "format_notice_msg", "format_info_msg", "format_debug_msg", "format_timestamp",
"h_separator_symbol", "box_characters_mode", "box_symbols_compatible", "box_symbols_pretty",
]
for var_name in included:
dict[var_name] = get(var_name)
return dict

7
addons/loggie/plugin.cfg Normal file
View file

@ -0,0 +1,7 @@
[plugin]
name="Loggie"
description="Simple functional stylish logger for your basic logging needs."
author="Shiva Shadowsong"
version="1.5"
script="plugin.gd"

47
addons/loggie/plugin.gd Normal file
View file

@ -0,0 +1,47 @@
@tool
class_name LoggieEditorPlugin extends EditorPlugin
func _enter_tree():
add_autoload_singleton(LoggieSettings.loggie_singleton_name, "res://addons/loggie/loggie.gd")
add_loggie_project_settings()
func _enable_plugin() -> void:
add_loggie_project_settings()
func _disable_plugin() -> void:
var wipe_setting_exists = ProjectSettings.has_setting(LoggieSettings.project_settings.remove_settings_if_plugin_disabled.path)
if (not wipe_setting_exists) or (wipe_setting_exists and ProjectSettings.get_setting(LoggieSettings.project_settings.remove_settings_if_plugin_disabled.path, true)):
push_warning("The Loggie plugin is being disabled, and all of its ProjectSettings are erased from Godot. If you wish to prevent this behavior, look for the 'Project Settings -> Loggie -> General -> Remove Settings if Plugin Disabled' option while the plugin is enabled.")
remove_loggie_project_setings()
else:
push_warning("The Loggie plugin is being disabled, but its ProjectSettings have been prevented from being removed from Godot. If you wish to allow that behavior, look for the 'Project Settings -> Loggie -> General -> Remove Settings if Plugin Disabled' option while the plugin is enabled.")
remove_autoload_singleton(LoggieSettings.loggie_singleton_name)
## Adds new Loggie related ProjectSettings to Godot.
func add_loggie_project_settings():
for setting in LoggieSettings.project_settings.values():
add_project_setting(setting["path"], setting["default_value"], setting["type"], setting["hint"], setting["hint_string"], setting["doc"])
## Removes Loggie related ProjectSettings from Godot.
func remove_loggie_project_setings():
for setting in LoggieSettings.project_settings.values():
ProjectSettings.set_setting(setting["path"], null)
var error: int = ProjectSettings.save()
if error != OK:
push_error("Loggie - Encountered error %d while saving project settings." % error)
## Adds a new project setting to Godot.
## TODO: Figure out how to also add the documentation to the ProjectSetting so that it shows up
## in the Godot Editor tooltip when the setting is hovered over.
func add_project_setting(setting_name: String, default_value : Variant, value_type: int, type_hint: int = PROPERTY_HINT_NONE, hint_string: String = "", documentation : String = ""):
if !ProjectSettings.has_setting(setting_name):
ProjectSettings.set_setting(setting_name, default_value)
ProjectSettings.set_initial_value(setting_name, default_value)
ProjectSettings.add_property_info({ "name": setting_name, "type": value_type, "hint": type_hint, "hint_string": hint_string})
ProjectSettings.set_as_basic(setting_name, true)
var error: int = ProjectSettings.save()
if error:
push_error("Loggie - Encountered error %d while saving project settings." % error)

View file

@ -0,0 +1,53 @@
@tool
class_name LoggieEnums extends Node
## Based on which log level is currently set to be used by the Loggie., attempting to log a message that's on
## a higher-than-configured log level will result in nothing happening.
enum LogLevel {
ERROR, ## Log level which includes only the logging of Error type messages.
WARN, ## Log level which includes the logging of Error and Warning type messages.
NOTICE, ## Log level which includes the logging of Error, Warning and Notice type messages.
INFO, ## Log level which includes the logging of Error, Warning, Notice and Info type messages.
DEBUG ## Log level which includes the logging of Error, Warning, Notice, Info and Debug type messages.
}
## The classification of message types that can be used to distinguish two identical strings in nature
## of their origin. This is different from [enum LogLevel].
enum MsgType {
STANDARD, ## A message that is considered a standard text that is not special in any way.
ERROR, ## A message that is considered to be an error message.
WARNING, ## A message that is considered to be a warning message.
DEBUG ## A message that is considered to be a message used for debugging.
}
enum TerminalMode {
PLAIN, ## Prints will be plain text.
ANSI, ## Prints will be styled using the ANSI standard. Compatible with Powershell, Win CMD, etc.
BBCODE ## Prints will be styled using the Godot BBCode rules. Compatible with the Godot console.
}
enum BoxCharactersMode {
COMPATIBLE, ## Boxes are drawn using characters that compatible with any kind of terminal or text reader.
PRETTY ## Boxes are drawn using special unicode characters that create a prettier looking box which may not display properly in some terminals or text readers.
}
## Defines a list of possible approaches that can be taken to derive some kind of a class name proxy from a script that doesn't have a 'class_name' clause.
enum NamelessClassExtensionNameProxy {
NOTHING, ## If there is no class_name, nothing will be displayed.
SCRIPT_NAME, ## Use the name of the script whose class_name we tried to read. (e.g. "my_script.gd").
BASE_TYPE, ## Use the name of the base type which the script extends (e.g. 'Node2D', 'Control', etc.)
}
## Defines a list of possible behaviors for the 'show_loggie_specs' setting.
enum ShowLoggieSpecsMode {
DISABLED, ## Loggie specs won't be shown.
ESSENTIAL, ## Show only the essentials.
ADVANCED ## Show all loggie specs.
}
## Defines a list of possible outcomes that can happen when attempting to log a message.
enum LogAttemptResult {
SUCCESS, ## Message will be logged successfully.
LOG_LEVEL_INSUFFICIENT, ## Message won't be logged because it was output at a log level higher than what Loggie is currently set to.
DOMAIN_DISABLED, ## Message won't be logged because it was outputted from a disabled domain.
}

View file

@ -0,0 +1,185 @@
@tool
## LoggieSystemSpecs is a helper class that defines various functions on how to access data about the local machine and its specs
## and creates displayable strings out of them.
class_name LoggieSystemSpecsMsg extends LoggieMsg
## Embeds various system specs into the content of this message.
func embed_specs() -> LoggieSystemSpecsMsg:
self.embed_system_specs()
self.embed_localization_specs()
self.embed_date_data().nl()
self.embed_hardware_specs().nl()
self.embed_video_specs().nl()
self.embed_display_specs().nl()
self.embed_audio_specs().nl()
self.embed_engine_specs().nl()
self.embed_input_specs()
return self
## Embeds essential data about the logger into the content of this message.
func embed_essential_logger_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
self.add(loggie.msg("|\t Is in Production:").bold(), loggie.is_in_production()).nl()
self.add(loggie.msg("|\t Terminal Mode:").bold(), LoggieEnums.TerminalMode.keys()[loggie.settings.terminal_mode]).nl()
self.add(loggie.msg("|\t Log Level:").bold(), LoggieEnums.LogLevel.keys()[loggie.settings.log_level]).nl()
return self
## Embeds advanced data about the logger into the content of this message.
func embed_advanced_logger_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
self.add(loggie.msg("|\t Is in Production:").bold(), loggie.is_in_production()).nl()
var settings_dict = loggie.settings.to_dict()
for setting_var_name : String in settings_dict.keys():
var setting_value = settings_dict[setting_var_name]
var content_to_print = setting_value
match setting_var_name:
"terminal_mode":
content_to_print = LoggieEnums.TerminalMode.keys()[setting_value]
"log_level":
content_to_print = LoggieEnums.LogLevel.keys()[setting_value]
"box_characters_mode":
content_to_print = LoggieEnums.BoxCharactersMode.keys()[setting_value]
self.add(loggie.msg("|\t", setting_var_name.capitalize(), ":").bold(), content_to_print).nl()
return self
## Adds data about the user's software to the content of this message.
func embed_system_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Operating System: ").color(Color.ORANGE).add(OS.get_name()).box(4)
self.add(header)
return self
## Adds data about localization to the content of this message.
func embed_localization_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Localization: ").color(Color.ORANGE).add(OS.get_locale()).box(7)
self.add(header)
return self
## Adds data about the current date/time to the content of this message.
func embed_date_data() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Date").color(Color.ORANGE).box(15)
self.add(header)
self.add(loggie.msg("Date and time (local):").bold(), Time.get_datetime_string_from_system(false, true)).nl()
self.add(loggie.msg("Date and time (UTC):").bold(), Time.get_datetime_string_from_system(true, true)).nl()
self.add(loggie.msg("Date (local):").bold(), Time.get_date_string_from_system(false)).nl()
self.add(loggie.msg("Date (UTC):").bold(), Time.get_date_string_from_system(true)).nl()
self.add(loggie.msg("Time (local):").bold(), Time.get_time_string_from_system(false)).nl()
self.add(loggie.msg("Time (UTC):").bold(), Time.get_time_string_from_system(true)).nl()
self.add(loggie.msg("Timezone:").bold(), Time.get_time_zone_from_system()).nl()
self.add(loggie.msg("UNIX time:").bold(), Time.get_unix_time_from_system()).nl()
return self
## Adds data about the user's hardware to the content of this message.
func embed_hardware_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Hardware").color(Color.ORANGE).box(13)
self.add(header)
self.add(loggie.msg("Model name:").bold(), OS.get_model_name()).nl()
self.add(loggie.msg("Processor name:").bold(), OS.get_processor_name()).nl()
return self
## Adds data about the video system to the content of this message.
func embed_video_specs() -> LoggieSystemSpecsMsg:
const adapter_type_to_string = ["Other (Unknown)", "Integrated", "Discrete", "Virtual", "CPU"]
var adapter_type_string = adapter_type_to_string[RenderingServer.get_video_adapter_type()]
var video_adapter_driver_info = OS.get_video_adapter_driver_info()
var loggie = get_logger()
var header = loggie.msg("Video").color(Color.ORANGE).box(15)
self.add(header)
self.add(loggie.msg("Adapter name:").bold(), RenderingServer.get_video_adapter_name()).nl()
self.add(loggie.msg("Adapter vendor:").bold(), RenderingServer.get_video_adapter_vendor()).nl()
self.add(loggie.msg("Adapter type:").bold(), adapter_type_string).nl()
self.add(loggie.msg("Adapter graphics API version:").bold(), RenderingServer.get_video_adapter_api_version()).nl()
if video_adapter_driver_info.size() > 0:
self.add(loggie.msg("Adapter driver name:").bold(), video_adapter_driver_info[0]).nl()
if video_adapter_driver_info.size() > 1:
self.add(loggie.msg("Adapter driver version:").bold(), video_adapter_driver_info[1]).nl()
return self
## Adds data about the display to the content of this message.
func embed_display_specs() -> LoggieSystemSpecsMsg:
const screen_orientation_to_string = [
"Landscape",
"Portrait",
"Landscape (reverse)",
"Portrait (reverse)",
"Landscape (defined by sensor)",
"Portrait (defined by sensor)",
"Defined by sensor",
]
var screen_orientation_string = screen_orientation_to_string[DisplayServer.screen_get_orientation()]
var loggie = get_logger()
var header = loggie.msg("Display").color(Color.ORANGE).box(13)
self.add(header)
self.add(loggie.msg("Screen count:").bold(), DisplayServer.get_screen_count()).nl()
self.add(loggie.msg("DPI:").bold(), DisplayServer.screen_get_dpi()).nl()
self.add(loggie.msg("Scale factor:").bold(), DisplayServer.screen_get_scale()).nl()
self.add(loggie.msg("Maximum scale factor:").bold(), DisplayServer.screen_get_max_scale()).nl()
self.add(loggie.msg("Startup screen position:").bold(), DisplayServer.screen_get_position()).nl()
self.add(loggie.msg("Startup screen size:").bold(), DisplayServer.screen_get_size()).nl()
self.add(loggie.msg("Startup screen refresh rate:").bold(), ("%f Hz" % DisplayServer.screen_get_refresh_rate()) if DisplayServer.screen_get_refresh_rate() > 0.0 else "").nl()
self.add(loggie.msg("Usable (safe) area rectangle:").bold(), DisplayServer.get_display_safe_area()).nl()
self.add(loggie.msg("Screen orientation:").bold(), screen_orientation_string).nl()
return self
## Adds data about the audio system to the content of this message.
func embed_audio_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Audio").color(Color.ORANGE).box(14)
self.add(header)
self.add(loggie.msg("Mix rate:").bold(), "%d Hz" % AudioServer.get_mix_rate()).nl()
self.add(loggie.msg("Output latency:").bold(), "%f ms" % (AudioServer.get_output_latency() * 1000)).nl()
self.add(loggie.msg("Output device list:").bold(), ", ".join(AudioServer.get_output_device_list())).nl()
self.add(loggie.msg("Capture device list:").bold(), ", ".join(AudioServer.get_input_device_list())).nl()
return self
## Adds data about the godot engine to the content of this message.
func embed_engine_specs() -> LoggieSystemSpecsMsg:
var loggie = get_logger()
var header = loggie.msg("Engine").color(Color.ORANGE).box(14)
self.add(header)
self.add(loggie.msg("Version:").bold(), Engine.get_version_info()["string"]).nl()
self.add(loggie.msg("Command-line arguments:").bold(), str(OS.get_cmdline_args())).nl()
self.add(loggie.msg("Is debug build:").bold(), OS.is_debug_build()).nl()
self.add(loggie.msg("Filesystem is persistent:").bold(), OS.is_userfs_persistent()).nl()
return self
## Adds data about the input device to the content of this message.
func embed_input_specs() -> LoggieSystemSpecsMsg:
var has_virtual_keyboard = DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD)
var loggie = get_logger()
var header = loggie.msg("Input").color(Color.ORANGE).box(14)
self.add(header)
self.add(loggie.msg("Device has touch screen:").bold(), DisplayServer.is_touchscreen_available()).nl()
self.add(loggie.msg("Device has virtual keyboard:").bold(), has_virtual_keyboard).nl()
if has_virtual_keyboard:
self.add(loggie.msg("Virtual keyboard height:").bold(), DisplayServer.virtual_keyboard_get_height())
return self
## Prints out a bunch of useful data about a given script.
## Useful for debugging.
func embed_script_data(script : Script):
var loggie = get_logger()
self.add("Script Data for:", script.get_path()).color("pink")
self.add(":").nl()
self.add(loggie.msg("get_class(): ").color("slate_blue").bold()).add(script.get_class()).nl()
self.add(loggie.msg("get_global_name(): ").color("slate_blue").bold()).add(script.get_global_name()).nl()
self.add(loggie.msg("get_base_script(): ").color("slate_blue").bold()).add(script.get_base_script().resource_path if script.get_base_script() != null else "No base script.").nl()
self.add(loggie.msg("get_instance_base_type(): ").color("slate_blue").bold()).add(script.get_instance_base_type()).nl()
self.add(loggie.msg("get_script_property_list(): ").color("slate_blue").bold()).add(script.get_script_property_list()).nl()
return self

View file

@ -0,0 +1,369 @@
@tool
class_name LoggieTools extends Node
## Removes BBCode from the given text.
static func remove_BBCode(text: String) -> String:
# The bbcode tags to remove.
var tags = ["b", "i", "u", "s", "indent", "code", "url", "center", "right", "color", "bgcolor", "fgcolor"]
var regex = RegEx.new()
var tags_pattern = "|".join(tags)
regex.compile("\\[/?(" + tags_pattern + ")(=[^\\]]*)?\\]")
var stripped_text = regex.sub(text, "", true)
return stripped_text
## Concatenates all given args into one single string, in consecutive order starting with 'msg'.
static func concatenate_msg_and_args(msg : Variant, arg1 : Variant = null, arg2 : Variant = null, arg3 : Variant = null, arg4 : Variant = null, arg5 : Variant = null, arg6 : Variant = null) -> String:
var final_msg = convert_to_string(msg)
var arguments = [arg1, arg2, arg3, arg4, arg5, arg6]
for arg in arguments:
if arg != null:
final_msg += (" " + convert_to_string(arg))
return final_msg
## Converts [param something] into a string.
## If [param something] is a Dictionary, uses a special way to convert it into a string.
## You can add more exceptions and rules for how different things are converted to strings here.
static func convert_to_string(something : Variant) -> String:
var result : String
if something is Dictionary:
result = JSON.new().stringify(something, " ", false, true)
elif something is LoggieMsg:
result = str(something.string())
else:
result = str(something)
return result
## Takes the given [param str] and returns a terminal-ready version of it by converting its content
## to the appropriate format required to display the string correctly in the provided [param mode]
## terminal mode.
static func get_terminal_ready_string(str : String, mode : LoggieEnums.TerminalMode) -> String:
match mode:
LoggieEnums.TerminalMode.ANSI:
# We put the message through the rich_to_ANSI converter which takes care of converting BBCode
# to appropriate ANSI. (Only if the TerminalMode is set to ANSI).
# Godot claims to be already preparing BBCode output for ANSI, but it only works with a small
# predefined set of colors, and I think it totally strips stuff like [b], [i], etc.
# It is possible to display those stylings in ANSI, but we have to do our own conversion here
# to support these features instead of having them stripped.
str = LoggieTools.rich_to_ANSI(str)
LoggieEnums.TerminalMode.BBCODE:
# No need to do anything for BBCODE mode, because we already expect all strings to
# start out with this format in mind.
pass
LoggieEnums.TerminalMode.PLAIN, _:
str = LoggieTools.remove_BBCode(str)
return str
## Converts a given [Color] to an ANSI compatible representation of it in code.
static func color_to_ANSI(color: Color) -> String:
var r = int(color.r * 255)
var g = int(color.g * 255)
var b = int(color.b * 255)
return "\u001b[38;2;%d;%d;%dm" % [r, g, b]
## Strips the BBCode from the given text, and converts all [b], [i] and [color] tags to appropriate ANSI representable codes,
## then returns the converted string. The result of this conversion becomes an ANSI compatible representation of the given [param text].
static func rich_to_ANSI(text: String) -> String:
var regex_color = RegEx.new()
regex_color.compile("\\[color=(.*?)\\](.*?)\\[/color\\]")
# Process color tags first
while regex_color.search(text):
var match = regex_color.search(text)
var color_str = match.get_string(1).to_upper()
var color: Color
var color_code: String
var reset_code = "\u001b[0m"
# Try to parse the color string
if LoggieTools.NamedColors.has(color_str):
color = LoggieTools.NamedColors[color_str]
else:
color = Color(color_str)
if color:
color_code = color_to_ANSI(color)
else:
color_code = ""
reset_code = ""
var replacement = color_code + match.get_string(2) + reset_code
text = text.replace(match.get_string(0), replacement)
# Process bold and italic tags
var bold_on = "\u001b[1m"
var bold_off = "\u001b[22m"
var italic_on = "\u001b[3m"
var italic_off = "\u001b[23m"
text = text.replace("[b]", bold_on).replace("[/b]", bold_off)
text = text.replace("[i]", italic_on).replace("[/i]", italic_off)
# Remove any other BBCode tags but retain the text between them
var regex_bbcode = RegEx.new()
regex_bbcode.compile("\\[(b|/b|i|/i|color=[^\\]]+|/color)\\]")
text = regex_bbcode.sub(text, "", true)
return text
## Returns a dictionary of call stack data related to the stack the call to this function is a part of.
static func get_current_stack_frame_data() -> Dictionary:
var stack = get_stack()
const callerIndex = 3
var targetIndex = callerIndex if stack.size() >= callerIndex else stack.size() - 1
if stack.size() > 0:
return stack[targetIndex]
else:
return {
"source" : "UnknownStackFrameSource",
"line" : 0,
"function" : "UnknownFunction"
}
## Returns the `class_name` of a script.
## [br][param path_or_script] should be either an absolute path to the script
## (String, e.g. "res://my_script.gd"), or a [Script] object.
## [br][param proxy] defines which kind of text will be used as a replacement
## for the class name if the script has no 'class_name'.
static func get_class_name_from_script(path_or_script : Variant, proxy : LoggieEnums.NamelessClassExtensionNameProxy) -> String:
var script
var _class_name = ""
if path_or_script is String or path_or_script is StringName:
if !ResourceLoader.exists(path_or_script, "Script"):
return _class_name
script = load(path_or_script)
elif path_or_script is Script:
script = path_or_script
if not (script is Script):
push_error("Invalid 'path_or_script' param provided to get_class_name_from_script: {path}".format({"path" : path_or_script}))
else:
if not script.has_method("get_global_name"):
# User is using a pre-4.3 version of Godot that doesn't have Script.get_global_name.
# We must use a different method to achieve this then.
return extract_class_name_from_gd_script(path_or_script, proxy)
# Try to get the class name directly.
_class_name = script.get_global_name()
if _class_name != "":
return _class_name
# If that's empty, the script is either a base class, or a class without a name.
# Check if this script has a base script, and if so, use that one as the target whose name to obtain.
# If it doesn't have it, use what the [param proxy] demands.
var base_script = script.get_base_script()
if base_script != null:
return get_class_name_from_script(base_script, proxy)
else:
match proxy:
LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE:
_class_name = script.get_instance_base_type()
LoggieEnums.NamelessClassExtensionNameProxy.SCRIPT_NAME:
_class_name = script.get_script_property_list().front()["name"]
return _class_name
## Opens and reads a .gd script file to find out its 'class_name' or what it 'extends'.
## [param path_or_script] should be either an absolute path to the script
## (String, e.g. "res://my_script.gd"), or a [Script] object.
## [br][param proxy] defines which kind of text will be used as a replacement
## for the class name if the script has no 'class_name'.
## [br][br][b]Note:[/b] This is a compatibility method that will be used on older versions of Godot which
## don't support [method Script.get_global_name].
static func extract_class_name_from_gd_script(path_or_script : Variant, proxy : LoggieEnums.NamelessClassExtensionNameProxy) -> String:
var path : String
if path_or_script is String:
path = path_or_script
elif path_or_script is Script:
path = path_or_script.resource_path
else:
push_error("Invalid 'path_or_script' param provided to extract_class_name_from_gd_script: {path}".format({"path" : path_or_script}))
return ""
var file = FileAccess.open(path, FileAccess.READ)
if not file:
return "File Open Error {filepath}".format({"filepath" : path})
var _class_name: String = ""
for line_num in 40: # Loop only up to 40 lines
if file.eof_reached():
break
var line = file.get_line().strip_edges()
if line.begins_with("class_name"):
_class_name = line.split(" ")[1]
break
if _class_name == "":
var script = load(path)
if script is Script:
match proxy:
LoggieEnums.NamelessClassExtensionNameProxy.BASE_TYPE:
_class_name = script.get_instance_base_type()
LoggieEnums.NamelessClassExtensionNameProxy.SCRIPT_NAME:
_class_name = script.get_script_property_list().front()["name"]
file.close()
return _class_name
## A dictionary of named colors matching the constants from [Color] used to help with rich text coloring.
## There may be a way to obtain these Color values without this dictionary if one can somehow check for the
## existence and value of a constant on the Color class (since they're already there),
## but I can't seem to find a way, so this will have to do for now.
static var NamedColors = {
"ALICE_BLUE": Color(0.941176, 0.972549, 1, 1),
"ANTIQUE_WHITE": Color(0.980392, 0.921569, 0.843137, 1),
"AQUA": Color(0, 1, 1, 1),
"AQUAMARINE": Color(0.498039, 1, 0.831373, 1),
"AZURE": Color(0.941176, 1, 1, 1),
"BEIGE": Color(0.960784, 0.960784, 0.862745, 1),
"BISQUE": Color(1, 0.894118, 0.768627, 1),
"BLACK": Color(0, 0, 0, 1),
"BLANCHED_ALMOND": Color(1, 0.921569, 0.803922, 1),
"BLUE": Color(0, 0, 1, 1),
"BLUE_VIOLET": Color(0.541176, 0.168627, 0.886275, 1),
"BROWN": Color(0.647059, 0.164706, 0.164706, 1),
"BURLYWOOD": Color(0.870588, 0.721569, 0.529412, 1),
"CADET_BLUE": Color(0.372549, 0.619608, 0.627451, 1),
"CHARTREUSE": Color(0.498039, 1, 0, 1),
"CHOCOLATE": Color(0.823529, 0.411765, 0.117647, 1),
"CORAL": Color(1, 0.498039, 0.313726, 1),
"CORNFLOWER_BLUE": Color(0.392157, 0.584314, 0.929412, 1),
"CORNSILK": Color(1, 0.972549, 0.862745, 1),
"CRIMSON": Color(0.862745, 0.0784314, 0.235294, 1),
"CYAN": Color(0, 1, 1, 1),
"DARK_BLUE": Color(0, 0, 0.545098, 1),
"DARK_CYAN": Color(0, 0.545098, 0.545098, 1),
"DARK_GOLDENROD": Color(0.721569, 0.52549, 0.0431373, 1),
"DARK_GRAY": Color(0.662745, 0.662745, 0.662745, 1),
"DARK_GREEN": Color(0, 0.392157, 0, 1),
"DARK_KHAKI": Color(0.741176, 0.717647, 0.419608, 1),
"DARK_MAGENTA": Color(0.545098, 0, 0.545098, 1),
"DARK_OLIVE_GREEN": Color(0.333333, 0.419608, 0.184314, 1),
"DARK_ORANGE": Color(1, 0.54902, 0, 1),
"DARK_ORCHID": Color(0.6, 0.196078, 0.8, 1),
"DARK_RED": Color(0.545098, 0, 0, 1),
"DARK_SALMON": Color(0.913725, 0.588235, 0.478431, 1),
"DARK_SEA_GREEN": Color(0.560784, 0.737255, 0.560784, 1),
"DARK_SLATE_BLUE": Color(0.282353, 0.239216, 0.545098, 1),
"DARK_SLATE_GRAY": Color(0.184314, 0.309804, 0.309804, 1),
"DARK_TURQUOISE": Color(0, 0.807843, 0.819608, 1),
"DARK_VIOLET": Color(0.580392, 0, 0.827451, 1),
"DEEP_PINK": Color(1, 0.0784314, 0.576471, 1),
"DEEP_SKY_BLUE": Color(0, 0.74902, 1, 1),
"DIM_GRAY": Color(0.411765, 0.411765, 0.411765, 1),
"DODGER_BLUE": Color(0.117647, 0.564706, 1, 1),
"FIREBRICK": Color(0.698039, 0.133333, 0.133333, 1),
"FLORAL_WHITE": Color(1, 0.980392, 0.941176, 1),
"FOREST_GREEN": Color(0.133333, 0.545098, 0.133333, 1),
"FUCHSIA": Color(1, 0, 1, 1),
"GAINSBORO": Color(0.862745, 0.862745, 0.862745, 1),
"GHOST_WHITE": Color(0.972549, 0.972549, 1, 1),
"GOLD": Color(1, 0.843137, 0, 1),
"GOLDENROD": Color(0.854902, 0.647059, 0.12549, 1),
"GRAY": Color(0.745098, 0.745098, 0.745098, 1),
"GREEN": Color(0, 1, 0, 1),
"GREEN_YELLOW": Color(0.678431, 1, 0.184314, 1),
"HONEYDEW": Color(0.941176, 1, 0.941176, 1),
"HOT_PINK": Color(1, 0.411765, 0.705882, 1),
"INDIAN_RED": Color(0.803922, 0.360784, 0.360784, 1),
"INDIGO": Color(0.294118, 0, 804, 1),
"IVORY": Color(1, 1, 0.941176, 1),
"KHAKI": Color(0.941176, 0.901961, 0.54902, 1),
"LAVENDER": Color(0.901961, 0.901961, 0.980392, 1),
"LAVENDER_BLUSH": Color(1, 0.941176, 0.960784, 1),
"LAWN_GREEN": Color(0.486275, 0.988235, 0, 1),
"LEMON_CHIFFON": Color(1, 0.980392, 0.803922, 1),
"LIGHT_BLUE": Color(0.678431, 0.847059, 0.901961, 1),
"LIGHT_CORAL": Color(0.941176, 0.501961, 0.501961, 1),
"LIGHT_CYAN": Color(0.878431, 1, 1, 1),
"LIGHT_GOLDENROD": Color(0.980392, 0.980392, 0.823529, 1),
"LIGHT_GRAY": Color(0.827451, 0.827451, 0.827451, 1),
"LIGHT_GREEN": Color(0.564706, 0.933333, 0.564706, 1),
"LIGHT_PINK": Color(1, 0.713726, 0.756863, 1),
"LIGHT_SALMON": Color(1, 0.627451, 0.478431, 1),
"LIGHT_SEA_GREEN": Color(0.12549, 0.698039, 0.666667, 1),
"LIGHT_SKY_BLUE": Color(0.529412, 0.807843, 0.980392, 1),
"LIGHT_SLATE_GRAY": Color(0.466667, 0.533333, 0.6, 1),
"LIGHT_STEEL_BLUE": Color(0.690196, 0.768627, 0.870588, 1),
"LIGHT_YELLOW": Color(1, 1, 0.878431, 1),
"LIME": Color(0, 1, 0, 1),
"LIME_GREEN": Color(0.196078, 0.803922, 0.196078, 1),
"LINEN": Color(0.980392, 0.941176, 0.901961, 1),
"MAGENTA": Color(1, 0, 1, 1),
"MAROON": Color(0.690196, 0.188235, 0.376471, 1),
"MEDIUM_AQUAMARINE": Color(0.4, 0.803922, 0.666667, 1),
"MEDIUM_BLUE": Color(0, 0, 0.803922, 1),
"MEDIUM_ORCHID": Color(0.729412, 0.333333, 0.827451, 1),
"MEDIUM_PURPLE": Color(0.576471, 0.439216, 0.858824, 1),
"MEDIUM_SEA_GREEN": Color(0.235294, 0.701961, 0.443137, 1),
"MEDIUM_SLATE_BLUE": Color(0.482353, 0.407843, 0.933333, 1),
"MEDIUM_SPRING_GREEN": Color(0, 0.980392, 0.603922, 1),
"MEDIUM_TURQUOISE": Color(0.282353, 0.819608, 0.8, 1),
"MEDIUM_VIOLET_RED": Color(0.780392, 0.0823529, 0.521569, 1),
"MIDNIGHT_BLUE": Color(0.0980392, 0.0980392, 0.439216, 1),
"MINT_CREAM": Color(0.960784, 1, 0.980392, 1),
"MISTY_ROSE": Color(1, 0.894118, 0.882353, 1),
"MOCCASIN": Color(1, 0.894118, 0.709804, 1),
"NAVAJO_WHITE": Color(1, 0.870588, 0.678431, 1),
"NAVY_BLUE": Color(0, 0, 0.501961, 1),
"OLD_LACE": Color(0.992157, 0.960784, 0.901961, 1),
"OLIVE": Color(0.501961, 0.501961, 0, 1),
"OLIVE_DRAB": Color(0.419608, 0.556863, 0.137255, 1),
"ORANGE": Color(1, 0.647059, 0, 1),
"ORANGE_RED": Color(1, 0.270588, 0, 1),
"ORCHID": Color(0.854902, 0.439216, 0.839216, 1),
"PALE_GOLDENROD": Color(0.933333, 0.909804, 0.666667, 1),
"PALE_GREEN": Color(0.596078, 0.984314, 0.596078, 1),
"PALE_TURQUOISE": Color(0.686275, 0.933333, 0.933333, 1),
"PALE_VIOLET_RED": Color(0.858824, 0.439216, 0.576471, 1),
"PAPAYA_WHIP": Color(1, 0.937255, 0.835294, 1),
"PEACH_PUFF": Color(1, 0.854902, 0.72549, 1),
"PERU": Color(0.803922, 0.521569, 0.247059, 1),
"PINK": Color(1, 0.752941, 0.796078, 1),
"PLUM": Color(0.866667, 0.627451, 0.866667, 1),
"POWDER_BLUE": Color(0.690196, 0.878431, 0.901961, 1),
"PURPLE": Color(0.627451, 0.12549, 0.941176, 1),
"REBECCA_PURPLE": Color(0.4, 0.2, 0.6, 1),
"RED": Color(1, 0, 0, 1),
"ROSY_BROWN": Color(0.737255, 0.560784, 0.560784, 1),
"ROYAL_BLUE": Color(0.254902, 0.411765, 0.882353, 1),
"SADDLE_BROWN": Color(0.545098, 0.270588, 0.0745098, 1),
"SALMON": Color(0.980392, 0.501961, 0.447059, 1),
"SANDY_BROWN": Color(0.956863, 0.643137, 0.376471, 1),
"SEA_GREEN": Color(0.180392, 0.545098, 0.341176, 1),
"SEASHELL": Color(1, 0.960784, 0.933333, 1),
"SIENNA": Color(0.627451, 0.321569, 0.176471, 1),
"SILVER": Color(0.752941, 0.752941, 0.752941, 1),
"SKY_BLUE": Color(0.529412, 0.807843, 0.921569, 1),
"SLATE_BLUE": Color(0.415686, 0.352941, 0.803922, 1),
"SLATE_GRAY": Color(0.439216, 0.501961, 0.564706, 1),
"SNOW": Color(1, 0.980392, 0.980392, 1),
"SPRING_GREEN": Color(0, 1, 0.498039, 1),
"STEEL_BLUE": Color(0.27451, 0.509804, 0.705882, 1),
"TAN": Color(0.823529, 0.705882, 0.54902, 1),
"TEAL": Color(0, 0.501961, 0.501961, 1),
"THISTLE": Color(0.847059, 0.74902, 0.847059, 1),
"TOMATO": Color(1, 0.388235, 0.278431, 1),
"TRANSPARENT": Color(1, 1, 1, 0),
"TURQUOISE": Color(0.25098, 0.878431, 0.815686, 1),
"VIOLET": Color(0.933333, 0.509804, 0.933333, 1),
"WEB_GRAY": Color(0.501961, 0.501961, 0.501961, 1),
"WEB_GREEN": Color(0, 0.501961, 0, 1),
"WEB_MAROON": Color(0.501961, 0, 0, 1),
"WEB_PURPLE": Color(0.501961, 0, 0.501961, 1),
"WHEAT": Color(0.960784, 0.870588, 0.701961, 1),
"WHITE": Color(1, 1, 1, 1),
"WHITE_SMOKE": Color(0.960784, 0.960784, 0.960784, 1),
"YELLOW": Color(1, 1, 0, 1),
"YELLOW_GREEN": Color(0.603922, 0.803922, 0.196078, 1)
}