From 028d8db0e2e291f4a91de3ae359b7030dc8537ee Mon Sep 17 00:00:00 2001 From: Kris Date: Sun, 1 Dec 2024 22:21:32 +0100 Subject: [PATCH] V1.5 (#15) * Update project version. * Fix a mistake where the separator placeholder in LoggieMsg.prefix was not used correctly. * fix: pad timestamp fields that need padding * LoggieSettings is now a Resource instead of Node. Fixes a memory leak. * Fix issue where enforced production settings only worked if you weren't using custom_settings.gd. * New setting: enforce_optimal_settings_in_release_build. * Update some function parameter names to avoid shadowing existing members. * Update a message in test.gd to use Loggie instead of print_rich. * Update test.gd to explicitly test out a color styled version of each type of message. * New feature: Message Segmentation. * Update some function parameter/varaible names to avoid shadowing existing members. * Remove original_content and support for it. * Add LoggieMsg.tab. * Apply parameter name change to a couple more places where it needed to be done. * Add LoggieEnums.MsgType. * Add LoggieTools.get_terminal_ready_string. * Clean up LoggieMsg.output wrappers, and fix issue with improperly formatted output passed to stuff like push_error, push_warning and print_debug. * Update a print in test.gd. * Add tests for each type of coloring of messages. --------- Co-authored-by: dusk --- addons/loggie/custom_settings.gd.example | 2 + addons/loggie/loggie.gd | 41 ++-- addons/loggie/loggie_message.gd | 245 +++++++++++++---------- addons/loggie/loggie_settings.gd | 20 +- addons/loggie/plugin.cfg | 2 +- addons/loggie/tools/loggie_enums.gd | 9 + addons/loggie/tools/loggie_tools.gd | 23 ++- project.godot | 2 +- test/test.gd | 43 +++- 9 files changed, 250 insertions(+), 137 deletions(-) diff --git a/addons/loggie/custom_settings.gd.example b/addons/loggie/custom_settings.gd.example index 4f49933..843ba1a 100644 --- a/addons/loggie/custom_settings.gd.example +++ b/addons/loggie/custom_settings.gd.example @@ -11,6 +11,8 @@ func load(): 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 diff --git a/addons/loggie/loggie.gd b/addons/loggie/loggie.gd index 7872cf9..18f991a 100644 --- a/addons/loggie/loggie.gd +++ b/addons/loggie/loggie.gd @@ -6,7 +6,7 @@ extends Node ## Stores a string describing the current version of Loggie. -const VERSION : String = "v1.4" +const VERSION : String = "v1.5" ## Emitted any time Loggie attempts to log a message. ## Useful for capturing the messages that pass through Loggie. @@ -44,18 +44,19 @@ func _init() -> void: if _settings != null: self.settings = _settings.new() self.settings.load() - if is_in_production(): - self.settings.terminal_mode = LoggieEnums.TerminalMode.PLAIN - self.settings.box_characters_mode = LoggieEnums.BoxCharactersMode.COMPATIBLE 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 settings.derive_and_show_class_names == true and OS.has_feature("debug"): + 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 @@ -70,12 +71,12 @@ func _init() -> void: if Engine.is_editor_hint(): return - if settings.show_loggie_specs != LoggieEnums.ShowLoggieSpecsMode.DISABLED: + 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 settings.show_loggie_specs: + match self.settings.show_loggie_specs: LoggieEnums.ShowLoggieSpecsMode.ESSENTIAL: loggie_specs_msg.embed_essential_logger_specs() LoggieEnums.ShowLoggieSpecsMode.ADVANCED: @@ -83,7 +84,7 @@ func _init() -> void: loggie_specs_msg.preprocessed(false).info() - if settings.show_system_specs: + if self.settings.show_system_specs: var system_specs_msg = LoggieSystemSpecsMsg.new().use_logger(self) system_specs_msg.embed_specs().preprocessed(false).info() @@ -130,37 +131,37 @@ func is_domain_enabled(domain_name : String) -> bool: ## 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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - var loggieMsg = LoggieMsg.new(msg, arg1, arg2, arg3, arg4, arg5) +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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - return msg(msg, arg1, arg2, arg3, arg4, arg5).info() +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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - return msg(msg, arg1, arg2, arg3, arg4, arg5).warn() +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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - return msg(msg, arg1, arg2, arg3, arg4, arg5).error() +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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - return msg(msg, arg1, arg2, arg3, arg4, arg5).debug() +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(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: - return msg(msg, arg1, arg2, arg3, arg4, arg5).notice() +func notice(message = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> LoggieMsg: + return msg(message, arg1, arg2, arg3, arg4, arg5).notice() diff --git a/addons/loggie/loggie_message.gd b/addons/loggie/loggie_message.gd index a892265..4334f7b 100644 --- a/addons/loggie/loggie_message.gd +++ b/addons/loggie/loggie_message.gd @@ -1,7 +1,7 @@ @tool -## LoggieMsg represents a mutable object that holds a string message ([member content]), its original unmutated form ([member original_content]), and -## a bunch of helper methods that make it easy to manipulate the content and chain together additions and changes to it. +## 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. @@ -12,14 +12,14 @@ ## [codeblock]Loggie.msg("Hello world").color(Color("#ffffff")).suffix("!").info()[/codeblock] class_name LoggieMsg extends RefCounted -## The original string content of this message, as it existed at the moment this message was instantiated. -## This content can be accessed with [method get_original], or you can convert the current [member content] to its original form -## by calling [method to_original]. -var original_content : String = "" +## 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 string content of this message. By calling various helper methods in this class, this content is further altered. -## You can then output it with methods like [method info], [method debug], etc. -var content : String = "" +## 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. @@ -35,9 +35,8 @@ var preprocess : bool = true ## This variable should be set with [method use_logger] before an attempt is made to log this message out. var _logger : Variant -func _init(msg = "", arg1 = null, arg2 = null, arg3 = null, arg4 = null, arg5 = null) -> void: - self.content = LoggieTools.concatenate_msg_and_args(msg, arg1, arg2, arg3, arg4, arg5) - self.original_content = self.content +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: @@ -49,30 +48,50 @@ 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 level to the standard output using either [method print_rich] or [method print]. +## 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, msg : String, domain : String = "") -> void: +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, msg, LoggieEnums.LogAttemptResult.LOG_LEVEL_INSUFFICIENT) + 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(domain): - loggie.log_attempted.emit(self, msg, LoggieEnums.LogAttemptResult.DOMAIN_DISABLED) + 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 !domain.is_empty() and loggie.settings.output_message_domain == true: - msg = loggie.settings.format_domain_prefix.format({"domain" : domain, "msg" : msg}) + 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). @@ -88,142 +107,135 @@ func output(level : LoggieEnums.LogLevel, msg : String, domain : String = "") -> loggie.class_names[scriptPath] = _class_name if _class_name != "": - msg = "[b]({class_name})[/b] {msg}".format({ + message = "[b]({class_name})[/b] {msg}".format({ "class_name" : _class_name, - "msg" : msg + "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) - msg = "{formatted_time} {msg}".format({ + 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" : msg + "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: - # 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. - msg = LoggieTools.rich_to_ANSI(msg) - print_rich(msg) - LoggieEnums.TerminalMode.BBCODE: - print_rich(msg) + LoggieEnums.TerminalMode.ANSI, LoggieEnums.TerminalMode.BBCODE: + print_rich(message) LoggieEnums.TerminalMode.PLAIN, _: - msg = LoggieTools.remove_BBCode(msg) - print(msg) - - loggie.log_attempted.emit(self, msg, LoggieEnums.LogAttemptResult.SUCCESS) + 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: - var loggie = get_logger() - if loggie != null and loggie.settings != null: - var msg = loggie.settings.format_error_msg.format({"msg": self.content}) - output(LoggieEnums.LogLevel.ERROR, msg, self.domain_name) - if loggie.settings.print_errors_to_console and loggie.settings.log_level >= LoggieEnums.LogLevel.ERROR: - push_error(self.string()) + 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: - var loggie = get_logger() - if loggie != null and loggie.settings != null: - var msg = loggie.settings.format_warning_msg.format({"msg": self.content}) - output(LoggieEnums.LogLevel.WARN, msg, self.domain_name) - if loggie.settings.print_warnings_to_console and loggie.settings.log_level >= LoggieEnums.LogLevel.WARN: - push_warning(self.string()) + 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: - var loggie = get_logger() - if loggie != null and loggie.settings != null: - var msg = loggie.settings.format_notice_msg.format({"msg": self.content}) - output(LoggieEnums.LogLevel.NOTICE, msg, self.domain_name) + 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: - var loggie = get_logger() - if loggie != null and loggie.settings != null: - var msg = loggie.settings.format_info_msg.format({"msg": self.content}) - output(LoggieEnums.LogLevel.INFO, msg, self.domain_name) + 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: - var loggie = get_logger() - if loggie != null and loggie.settings != null: - var msg = loggie.settings.format_debug_msg.format({"msg": self.content}) - output(LoggieEnums.LogLevel.DEBUG, msg, self.domain_name) - if loggie.settings.use_print_debug_for_debug_msg and loggie.settings.log_level >= LoggieEnums.LogLevel.DEBUG: - print_debug(self.string()) + output(LoggieEnums.LogLevel.DEBUG, self.string(), self.domain_name, LoggieEnums.MsgType.DEBUG) return self ## Returns the string content of this message. -func string() -> String: - return self.content +## 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: - self.content = LoggieTools.rich_to_ANSI(self.content) + 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: - self.content = LoggieTools.remove_BBCode(self.content) + var new_content : Array = [] + for segment in self.content: + new_content.append(LoggieTools.remove_BBCode(segment)) + self.content = new_content return self -## Returns the original version of this message (as it was in the moment when it was constructed). -func get_original() -> String: - return self.original_content - -## Changes the content of this message to be equal to the original version of this message (as it was in the moment when it was constructed). -func to_original() -> LoggieMsg: - self.content = self.original_content - return self - -## Wraps the current content of this message in the given color. +## 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 = "[color={colorstr}]{msg}[/color]".format({"colorstr": _color, "msg": self.content}) + self.content[current_segment_index] = "[color={colorstr}]{msg}[/color]".format({ + "colorstr": _color, + "msg": self.content[current_segment_index] + }) + return self -## Stylizes the current content of this message to be bold. +## Stylizes the current segment of this message to be bold. func bold() -> LoggieMsg: - self.content = "[b]{msg}[/b]".format({"msg": self.content}) + self.content[current_segment_index] = "[b]{msg}[/b]".format({"msg": self.content[current_segment_index]}) return self -## Stylizes the current content of this message to be italic. +## Stylizes the current segment of this message to be italic. func italic() -> LoggieMsg: - self.content = "[i]{msg}[/i]".format({"msg": self.content}) + self.content[current_segment_index] = "[i]{msg}[/i]".format({"msg": self.content[current_segment_index]}) return self -## Stylizes the current content of this message as a header. +## Stylizes the current segment of this message as a header. func header() -> LoggieMsg: var loggie = get_logger() - self.content = loggie.settings.format_header.format({"msg": self.content}) + 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 content +## 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).strip_edges(true, true) + 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 @@ -236,7 +248,7 @@ func box(h_padding : int = 4): var middle_row_design = "{vert_line}{padding}{content}{space_fill}{padding}{vert_line}".format({ "vert_line" : box_character_source.v_line, - "content" : self.content, + "content" : self.content[current_segment_index], "padding" : " ".repeat(h_padding), "space_fill" : " ".repeat(h_fill_length - stripped_content.length() - h_padding*2) }) @@ -247,7 +259,7 @@ func box(h_padding : int = 4): "bottom_right_corner" : box_character_source.bottom_right }) - self.content = "{top_row}\n{middle_row}\n{bottom_row}\n".format({ + 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 @@ -258,18 +270,24 @@ func box(h_padding : int = 4): return self ## Appends additional content to this message at the end of the current content and its stylings. -func add(msg : Variant = null, arg1 : Variant = null, arg2 : Variant = null, arg3 : Variant = null, arg4 : Variant = null, arg5 : Variant = null) -> LoggieMsg: - self.content = self.content + LoggieTools.concatenate_msg_and_args(msg, arg1, arg2, arg3, arg4, arg5) +## 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 this message. +## Adds a specified amount of newlines to the end of the current segment of this message. func nl(amount : int = 1) -> LoggieMsg: - self.content += "\n".repeat(amount) + self.content[current_segment_index] += "\n".repeat(amount) return self -## Adds a specified amount of spaces to the end of this message. +## Adds a specified amount of spaces to the end of the current segment of this message. func space(amount : int = 1) -> LoggieMsg: - self.content += " ".repeat(amount) + 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. @@ -278,30 +296,43 @@ func domain(_domain_name : String) -> LoggieMsg: self.domain_name = _domain_name return self -## Prepends the given prefix string to the start of the message with the provided separator. -func prefix(prefix : String, separator : String = "") -> LoggieMsg: - self.content = "{prefix}{space}{content}".format({ - "prefix" : prefix, +## 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 + "content" : self.content[0] }) return self -## Appends the given suffix string to the end of the message with the provided separator. -func suffix(suffix : String, separator : String = "") -> LoggieMsg: - self.content = "{content}{separator}{suffix}".format({ - "suffix" : suffix, +## 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 + "content" : self.content[self.content.size() - 1] }) return self -## Appends a horizontal separator with the given length to the message. +## 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 += (symbol.repeat(size)) + 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]. diff --git a/addons/loggie/loggie_settings.gd b/addons/loggie/loggie_settings.gd index 825316b..22c3ad9 100644 --- a/addons/loggie/loggie_settings.gd +++ b/addons/loggie/loggie_settings.gd @@ -8,7 +8,7 @@ ## [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 Node +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] @@ -60,6 +60,14 @@ const project_settings = { "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, @@ -258,6 +266,12 @@ 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 # ----------------------------------------------- # @@ -352,6 +366,7 @@ func load(): 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) @@ -359,6 +374,7 @@ func load(): 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) @@ -375,7 +391,7 @@ func load(): func to_dict() -> Dictionary: var dict = {} var included = [ - "terminal_mode", "log_level", "show_loggie_specs", "show_system_specs", + "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", diff --git a/addons/loggie/plugin.cfg b/addons/loggie/plugin.cfg index a5a7902..537456e 100644 --- a/addons/loggie/plugin.cfg +++ b/addons/loggie/plugin.cfg @@ -3,5 +3,5 @@ name="Loggie" description="Simple functional stylish logger for your basic logging needs." author="Shiva Shadowsong" -version="1.4" +version="1.5" script="plugin.gd" diff --git a/addons/loggie/tools/loggie_enums.gd b/addons/loggie/tools/loggie_enums.gd index a0d06ab..dcfeb16 100644 --- a/addons/loggie/tools/loggie_enums.gd +++ b/addons/loggie/tools/loggie_enums.gd @@ -11,6 +11,15 @@ enum LogLevel { 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. diff --git a/addons/loggie/tools/loggie_tools.gd b/addons/loggie/tools/loggie_tools.gd index fa5977b..9b94e58 100644 --- a/addons/loggie/tools/loggie_tools.gd +++ b/addons/loggie/tools/loggie_tools.gd @@ -30,11 +30,32 @@ static func convert_to_string(something : Variant) -> String: if something is Dictionary: result = JSON.new().stringify(something, " ", false, true) elif something is LoggieMsg: - result = str(something.content) + 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) diff --git a/project.godot b/project.godot index be295fa..89ffe05 100644 --- a/project.godot +++ b/project.godot @@ -19,7 +19,7 @@ config/description="Loggie is a basic logging utility for those who could use a Loggie takes care that your logs always look clean in release builds, while allowing you to use extra styling for console targeted output. ANSI-compatible, and friendly both for solo developers and developers in a team (externally loaded settings for each developer). If you need something simple but effective, Loggie is your guy." -config/version="1.4" +config/version="1.5" run/main_scene="res://test/test.tscn" config/features=PackedStringArray("4.3", "Forward Plus") config/icon="res://addons/loggie/assets/icon.png" diff --git a/test/test.gd b/test/test.gd index 77ce16d..b03d69a 100644 --- a/test/test.gd +++ b/test/test.gd @@ -28,10 +28,11 @@ func _ready() -> void: #test_decors() #test_output_from_classes_of_various_inheritances_and_origins() #test_domains() + #test_segments() func setup_gui(): $Label.text = "Loggie {version}".format({"version": Loggie.VERSION}) - print_rich("[i]Edit the test.tscn _ready function and uncomment the calls to features you want to test out.[/i]") + Loggie.msg("Edit the test.tscn _ready function and uncomment the calls to features you want to test out.").italic().color(Color.GRAY).preprocessed(false).info() # ----------------------------------------- #region Tests @@ -115,11 +116,29 @@ func test_decors(): # Test outputting a header, with a newline and a 30 character long horizontal separator. Loggie.msg("Colored Header").header().color("yellow").nl().hseparator(30).info() - # Test a supported color message. - Loggie.msg("I'm cyan.").color("cyan").info() + # Test a supported color message of all types. + Loggie.msg("Supported color info.").color("cyan").info() + Loggie.msg("Supported color notice.").color("cyan").notice() + Loggie.msg("Supported color warning.").color("cyan").warn() + Loggie.msg("Supported color error.").color("cyan").error() + Loggie.msg("Supported color debug.").color("cyan").debug() + + # Test a godot colored message of all types. + # Godot-colors are colors defined as consts in the 'Color' class but not + # explicitly supported by 'print_rich'. + Loggie.msg("Custom colored info msg.").color(Color.SLATE_BLUE).info() + Loggie.msg("Custom colored notice.").color(Color.SLATE_BLUE).notice() + Loggie.msg("Custom colored warning.").color(Color.SLATE_BLUE).warn() + Loggie.msg("Custom colored error.").color(Color.SLATE_BLUE).error() + Loggie.msg("Custom colored debug.").color(Color.SLATE_BLUE).debug() # Test a custom colored message. - Loggie.msg("I'm slate blue.").color(Color.SLATE_BLUE).info() + # (Arbitrary hex codes). + Loggie.msg("Custom colored info msg.").color("#3afabc").info() + Loggie.msg("Custom colored notice.").color("#3afabc").notice() + Loggie.msg("Custom colored warning.").color("#3afabc").warn() + Loggie.msg("Custom colored error.").color("#3afabc").error() + Loggie.msg("Custom colored debug.").color("#3afabc").debug() # Test pretty printing a dictionary. var testDict = { @@ -131,7 +150,21 @@ func test_decors(): "c" : ["A", {"B" : "2"}, 3] } Loggie.msg(testDict).info() - print() + + +func test_segments(): + # Test basic segmenting. + var msg = Loggie.msg("Segment 1 *").endseg().add(" Segment 2 *").endseg().add(" Segment 3").info() + + # Print the 2nd segment of that segmented message: + Loggie.info("Segment 1 is:", msg.string(1)) + + # Test messages where each segment has different styles. + Loggie.msg("SegmentKey:").bold().color(Color.ORANGE).msg("SegmentValue").color(Color.DIM_GRAY).info() + Loggie.msg("SegHeader").header().color(Color.ORANGE).space().msg("SegPlain ").msg("SegGrayItalic").italic().color(Color.DIM_GRAY).prefix("PREFIX: ").suffix(" - SUFFIX").debug() + + print("\n\n") + Loggie.msg("Segment1: ").color("orange").msg("Segment2").info() #endregion