From c915e136ed55f4110a05072fdb6b2d9a02b47b79 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 29 Jan 2025 14:26:07 +0100 Subject: [PATCH 01/57] Add top-level output module (incomplete) --- cmake/rsp/output.cmake | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cmake/rsp/output.cmake diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake new file mode 100644 index 0000000..c4c7ddc --- /dev/null +++ b/cmake/rsp/output.cmake @@ -0,0 +1,8 @@ +# -------------------------------------------------------------------------------------------------------------- # +# Output functions +# -------------------------------------------------------------------------------------------------------------- # + +include_guard(GLOBAL) + +# Debug +message(VERBOSE "rsp/output module included") \ No newline at end of file From ff5d39ac077fa82583a1139c949582d4a693ff69 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 12:41:39 +0100 Subject: [PATCH 02/57] Add ANSI sub-module --- cmake/rsp/output/ansi.cmake | 279 ++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 cmake/rsp/output/ansi.cmake diff --git a/cmake/rsp/output/ansi.cmake b/cmake/rsp/output/ansi.cmake new file mode 100644 index 0000000..f4d7f6b --- /dev/null +++ b/cmake/rsp/output/ansi.cmake @@ -0,0 +1,279 @@ +# -------------------------------------------------------------------------------------------------------------- # +# ANSI utilities +# -------------------------------------------------------------------------------------------------------------- # + +include_guard(GLOBAL) + +# Debug +message(VERBOSE "rsp/output/ansi module included") + +if (NOT DEFINED RSP_IS_ANSI_ENABLED) + + # State that determines if ANSI is enabled or not + # + # @internal + # + set(RSP_IS_ANSI_ENABLED false) +endif () + +if (NOT DEFINED RSP_DEFAULT_ESCAPE_CHARACTER) + + # The default escape character to be used for + # defining an ANSI escape sequence. + # + # @see ansi_escape_sequence() + # + string(ASCII 27 RSP_DEFAULT_ESCAPE_CHARACTER) +endif () + +if (NOT DEFINED RSP_ANSI_PRESET) + + # List of predefined variable names and ANSI codes + # + # @see https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters + # @see disable_ansi() + # @see ansi_sgr() + # + # Format: + # [|]... + # + set(RSP_ANSI_PRESET + # Reset / Restore + "RESTORE 0" + + # Demo style + # "MY_STYLE 1|3|4|9|31|47" + + # Text + "TEXT_BOLD 1" + "TEXT_BOLD_RESTORE 22" + + "TEXT_DIM 2" + "TEXT_DIM_RESTORE 22" + + "TEXT_ITALIC 3" + "TEXT_ITALIC_RESTORE 23" + + "TEXT_UNDERLINE 4" + "TEXT_UNDERLINE_RESTORE 24" + + "TEXT_BLINK 5" + "TEXT_BLINK_RESTORE 25" + + "TEXT_INVERSE 7" + "TEXT_INVERSE_RESTORE 27" + + "TEXT_HIDDEN 8" + "TEXT_HIDDEN_RESTORE 28" + + "TEXT_STRIKETHROUGH 9" + "TEXT_STRIKETHROUGH_RESTORE 29" + + # Colours + "COLOR_BLACK 30" + "COLOR_BG_BLACK 40" + "COLOR_BRIGHT_BLACK 90" + "COLOR_BRIGHT_BG_BLACK 100" + + "COLOR_RED 31" + "COLOR_BG_RED 41" + "COLOR_BRIGHT_RED 91" + "COLOR_BRIGHT_BG_RED 101" + + "COLOR_GREEN 32" + "COLOR_BG_GREEN 42" + "COLOR_BRIGHT_GREEN 92" + "COLOR_BRIGHT_BG_GREEN 102" + + "COLOR_YELLOW 33" + "COLOR_BG_YELLOW 43" + "COLOR_BRIGHT_YELLOW 93" + "COLOR_BRIGHT_BG_YELLOW 103" + + "COLOR_BLUE 34" + "COLOR_BG_BLUE 44" + "COLOR_BRIGHT_BLUE 94" + "COLOR_BRIGHT_BG_BLUE 104" + + "COLOR_MAGENTA 35" + "COLOR_BG_MAGENTA 45" + "COLOR_BRIGHT_MAGENTA 95" + "COLOR_BRIGHT_BG_MAGENTA 105" + + "COLOR_CYAN 36" + "COLOR_BG_CYAN 46" + "COLOR_BRIGHT_CYAN 96" + "COLOR_BRIGHT_BG_CYAN 106" + + "COLOR_WHITE 37" + "COLOR_BG_WHITE 47" + "COLOR_BRIGHT_WHITE 97" + "COLOR_BRIGHT_BG_WHITE 107" + + "COLOR_DEFAULT 39" + "COLOR_BG_DEFAULT 49" + ) +endif () + +if (NOT COMMAND "enable_ansi") + + #! enable_ansi : Defines a series variables containing ansi escape sequences + # + # @see RSP_ANSI_PRESET + # @see disable_ansi() + # + # @param [PRESET ] Optional - List that defines variable names and + # ANSI codes. Defaults to RSP_ANSI_PRESET. + # + function(enable_ansi) + set(multiValueArgs PRESET) + cmake_parse_arguments(INPUT "" "" "${multiValueArgs}" ${ARGN}) + + # ---------------------------------------------------------------------------------------------- # + # Resolve optional arguments + + # Use RSP ANSI preset, if none given + if (NOT DEFINED INPUT_PRESET) + set(INPUT_PRESET "${RSP_ANSI_PRESET}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + foreach(item IN LISTS INPUT_PRESET) + + string(REPLACE " " ";" parts "${item}") + list(GET parts 0 name) + list(GET parts 1 value) + + # Replace evt. "|" to semicolons to allow multiple codes + string(REPLACE "|" ";" value "${value}") + + # Debug + # dump(name value) + + ansi_sgr(OUTPUT sequence CODE "${value}") + + set("${name}" "${sequence}" PARENT_SCOPE) + endforeach() + + # ---------------------------------------------------------------------------------------------- # + + # Change state + set(RSP_IS_ANSI_ENABLED true PARENT_SCOPE) + endfunction() +endif () + +if (NOT COMMAND "disable_ansi") + + #! enable_ansi : Unsets the variables containing ansi escape sequences + # + # @see RSP_ANSI_PRESET + # @see enable_ansi() + # + # @param [PRESET ] Optional - List that defines variable names and + # ANSI codes. Defaults to RSP_ANSI_PRESET. + # + function(disable_ansi) + set(multiValueArgs PRESET) + cmake_parse_arguments(INPUT "" "" "${multiValueArgs}" ${ARGN}) + + # ---------------------------------------------------------------------------------------------- # + # Resolve optional arguments + + # Use RSP ANSI preset, if none given + if (NOT DEFINED INPUT_PRESET) + set(INPUT_PRESET "${RSP_ANSI_PRESET}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + foreach(item IN LISTS INPUT_PRESET) + + string(REPLACE " " ";" parts "${item}") + list(GET parts 0 name) + + # Debug + # dump(name value) + + unset("${name}" PARENT_SCOPE) + endforeach() + + # ---------------------------------------------------------------------------------------------- # + + # Change state + set(RSP_IS_ANSI_ENABLED false PARENT_SCOPE) + endfunction() +endif () + +if (NOT COMMAND "ansi_sgr") + + #! ansi_sgr : Define an "Select Graphic Rendition (SGR)" ansi control sequence + # + # @see https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters + # @see ansi_escape_sequence() + # + # @example + # ansi_sgr(OUTPUT MY_STYLE CODE "1;31") + # ansi_sgr(OUTPUT RESTORE CODE "0") + # message("${MY_STYLE}Title:${RESTORE} John Doe's Adventures") + # + # @param OUTPUT The output variable to assign escape sequence to. + # @param CODE The control sequence. + # + # @return + # [OUTPUT] The control sequence + # + function(ansi_sgr) + set(oneValueArgs OUTPUT) + set(multiValueArgs CODE) + + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + requires_arguments("OUTPUT;CODE" INPUT) + + ansi_escape_sequence(OUTPUT "${INPUT_OUTPUT}" CODE "[${INPUT_CODE}m") + + return(PROPAGATE "${INPUT_OUTPUT}") + endfunction() +endif () + +if (NOT COMMAND "ansi_escape_sequence") + + #! ansi_escape_sequence : Define an ANSI escape sequence + # + # @see https://en.wikipedia.org/wiki/ANSI_escape_code + # @see RSP_DEFAULT_ESCAPE_CHARACTER + # + # @example + # ansi_escape_sequence(OUTPUT MY_STYLE CODE "[1;31m") + # ansi_escape_sequence(OUTPUT RESTORE CODE "[0m") + # message("${MY_STYLE}Foo${RESTORE} bar") + # + # @param OUTPUT The output variable to assign escape sequence to. + # @param CODE The code sequence. + # @param [ESCAPE ] Escape character to use. Defaults to RSP_DEFAULT_ESCAPE_CHARACTER, + # when none given. + # + # @return + # [OUTPUT] The escape sequence + # + function(ansi_escape_sequence) + set(oneValueArgs OUTPUT CODE ESCAPE_CHARACTER) + + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "" ${ARGN}) + requires_arguments("OUTPUT;CODE" INPUT) + + # ---------------------------------------------------------------------------------------------- # + # Resolve optional arguments + + # Escape character + if (NOT DEFINED INPUT_ESCAPE_CHARACTER) + set(INPUT_ESCAPE_CHARACTER "${RSP_DEFAULT_ESCAPE_CHARACTER}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + set("${INPUT_OUTPUT}" "${INPUT_ESCAPE_CHARACTER}${INPUT_CODE}") + + return(PROPAGATE "${INPUT_OUTPUT}") + endfunction() +endif () \ No newline at end of file From e5e93023beb27e3abd133ff9f1edead5b2d88192 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 12:41:57 +0100 Subject: [PATCH 03/57] Include ANSI sub-module --- cmake/rsp/output.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index c4c7ddc..ea6bbbb 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -5,4 +5,6 @@ include_guard(GLOBAL) # Debug -message(VERBOSE "rsp/output module included") \ No newline at end of file +message(VERBOSE "rsp/output module included") + +include("rsp/output/ansi") \ No newline at end of file From d021ca39f2cc8067134b08beb45846d1ce7a41a6 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 13:09:18 +0100 Subject: [PATCH 04/57] Use requires_arguments() for assert compare values --- cmake/rsp/testing/asserts.cmake | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/cmake/rsp/testing/asserts.cmake b/cmake/rsp/testing/asserts.cmake index 2e47e42..3a6485e 100644 --- a/cmake/rsp/testing/asserts.cmake +++ b/cmake/rsp/testing/asserts.cmake @@ -662,15 +662,8 @@ if (NOT COMMAND "assert_compare_values") function(assert_compare_values) set(oneValueArgs OUTPUT EXPECTED ACTUAL OPERATOR) - cmake_parse_arguments(INPUT "" "${oneValueArgs}" "" ${ARGN}) - - # Ensure required arguments are defined - set(requiredArgs "OUTPUT;EXPECTED;ACTUAL;OPERATOR") - foreach (arg ${requiredArgs}) - if (NOT DEFINED INPUT_${arg}) - message(FATAL_ERROR "${arg} argument is missing, for ${CMAKE_CURRENT_FUNCTION}()") - endif () - endforeach () + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + requires_arguments("OUTPUT;EXPECTED;ACTUAL;OPERATOR" INPUT) # ------------------------------------------------------------------------------------- # From e2d1366f7a0c3e36596dfbd26a4b1cd46369f136 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 13:10:18 +0100 Subject: [PATCH 05/57] Shorten "escape character" input variable Also, allow provided code to contain multiple values (list form), due to expected semicolons in code. --- cmake/rsp/output/ansi.cmake | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cmake/rsp/output/ansi.cmake b/cmake/rsp/output/ansi.cmake index f4d7f6b..d40897f 100644 --- a/cmake/rsp/output/ansi.cmake +++ b/cmake/rsp/output/ansi.cmake @@ -257,22 +257,23 @@ if (NOT COMMAND "ansi_escape_sequence") # [OUTPUT] The escape sequence # function(ansi_escape_sequence) - set(oneValueArgs OUTPUT CODE ESCAPE_CHARACTER) + set(oneValueArgs OUTPUT ESCAPE) + set(multiValueArgs CODE) - cmake_parse_arguments(INPUT "" "${oneValueArgs}" "" ${ARGN}) + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) requires_arguments("OUTPUT;CODE" INPUT) # ---------------------------------------------------------------------------------------------- # # Resolve optional arguments # Escape character - if (NOT DEFINED INPUT_ESCAPE_CHARACTER) - set(INPUT_ESCAPE_CHARACTER "${RSP_DEFAULT_ESCAPE_CHARACTER}") + if (NOT DEFINED INPUT_ESCAPE) + set(INPUT_ESCAPE "${RSP_DEFAULT_ESCAPE_CHARACTER}") endif () # ---------------------------------------------------------------------------------------------- # - set("${INPUT_OUTPUT}" "${INPUT_ESCAPE_CHARACTER}${INPUT_CODE}") + set("${INPUT_OUTPUT}" "${INPUT_ESCAPE}${INPUT_CODE}") return(PROPAGATE "${INPUT_OUTPUT}") endfunction() From 63ddee82c973baad5d39fcb0fd38e94a0ee877a3 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 13:34:45 +0100 Subject: [PATCH 06/57] Add tests for ANSI output utils --- tests/unit/output/ansi_test.cmake | 96 +++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/unit/output/ansi_test.cmake diff --git a/tests/unit/output/ansi_test.cmake b/tests/unit/output/ansi_test.cmake new file mode 100644 index 0000000..41e7b05 --- /dev/null +++ b/tests/unit/output/ansi_test.cmake @@ -0,0 +1,96 @@ +include("rsp/testing") +include("rsp/debug") +include("rsp/output") + +define_test_case( + "ANSI Test" + LABELS "ansi;output" + + AFTER "cleanup_ansi" + + #RUN_SERIAL +) + +function(cleanup_ansi) + disable_ansi() +endfunction() + +# -------------------------------------------------------------------------------------------------------------- # +# Actual tests +# -------------------------------------------------------------------------------------------------------------- # + +define_test("ANSI is disabled by default" "ansi_disabled_by_default") +function(ansi_disabled_by_default) + + assert_defined(RSP_IS_ANSI_ENABLED MESSAGE "state variable not defined") + assert_falsy(RSP_IS_ANSI_ENABLED MESSAGE "ANSI should NOT be enabled, by default") +endfunction() + +define_test("can define ansi escape sequence" "can_define_ansi_escape_sequence") +function(can_define_ansi_escape_sequence) + + # NOTE: We need to replace the default escape char for this test, or we + # cannot compare strings as desired. + set(escape "e") + + + #set(value "[1;31m") # warning, semicolon causes issues for asserts (list form) + #set(value "[31m") # warning, the "[" seems to be parsed somehow by cmake, + # causing assert_compare_values() to interpret "expected" + # to contain a long string of all input vars!? + set(value "31m") + + ansi_escape_sequence(OUTPUT sequence ESCAPE "${escape}" CODE "${value}") + + # Debug + #dump(sequence) + + # Uh... well, this will have to do. A manual test can ensure that this works as + # desired... + assert_string_equals("${escape}${value}" "${sequence}" MESSAGE "Incorrect sequence defined") +endfunction() + +define_test("can define ansi SGR" "can_define_ansi_sgr") +function(can_define_ansi_sgr) + + ansi_sgr(OUTPUT style CODE "1;31") + + # debug + # string(REPLACE "${RSP_DEFAULT_ESCAPE_CHARACTER}[" "@" style "${style}") + + assert_string_not_empty("${style}" MESSAGE "SGR was not defined") +endfunction() + +define_test("can enable ANSI (default preset)" "can_enable_ansi") +function(can_enable_ansi) + + enable_ansi() + + # Ensure that some of the predefined variables are defined, from default preset + assert_defined(TEXT_BOLD) + assert_defined(COLOR_RED) + assert_defined(COLOR_BRIGHT_WHITE) + + assert_truthy(RSP_IS_ANSI_ENABLED MESSAGE "ANSI should had been enabled") +endfunction() + +define_test("can enable ANSI (custom preset)" "can_enable_ansi_custom_preset") +function(can_enable_ansi_custom_preset) + + set(my_preset + "MY_STYLE 1|35" + ) + + enable_ansi(PRESET ${my_preset}) + + # Ensure that some of the predefined variables are defined, from default preset + assert_defined(MY_STYLE MESSAGE "Custom preset not defined") + + # ----------------------------------------------------------------------- # + # Cleanup + + disable_ansi(PRESET ${my_preset}) + + assert_falsy(RSP_IS_ANSI_ENABLED MESSAGE "ANSI disable failure - 'is ansi enabled' state not changed") + assert_not_defined(MY_STYLE MESSAGE "ANSI disable failure - failed to remove custom preset...") +endfunction() \ No newline at end of file From 3e8552578672bb0110ba81a24b179a26665f7f51 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 14:04:00 +0100 Subject: [PATCH 07/57] Fix RSP_ANSI_PRESET not defined for entire project This was a HUGE issue to locate - the predefined preset was simply NOT defined, for reasons that escape me. A dump() revealed that despite the include_guard(GLOBAL), the output/ansi was somehow included twice, despite the the guard that should prevent it. This made cmake behave really strange! --- tests/unit/output/ansi_test.cmake | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/unit/output/ansi_test.cmake b/tests/unit/output/ansi_test.cmake index 41e7b05..35efa47 100644 --- a/tests/unit/output/ansi_test.cmake +++ b/tests/unit/output/ansi_test.cmake @@ -1,6 +1,13 @@ include("rsp/testing") include("rsp/debug") -include("rsp/output") + +# All tests are included via tests/CMakeLists.txt, using the define_test_suite(). +# Yet, it seems that when "rsp/output" is included here, the RSP_ANSI_PRESET list +# either reset or simply not defined! The "include_guard(GLOBAL)" might not +# behave as desired! Therefore, we ONLY include "rsp/output" when executing tests! +if (_RSP_TEST_EXECUTOR_RUNNING) + include("rsp/output") +endif () define_test_case( "ANSI Test" From 13fff16c03c81911abad2c5ec4e7366371876778 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 14:09:35 +0100 Subject: [PATCH 08/57] Add demo output function Useful for a "quick" test / showcase --- cmake/rsp/output/ansi.cmake | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/cmake/rsp/output/ansi.cmake b/cmake/rsp/output/ansi.cmake index d40897f..60c98ea 100644 --- a/cmake/rsp/output/ansi.cmake +++ b/cmake/rsp/output/ansi.cmake @@ -277,4 +277,74 @@ if (NOT COMMAND "ansi_escape_sequence") return(PROPAGATE "${INPUT_OUTPUT}") endfunction() +endif () + +if (NOT COMMAND "output_ansi_demo") + + #! output_ansi_demo : Outputs a demo of the predefined ANSI variables + # + # NOTE: You must ensure that ANSI has been enabled, to see the effect + # in your terminal. + # + # @see enable_ansi() + # + function(output_ansi_demo) + set(txt "Mash up each side of the spinach with one cup of pickles.") + + message("\nANSI output demo:") + + message("Text:") + message("bold: ${TEXT_BOLD}${txt}${RESTORE}") + message("dim/faint: ${TEXT_DIM}${txt}${RESTORE}") + message("italic: ${TEXT_ITALIC}${txt}${RESTORE}") + message("underline: ${TEXT_UNDERLINE}${txt}${RESTORE}") + message("blink: ${TEXT_BLINK}${txt}${RESTORE}") + message("inverse/reverse: ${TEXT_INVERSE}${txt}${RESTORE}") + message("hidden/invisible: ${TEXT_HIDDEN}${txt}${RESTORE}") + message("Strikethrough: ${TEXT_STRIKETHROUGH}${txt}${RESTORE}") + + message("\nColors") + message("black: ${COLOR_BLACK}${txt}${RESTORE}") + message("black bg: ${COLOR_BG_BLACK}${txt}${RESTORE}") + message("black (bright): ${COLOR_BRIGHT_BLACK}${txt}${RESTORE}") + message("black (bright) bg: ${COLOR_BRIGHT_BG_BLACK}${txt}${RESTORE}") + + message("red: ${COLOR_RED}${txt}${RESTORE}") + message("red bg: ${COLOR_BG_RED}${txt}${RESTORE}") + message("red (bright): ${COLOR_BRIGHT_RED}${txt}${RESTORE}") + message("red (bright) bg: ${COLOR_BRIGHT_BG_RED}${txt}${RESTORE}") + + message("green: ${COLOR_GREEN}${txt}${RESTORE}") + message("green bg: ${COLOR_BG_GREEN}${txt}${RESTORE}") + message("green (bright): ${COLOR_BRIGHT_GREEN}${txt}${RESTORE}") + message("green (bright) bg: ${COLOR_BRIGHT_BG_GREEN}${txt}${RESTORE}") + + message("yellow: ${COLOR_YELLOW}${txt}${RESTORE}") + message("yellow bg: ${COLOR_BG_YELLOW}${txt}${RESTORE}") + message("yellow (bright): ${COLOR_BRIGHT_YELLOW}${txt}${RESTORE}") + message("yellow (bright) bg: ${COLOR_BRIGHT_BG_YELLOW}${txt}${RESTORE}") + + message("blue: ${COLOR_BLUE}${txt}${RESTORE}") + message("blue bg: ${COLOR_BG_BLUE}${txt}${RESTORE}") + message("blue (bright): ${COLOR_BRIGHT_BLUE}${txt}${RESTORE}") + message("blue (bright) bg: ${COLOR_BRIGHT_BG_BLUE}${txt}${RESTORE}") + + message("magenta: ${COLOR_MAGENTA}${txt}${RESTORE}") + message("magenta bg: ${COLOR_BG_MAGENTA}${txt}${RESTORE}") + message("magenta (bright): ${COLOR_BRIGHT_MAGENTA}${txt}${RESTORE}") + message("magenta (bright) bg: ${COLOR_BRIGHT_BG_MAGENTA}${txt}${RESTORE}") + + message("cyan: ${COLOR_CYAN}${txt}${RESTORE}") + message("cyan bg: ${COLOR_BG_CYAN}${txt}${RESTORE}") + message("cyan (bright): ${COLOR_BRIGHT_CYAN}${txt}${RESTORE}") + message("cyan (bright) bg: ${COLOR_BRIGHT_BG_CYAN}${txt}${RESTORE}") + + message("white: ${COLOR_WHITE}${txt}${RESTORE}") + message("white bg: ${COLOR_BG_WHITE}${txt}${RESTORE}") + message("white (bright): ${COLOR_BRIGHT_WHITE}${txt}${RESTORE}") + message("white (bright) bg: ${COLOR_BRIGHT_BG_WHITE}${txt}${RESTORE}") + + message("default: ${COLOR_DEFAULT}${txt}${RESTORE}") + message("default bg: ${COLOR_BG_DEFAULT}${txt}${RESTORE}") + endfunction() endif () \ No newline at end of file From c424c56693d23d2001c6a9bfa00c25426557c349 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 14:12:30 +0100 Subject: [PATCH 09/57] Enable ANSI, when project is the root project --- CMakeLists.txt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4918fed..ebe6bda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,13 @@ endif () # Misc. # -------------------------------------------------------------------------------------------------------------- # -# include("rsp/debug") +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include("rsp/debug") + include("rsp/output") + + enable_ansi() -# dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME) \ No newline at end of file + # output_ansi_demo() + + # dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME) +endif() From 3a47b80d82c49c8f9bd8050d90b679bbc9d5bb22 Mon Sep 17 00:00:00 2001 From: alin Date: Thu, 30 Jan 2025 14:14:03 +0100 Subject: [PATCH 10/57] Change release notes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbbe33..247cbc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `CPM.cmake` script that downloads specified version of [CPM](https://github.com/cpm-cmake/CPM.cmake). * `fail_in_source_build()`, `extract_value()`, `requires_arguments()` and `safeguard_properties()` utils functions, in `helpers.cmake`. * `dump()` and `dd()` in `debug.cmake`. +* ANSI utils, in `output.cmake` * `semver_parse()`, `write_version_file` and `version_from_file()` utils, in `version.cmake`. * `git_find_version_tag()` util, in `git.cmake`. * `VERSION` file. From bec7b41263ceabda0c3ee4cbd32ed712a200a07b Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 31 Jan 2025 11:03:20 +0100 Subject: [PATCH 11/57] Add output() function --- cmake/rsp/output.cmake | 166 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index ea6bbbb..1bcbae7 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -1,5 +1,5 @@ # -------------------------------------------------------------------------------------------------------------- # -# Output functions +# Output Utilities # -------------------------------------------------------------------------------------------------------------- # include_guard(GLOBAL) @@ -7,4 +7,166 @@ include_guard(GLOBAL) # Debug message(VERBOSE "rsp/output module included") -include("rsp/output/ansi") \ No newline at end of file +include("rsp/helpers") +include("rsp/output/ansi") + +# -------------------------------------------------------------------------------------------------------------- # +# Defaults... +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT DEFINED RSP_CMAKE_MESSAGE_MODES) + + # CMake's message modes / types + # + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # + set(RSP_CMAKE_MESSAGE_MODES + TRACE + DEBUG + VERBOSE + STATUS + NOTICE + DEPRECATION + AUTHOR_WARNING + WARNING + SEND_ERROR + FATAL_ERROR + ) +endif () + +if (NOT DEFINED RSP_DEFAULT_LABEL_FORMAT) + + # Default label format + # + # The "%label%" is automatically replaced with + # the actual label to be displayed. + # + # @see output() + # + set(RSP_DEFAULT_LABEL_FORMAT "%label%: ") +endif () + +if (NOT DEFINED RSP_DEFAULT_LIST_SEPARATOR) + + # Default list separator + # + # Used when a list is requested printed + # + # @see output() + # + set(RSP_DEFAULT_LIST_SEPARATOR "\n") +endif () + +# -------------------------------------------------------------------------------------------------------------- # +# Output related functions & macros +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT COMMAND "output") + + #! output : Output a message to stdout or stderr (Cmake's message type specific) + # + # @see https://cmake.org/cmake/help/latest/command/message.html + # @see RSP_DEFAULT_LABEL_FORMAT + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to NOTICE, when not specified. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead of + # being printed to stdout or stderr. + # @param [LABEL ] Optional - A label to display as the message's prefix. Defaults to empty string. + # The label does NOT affect how cmake treats the message, it is purely for + # visual purposes. + # @param [LABEL_FORMAT ] Optional - A format string in which the actual label is inserted. + # E.g. "(%label%): ". The "%label%" is replaced with the provided label. + # Defaults to RSP_DEFAULT_LABEL_FORMAT when none given. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + function(output message) + set(options "${RSP_CMAKE_MESSAGE_MODES}") + set(oneValueArgs OUTPUT LABEL LABEL_FORMAT LIST_SEPARATOR) + set(multiValueArgs "") + + # Ensure to parse named arguments only AFTER the "message" parameter, or strange + # behaviour can occur, if one or more of the arguments contain escape codes. + # + # The following will NOT work, if e.g. LABEL contains ANSI escape codes and a LABEL_FORMAT + # is also provided. The LABEL or other arguments are just not parsed correctly! + # cmake_parse_arguments(INPUT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) # Might fail... + + cmake_parse_arguments(PARSE_ARGV 1 INPUT "${options}" "${oneValueArgs}" "${multiValueArgs}") + + # ---------------------------------------------------------------------------------------------- # + + # Message mode + set(mode "NOTICE") + foreach (m IN LISTS options) + if (INPUT_${m}) + message(VERBOSE "Message mode set to: ${m}") + + set(mode "${m}") + break() + endif () + endforeach () + + # Label format + set(label_format "${RSP_DEFAULT_LABEL_FORMAT}") + if (DEFINED INPUT_LABEL_FORMAT) + set(label_format "${INPUT_LABEL_FORMAT}") + endif () + + # Label + set(label "") + if (DEFINED INPUT_LABEL) + string(REPLACE "%label%" "${INPUT_LABEL}" label "${label_format}") + endif () + + # List separator + set(separator "${RSP_DEFAULT_LIST_SEPARATOR}") + if (DEFINED INPUT_LIST_SEPARATOR) + set(separator "${INPUT_LIST_SEPARATOR}") + endif () + + # ---------------------------------------------------------------------------------------------- # + # Resolve message + + set(buffer "${message}") + if(DEFINED ${message}) + message(VERBOSE "message is a variable (${message})") + extract_value(resolved_msg ${message}) + + # Debug + # message(NOTICE "resolved message: ${resolved_msg}") + + # If the variable is a list, then append each entry to buffer + list(LENGTH resolved_msg length) + if (length GREATER 1) + # Replace every semicolon with a newline character. + string(REPLACE ";" "${separator}" resolved_msg "${resolved_msg}") + + # foreach (item IN LISTS resolved_msg) + # message(NOTICE "Item: ${item}") + # endforeach () + endif () + + # Use the resolved message to buffer. + set(buffer "${resolved_msg}") + endif () + + # Prefix final message with evt. label + set(formatted_output "${label}${buffer}") + + # ---------------------------------------------------------------------------------------------- # + + # Assign to output variable, if requested and return... + if (DEFINED INPUT_OUTPUT) + set("${INPUT_OUTPUT}" "${formatted_output}") + return(PROPAGATE "${INPUT_OUTPUT}") + endif () + + # Finally, output the message... + message(${mode} "${formatted_output}") + + endfunction() +endif () \ No newline at end of file From 8761adac43a17c0fba61a341faa90a163ab95bf8 Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 31 Jan 2025 12:07:06 +0100 Subject: [PATCH 12/57] Fix ANSI sequence variables only available for current scope This caused the "include" issue where the RSP ANSI PRESET list was no longer available, at the time when ANSI was enabled, because (in this case) the "rsp/output" was first included inside the test(s) files, meaning that sequence variables were only define within that file's scope! --- cmake/rsp/output.cmake | 6 ++++-- cmake/rsp/output/ansi.cmake | 25 ++++++++++++++++++++----- tests/unit/output/ansi_test.cmake | 9 +-------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 1bcbae7..0b9bfbe 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -31,6 +31,8 @@ if (NOT DEFINED RSP_CMAKE_MESSAGE_MODES) WARNING SEND_ERROR FATAL_ERROR + + CACHE STRING " RSP cmake message modes / types" ) endif () @@ -43,7 +45,7 @@ if (NOT DEFINED RSP_DEFAULT_LABEL_FORMAT) # # @see output() # - set(RSP_DEFAULT_LABEL_FORMAT "%label%: ") + set(RSP_DEFAULT_LABEL_FORMAT "%label%: " CACHE STRING " RSP Default label format for output()") endif () if (NOT DEFINED RSP_DEFAULT_LIST_SEPARATOR) @@ -54,7 +56,7 @@ if (NOT DEFINED RSP_DEFAULT_LIST_SEPARATOR) # # @see output() # - set(RSP_DEFAULT_LIST_SEPARATOR "\n") + set(RSP_DEFAULT_LIST_SEPARATOR "\\n" CACHE STRING " RSP Default list separator for output()") endif () # -------------------------------------------------------------------------------------------------------------- # diff --git a/cmake/rsp/output/ansi.cmake b/cmake/rsp/output/ansi.cmake index 60c98ea..4eec72e 100644 --- a/cmake/rsp/output/ansi.cmake +++ b/cmake/rsp/output/ansi.cmake @@ -13,7 +13,7 @@ if (NOT DEFINED RSP_IS_ANSI_ENABLED) # # @internal # - set(RSP_IS_ANSI_ENABLED false) + set(RSP_IS_ANSI_ENABLED false CACHE BOOL "RSP ANSI enable state") endif () if (NOT DEFINED RSP_DEFAULT_ESCAPE_CHARACTER) @@ -24,6 +24,7 @@ if (NOT DEFINED RSP_DEFAULT_ESCAPE_CHARACTER) # @see ansi_escape_sequence() # string(ASCII 27 RSP_DEFAULT_ESCAPE_CHARACTER) + set(RSP_DEFAULT_ESCAPE_CHARACTER "${RSP_DEFAULT_ESCAPE_CHARACTER}" CACHE STRING " RSP default escape character") endif () if (NOT DEFINED RSP_ANSI_PRESET) @@ -112,6 +113,8 @@ if (NOT DEFINED RSP_ANSI_PRESET) "COLOR_DEFAULT 39" "COLOR_BG_DEFAULT 49" + + CACHE STRING " RSP ANSI preset" ) endif () @@ -126,6 +129,8 @@ if (NOT COMMAND "enable_ansi") # ANSI codes. Defaults to RSP_ANSI_PRESET. # function(enable_ansi) + message(VERBOSE "Enabling ANSI") + set(multiValueArgs PRESET) cmake_parse_arguments(INPUT "" "" "${multiValueArgs}" ${ARGN}) @@ -139,6 +144,10 @@ if (NOT COMMAND "enable_ansi") # ---------------------------------------------------------------------------------------------- # + # Debug + # list(LENGTH INPUT_PRESET length ) + # message(">> Processing (${length}): ${INPUT_PRESET}") + foreach(item IN LISTS INPUT_PRESET) string(REPLACE " " ";" parts "${item}") @@ -153,13 +162,15 @@ if (NOT COMMAND "enable_ansi") ansi_sgr(OUTPUT sequence CODE "${value}") - set("${name}" "${sequence}" PARENT_SCOPE) + # Define the variable and sequence "globally"! + # set("${name}" "${sequence}" PARENT_SCOPE) # Will ONLY affect parent scope, ... + set("${name}" "${sequence}" CACHE STRING " RSP ANSI sequence" FORCE) endforeach() # ---------------------------------------------------------------------------------------------- # # Change state - set(RSP_IS_ANSI_ENABLED true PARENT_SCOPE) + set(RSP_IS_ANSI_ENABLED true CACHE BOOL "RSP ANSI enable state" FORCE) endfunction() endif () @@ -174,6 +185,8 @@ if (NOT COMMAND "disable_ansi") # ANSI codes. Defaults to RSP_ANSI_PRESET. # function(disable_ansi) + message(VERBOSE "Disabling ANSI") + set(multiValueArgs PRESET) cmake_parse_arguments(INPUT "" "" "${multiValueArgs}" ${ARGN}) @@ -195,13 +208,15 @@ if (NOT COMMAND "disable_ansi") # Debug # dump(name value) - unset("${name}" PARENT_SCOPE) + # Remove variable and sequence "globally"! + # unset("${name}" PARENT_SCOPE) # Will ONLY affect parent scope, ... + unset("${name}" CACHE) endforeach() # ---------------------------------------------------------------------------------------------- # # Change state - set(RSP_IS_ANSI_ENABLED false PARENT_SCOPE) + set(RSP_IS_ANSI_ENABLED false CACHE BOOL "RSP ANSI enable state" FORCE) endfunction() endif () diff --git a/tests/unit/output/ansi_test.cmake b/tests/unit/output/ansi_test.cmake index 35efa47..41e7b05 100644 --- a/tests/unit/output/ansi_test.cmake +++ b/tests/unit/output/ansi_test.cmake @@ -1,13 +1,6 @@ include("rsp/testing") include("rsp/debug") - -# All tests are included via tests/CMakeLists.txt, using the define_test_suite(). -# Yet, it seems that when "rsp/output" is included here, the RSP_ANSI_PRESET list -# either reset or simply not defined! The "include_guard(GLOBAL)" might not -# behave as desired! Therefore, we ONLY include "rsp/output" when executing tests! -if (_RSP_TEST_EXECUTOR_RUNNING) - include("rsp/output") -endif () +include("rsp/output") define_test_case( "ANSI Test" From cbfe74909e19f7407c197e513964258044cfdeaa Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 31 Jan 2025 12:24:01 +0100 Subject: [PATCH 13/57] Add tests for output() --- tests/unit/output/output_test.cmake | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/unit/output/output_test.cmake diff --git a/tests/unit/output/output_test.cmake b/tests/unit/output/output_test.cmake new file mode 100644 index 0000000..aece170 --- /dev/null +++ b/tests/unit/output/output_test.cmake @@ -0,0 +1,65 @@ +include("rsp/testing") +include("rsp/output") + +define_test_case( + "Output Test" + LABELS "output" +) + +# -------------------------------------------------------------------------------------------------------------- # +# Actual tests +# -------------------------------------------------------------------------------------------------------------- # + +define_test("can output string" "can_output_str_msg") +function(can_output_str_msg) + + output("foo" OUTPUT result) + + assert_string_equals("foo" "${result}" MESSAGE "Incorrect output") +endfunction() + +define_test("can output variable value" "can_output_var_value_msg") +function(can_output_var_value_msg) + + set(foo "bar") + output(foo OUTPUT result) + + assert_string_equals("${foo}" "${result}" MESSAGE "Incorrect output") +endfunction() + +define_test("can output list" "can_output_list_msg") +function(can_output_list_msg) + + set(my_list "a;b;c") + output(my_list OUTPUT result LIST_SEPARATOR ", ") + + set(expected "a, b, c") + + assert_string_equals("${expected}" "${result}" MESSAGE "Incorrect output") +endfunction() + +define_test("can output with label" "can_output_with_label_msg") +function(can_output_with_label_msg) + + set(msg "Hi there") + set(label "INFO: ") + set(label_format "%label%") # Keep format simple for test... + + output(msg + OUTPUT result + LABEL "${label}" + LABEL_FORMAT "${label_format}" + ) + + set(expected "${label}${msg}") + + assert_string_equals("${expected}" "${result}" MESSAGE "Incorrect output") +endfunction() + +define_test("applies cmake message mode" "applies_cmake_msg_mode" EXPECT_FAILURE) +function(applies_cmake_msg_mode) + + # CMake's FATAL_ERROR message mode requested, meaning that test + # MUST be expected to fail... + output("foo" FATAL_ERROR) +endfunction() \ No newline at end of file From 96bec1f396fc7a488dced2e536c370fe835d5e96 Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 31 Jan 2025 12:43:40 +0100 Subject: [PATCH 14/57] Refactor output(), extract message mode handling into macro Other output util functions will most likely also benefit from this. --- cmake/rsp/output.cmake | 37 +++--------------------- cmake/rsp/output/utils.cmake | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 cmake/rsp/output/utils.cmake diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 0b9bfbe..845de95 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -1,5 +1,5 @@ # -------------------------------------------------------------------------------------------------------------- # -# Output Utilities +# Output # -------------------------------------------------------------------------------------------------------------- # include_guard(GLOBAL) @@ -8,34 +8,13 @@ include_guard(GLOBAL) message(VERBOSE "rsp/output module included") include("rsp/helpers") +include("rsp/output/utils") include("rsp/output/ansi") # -------------------------------------------------------------------------------------------------------------- # # Defaults... # -------------------------------------------------------------------------------------------------------------- # -if (NOT DEFINED RSP_CMAKE_MESSAGE_MODES) - - # CMake's message modes / types - # - # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages - # - set(RSP_CMAKE_MESSAGE_MODES - TRACE - DEBUG - VERBOSE - STATUS - NOTICE - DEPRECATION - AUTHOR_WARNING - WARNING - SEND_ERROR - FATAL_ERROR - - CACHE STRING " RSP cmake message modes / types" - ) -endif () - if (NOT DEFINED RSP_DEFAULT_LABEL_FORMAT) # Default label format @@ -102,15 +81,7 @@ if (NOT COMMAND "output") # ---------------------------------------------------------------------------------------------- # # Message mode - set(mode "NOTICE") - foreach (m IN LISTS options) - if (INPUT_${m}) - message(VERBOSE "Message mode set to: ${m}") - - set(mode "${m}") - break() - endif () - endforeach () + resolve_msg_mode() # Label format set(label_format "${RSP_DEFAULT_LABEL_FORMAT}") @@ -168,7 +139,7 @@ if (NOT COMMAND "output") endif () # Finally, output the message... - message(${mode} "${formatted_output}") + message(${msg_mode} "${formatted_output}") endfunction() endif () \ No newline at end of file diff --git a/cmake/rsp/output/utils.cmake b/cmake/rsp/output/utils.cmake new file mode 100644 index 0000000..a88e26e --- /dev/null +++ b/cmake/rsp/output/utils.cmake @@ -0,0 +1,56 @@ +# -------------------------------------------------------------------------------------------------------------- # +# Output Utils +# -------------------------------------------------------------------------------------------------------------- # + +# -------------------------------------------------------------------------------------------------------------- # +# Message Modes +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT DEFINED RSP_CMAKE_MESSAGE_MODES) + + # CMake's message modes / types + # + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # + set(RSP_CMAKE_MESSAGE_MODES + TRACE + DEBUG + VERBOSE + STATUS + NOTICE + DEPRECATION + AUTHOR_WARNING + WARNING + SEND_ERROR + FATAL_ERROR + + CACHE STRING " RSP cmake message modes / types" + ) +endif () + +if (NOT COMMAND "resolve_msg_mode") + + #! resolve_msg_mode : Resolves requested CMake message mode + # + # WARNING: Macro is intended to be used internally within misc. output + # functions. It sets a `msg_mode`, based on the option requested, or + # defaults to `NOTICE`. + # + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # @see RSP_CMAKE_MESSAGE_MODES + # + # @internal + # + macro(resolve_msg_mode) + set(msg_mode "NOTICE") + + foreach (m IN LISTS RSP_CMAKE_MESSAGE_MODES) + if (INPUT_${m}) + message(VERBOSE "Message mode set to: ${m}") + + set(msg_mode "${m}") + break() + endif () + endforeach () + endmacro() +endif () \ No newline at end of file From c15906e11fe86e1c21af60f6b8087a8b5e4cc658 Mon Sep 17 00:00:00 2001 From: alin Date: Fri, 31 Jan 2025 15:43:43 +0100 Subject: [PATCH 15/57] Add logging module (incomplete) --- cmake/rsp/logging.cmake | 340 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 cmake/rsp/logging.cmake diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake new file mode 100644 index 0000000..59e3dd4 --- /dev/null +++ b/cmake/rsp/logging.cmake @@ -0,0 +1,340 @@ +# -------------------------------------------------------------------------------------------------------------- # +# Logging +# -------------------------------------------------------------------------------------------------------------- # +# +# The herein defined console log functions and macros are heavily inspired by the "PSR-3: Logger Interface". +# Originally licensed as MIT - Copyright (c) 2012 PHP Framework Interoperability Group. +# +# @see https://github.com/php-fig/log +# @see https://www.php-fig.org/psr/psr-3/ +# + +include_guard(GLOBAL) + +# Debug +message(VERBOSE "rsp/logging module included") + +include("rsp/helpers") +include("rsp/output") + +# -------------------------------------------------------------------------------------------------------------- # +# Defaults... +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT DEFINED RSP_LOG_LEVELS) + + # System is unusable. + set(EMERGENCY_LEVEL "emergency" CACHE STRING "RSP log level") + + # Action must be taken immediately. + set(ALERT_LEVEL "alert" CACHE STRING "RSP log level") + + # Critical conditions. + set(CRITICAL_LEVEL "critical" CACHE STRING "RSP log level") + + # Runtime errors that do not require immediate action but should typically + # be logged and monitored. + set(ERROR_LEVEL "error" CACHE STRING "RSP log level") + + # Exceptional occurrences that are not errors. + set(WARNING_LEVEL "warning" CACHE STRING "RSP log level") + + # Normal but significant events. + set(NOTICE_LEVEL "notice" CACHE STRING "RSP log level") + + # Interesting events. + set(INFO_LEVEL "info" CACHE STRING "RSP log level") + + # Detailed debug information. + set(DEBUG_LEVEL "debug" CACHE STRING "RSP log level") + + set(RSP_LOG_LEVELS + "${EMERGENCY_LEVEL}" + "${ALERT_LEVEL}" + "${CRITICAL_LEVEL}" + "${ERROR_LEVEL}" + "${WARNING_LEVEL}" + "${NOTICE_LEVEL}" + "${INFO_LEVEL}" + "${DEBUG_LEVEL}" + + CACHE STRING "RSP log levels" + ) +endif () + +# TODO: Pre-defined message modes for each log-level! + +if (NOT DEFINED RSP_LOG_SHOW_TIMESTAMP) + + # Show timestamp for log entries + set(RSP_LOG_SHOW_TIMESTAMP true CACHE BOOL " RSP show timestamp for log entries") +endif () + +if (NOT DEFINED RSP_LOG_TIMESTAMP_FORMAT) + + # Log timestamp format + set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE BOOL " RSP log timestamp format") +endif () + +# -------------------------------------------------------------------------------------------------------------- # +# Log functions +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT COMMAND "log") + + #! log : TODO + # + function(log level message) + set(options "${RSP_CMAKE_MESSAGE_MODES}") + set(oneValueArgs OUTPUT LIST_SEPARATOR) + set(multiValueArgs CONTEXT) + + cmake_parse_arguments(PARSE_ARGV 2 INPUT "${options}" "${oneValueArgs}" "${multiValueArgs}") + + # ---------------------------------------------------------------------------------------------- # + + # Resolve the requested log level + extract_value(log_level "${level}") + list(FIND RSP_LOG_LEVELS "${log_level}" level_exists) + if (level_exists EQUAL -1) + message(FATAL_ERROR "Log level '${log_level}' is NOT supported") + endif () + + # TODO: Message mode - should perhaps use a list, in combination to given level + resolve_msg_mode() + + # ---------------------------------------------------------------------------------------------- # + + # Format message label, acc. to the log level + format_log_level_label("${log_level}" label) + + # List separator + set(separator "${RSP_DEFAULT_LIST_SEPARATOR}") + if (DEFINED INPUT_LIST_SEPARATOR) + set(separator "${INPUT_LIST_SEPARATOR}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + set(buffer "") + + # Resolve and format the message - similar to what output() does... + set(resolved_msg "${message}") + if(DEFINED ${message}) + message(VERBOSE "log message is a variable (${message})") + extract_value(resolved_msg ${message}) + + # If the variable is a list, then append each entry to buffer + list(LENGTH resolved_msg length) + if (length GREATER 1) + # Replace every semicolon with a newline character. + string(REPLACE ";" "${separator}" resolved_msg "${resolved_msg}") + endif () + endif () + + # Format the log message, if needed... + format_log_message("${log_level}" "${resolved_msg}" buffer) + + # ---------------------------------------------------------------------------------------------- # + + # Context + if (DEFINED INPUT_CONTEXT) + format_log_context("${log_level}" "${INPUT_CONTEXT}" formatted_context) + string(APPEND buffer "${formatted_context}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + # timestamp + if (RSP_LOG_SHOW_TIMESTAMP) + format_log_timestamp("${log_level}" timestamp) + string(APPEND buffer "${timestamp}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + set(formatted_output "${buffer}") + + # TODO: Capture to OUTPUT...? + + output(${formatted_output} + "${msg_mode}" + LABEL "${label}" + ) + + endfunction() +endif () + +# -------------------------------------------------------------------------------------------------------------- # +# Log formatting utils +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT COMMAND "format_log_level_label") + + #! format_log_level_label : Formats a label, acc. to specified log level + # + # @param level Log level + # @param output The output variable to assign result to + # + # @return + # output The formatted label + # + function(format_log_level_label level output) + set(style_affix "_LABEL_STYLE") + + set("${EMERGENCY_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}${TEXT_ITALIC}") + set("${ALERT_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") + set("${CRITICAL_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") + set("${ERROR_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") + set("${WARNING_LEVEL}${style_affix}" "${COLOR_YELLOW}${TEXT_BOLD}") + set("${NOTICE_LEVEL}${style_affix}" "${COLOR_BRIGHT_BLUE}") + set("${INFO_LEVEL}${style_affix}" "${COLOR_BLUE}") + set("${DEBUG_LEVEL}${style_affix}" "${COLOR_WHITE}") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to find style for log level: ${level}") + endif () + + # Apply evt. additional formatting of label... + set(label "${level}") + + set("${output}" "${${level}${style_affix}}${label}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + +if (NOT COMMAND "format_log_message") + + #! format_log_message : Formats given log message, acc. to given log level + # + # @param level Log level + # @param message The log message + # @param output The output variable to assign result to + # + # @return + # output The formatted message + # + function(format_log_message level message output) + set(style_affix "_MESSAGE_STYLE") + + set("${EMERGENCY_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${ALERT_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${CRITICAL_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "${TEXT_ITALIC}") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style message for log level: ${level}") + endif () + + # Apply evt. additional formatting of message... + set(formatted "${message}") + + set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + + +if (NOT COMMAND "format_log_context") + + #! format_log_context : Formats evt. context variables, acc. to log level + # + # @param level Log level + # @param context List of variables + # @param output The output variable to assign result to + # + # @return + # output The formatted log context + # + function(format_log_context level context output) + set(style_affix "_CONTEXT_STYLE") + + set("${EMERGENCY_LEVEL}${style_affix}" "") + set("${ALERT_LEVEL}${style_affix}" "") + set("${CRITICAL_LEVEL}${style_affix}" "") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style context for log level: ${level}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + set(indent "\t") + set(buffer "") + + # ---------------------------------------------------------------------------------------------- # + + string(APPEND buffer "\n${indent}[") + + foreach (prop IN LISTS context) + + set(entry "${COLOR_BRIGHT_MAGENTA}${TEXT_ITALIC}${prop}${RESTORE} = ${${prop}}") + + string(REPEAT "${indent}" 2 i) + string(APPEND buffer "\n${i}${entry}") + endforeach () + + string(APPEND buffer "\n${indent}]") + + # ---------------------------------------------------------------------------------------------- # + + set(formatted "\n\t${COLOR_MAGENTA}Context${RESTORE}: ${buffer}") + + # ---------------------------------------------------------------------------------------------- # + + set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + +if (NOT COMMAND "format_log_timestamp") + + #! format_log_timestamp : Formats current timestamp, acc. to given log level + # + # @param level Log level + # @param output The output variable to assign result to + # + # @return + # output The formatted timestamp + # + function(format_log_timestamp level output) + set(style_affix "_TIMESTAMP_STYLE") + + set("${EMERGENCY_LEVEL}${style_affix}" "") + set("${ALERT_LEVEL}${style_affix}" "") + set("${CRITICAL_LEVEL}${style_affix}" "") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style timestamp for log level: ${level}") + endif () + + # Timestamp + string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") + set(formatted "\n\t${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") + + set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () \ No newline at end of file From 5bf7d6f1873c96578ca5bf7a24cd637224e27e62 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 12:12:37 +0100 Subject: [PATCH 16/57] Preserve message formatting, even when cmake's warn and above takes over This isn't 100%, but at least the colouring is preserved, and some of the double new lines have been removed. There is still a single double line that I was unable to figure out why it is being added. --- cmake/rsp/output.cmake | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 845de95..815f27c 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -118,6 +118,7 @@ if (NOT COMMAND "output") # Replace every semicolon with a newline character. string(REPLACE ";" "${separator}" resolved_msg "${resolved_msg}") + # Debug # foreach (item IN LISTS resolved_msg) # message(NOTICE "Item: ${item}") # endforeach () @@ -132,12 +133,28 @@ if (NOT COMMAND "output") # ---------------------------------------------------------------------------------------------- # - # Assign to output variable, if requested and return... + # Assign to output variable, if requested and stop any further processing. if (DEFINED INPUT_OUTPUT) set("${INPUT_OUTPUT}" "${formatted_output}") return(PROPAGATE "${INPUT_OUTPUT}") endif () + # ---------------------------------------------------------------------------------------------- # + + # Attempt to keep the formatting, even when the message mode is + # one that cmake formats. + set(target_modes "AUTHOR_WARNING;WARNING;SEND_ERROR;FATAL_ERROR") + list(FIND target_modes ${msg_mode} mode_exists) + if (NOT mode_exists EQUAL -1) + + # The "Carriage return (enter key)" seems to abort cmake's default + # formatting. However, each newline must be indented with a space, + # such that message() interprets it as a formatted message. + string(ASCII 13 CR) + set(formatted_output "${CR}${formatted_output}") + string(REPLACE "\n\t" "\n " formatted_output "${formatted_output}") + endif () + # Finally, output the message... message(${msg_mode} "${formatted_output}") From f5f920cdddf103ee8698d69632c57ff66da60a0e Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 12:21:33 +0100 Subject: [PATCH 17/57] Add RSP_LOG_INDENT Also, reformatted some of the log components. --- cmake/rsp/logging.cmake | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 59e3dd4..7d3f888 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -76,6 +76,12 @@ if (NOT DEFINED RSP_LOG_TIMESTAMP_FORMAT) set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE BOOL " RSP log timestamp format") endif () +if (NOT DEFINED RSP_LOG_INDENT) + + # Indentation for log context, timestamp,...etc + set(RSP_LOG_INDENT " " CACHE STRING " RSP ident for log context, timestamp...etc") +endif () + # -------------------------------------------------------------------------------------------------------------- # # Log functions # -------------------------------------------------------------------------------------------------------------- # @@ -180,9 +186,9 @@ if (NOT COMMAND "format_log_level_label") # output The formatted label # function(format_log_level_label level output) - set(style_affix "_LABEL_STYLE") + set(style_affix "_style") - set("${EMERGENCY_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}${TEXT_ITALIC}") + set("${EMERGENCY_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") set("${ALERT_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") set("${CRITICAL_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") set("${ERROR_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") @@ -217,13 +223,13 @@ if (NOT COMMAND "format_log_message") # output The formatted message # function(format_log_message level message output) - set(style_affix "_MESSAGE_STYLE") + set(style_affix "_style") - set("${EMERGENCY_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${EMERGENCY_LEVEL}${style_affix}" "${TEXT_BOLD}${TEXT_ITALIC}") set("${ALERT_LEVEL}${style_affix}" "${TEXT_BOLD}") set("${CRITICAL_LEVEL}${style_affix}" "${TEXT_BOLD}") set("${ERROR_LEVEL}${style_affix}" "") - set("${WARNING_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "${TEXT_ITALIC}") set("${NOTICE_LEVEL}${style_affix}" "") set("${INFO_LEVEL}${style_affix}" "") set("${DEBUG_LEVEL}${style_affix}" "${TEXT_ITALIC}") @@ -255,7 +261,7 @@ if (NOT COMMAND "format_log_context") # output The formatted log context # function(format_log_context level context output) - set(style_affix "_CONTEXT_STYLE") + set(style_affix "_style") set("${EMERGENCY_LEVEL}${style_affix}" "") set("${ALERT_LEVEL}${style_affix}" "") @@ -273,7 +279,7 @@ if (NOT COMMAND "format_log_context") # ---------------------------------------------------------------------------------------------- # - set(indent "\t") + set(indent "${RSP_LOG_INDENT}") set(buffer "") # ---------------------------------------------------------------------------------------------- # @@ -290,13 +296,11 @@ if (NOT COMMAND "format_log_context") string(APPEND buffer "\n${indent}]") - # ---------------------------------------------------------------------------------------------- # - - set(formatted "\n\t${COLOR_MAGENTA}Context${RESTORE}: ${buffer}") + set(formatted "${COLOR_MAGENTA}Context${RESTORE}: ${buffer}") # ---------------------------------------------------------------------------------------------- # - set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + set("${output}" "\n${indent}${${level}${style_affix}}${formatted}${RESTORE}") return(PROPAGATE "${output}") endfunction() @@ -313,7 +317,7 @@ if (NOT COMMAND "format_log_timestamp") # output The formatted timestamp # function(format_log_timestamp level output) - set(style_affix "_TIMESTAMP_STYLE") + set(style_affix "_style") set("${EMERGENCY_LEVEL}${style_affix}" "") set("${ALERT_LEVEL}${style_affix}" "") @@ -331,9 +335,9 @@ if (NOT COMMAND "format_log_timestamp") # Timestamp string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") - set(formatted "\n\t${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") + set(formatted "${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") - set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + set("${output}" "\n${RSP_LOG_INDENT}${${level}${style_affix}}${formatted}${RESTORE}") return(PROPAGATE "${output}") endfunction() From b9f974800f47d4f7c92464bbc50612adf366c658 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 12:31:58 +0100 Subject: [PATCH 18/57] Improve function description --- cmake/rsp/output.cmake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 815f27c..1105d2d 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -64,6 +64,9 @@ if (NOT COMMAND "output") # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. # Defaults to RSP_DEFAULT_LIST_SEPARATOR. # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # function(output message) set(options "${RSP_CMAKE_MESSAGE_MODES}") set(oneValueArgs OUTPUT LABEL LABEL_FORMAT LIST_SEPARATOR) From 0a1db69795930e07277531c5675c9ce766f439fe Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 12:44:27 +0100 Subject: [PATCH 19/57] Change resolve_msg_mode() to accept a default mode Macro is now slightly more flexible. --- cmake/rsp/output.cmake | 2 +- cmake/rsp/output/utils.cmake | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 1105d2d..f5fc145 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -84,7 +84,7 @@ if (NOT COMMAND "output") # ---------------------------------------------------------------------------------------------- # # Message mode - resolve_msg_mode() + resolve_msg_mode("NOTICE") # Label format set(label_format "${RSP_DEFAULT_LABEL_FORMAT}") diff --git a/cmake/rsp/output/utils.cmake b/cmake/rsp/output/utils.cmake index a88e26e..6abba0e 100644 --- a/cmake/rsp/output/utils.cmake +++ b/cmake/rsp/output/utils.cmake @@ -34,15 +34,18 @@ if (NOT COMMAND "resolve_msg_mode") # # WARNING: Macro is intended to be used internally within misc. output # functions. It sets a `msg_mode`, based on the option requested, or - # defaults to `NOTICE`. + # defaults to the `default_mode` argument. # # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages # @see RSP_CMAKE_MESSAGE_MODES # + # @param default_mode The default mode to return, when none + # was requested. E.g. "NOTICE". + # # @internal # - macro(resolve_msg_mode) - set(msg_mode "NOTICE") + macro(resolve_msg_mode default_mode) + set(msg_mode "${default_mode}") foreach (m IN LISTS RSP_CMAKE_MESSAGE_MODES) if (INPUT_${m}) From 6e3040eef63143b14e8befc79981236ba647c0cd Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:08:27 +0100 Subject: [PATCH 20/57] Resolve cmake message mode, acc. to PSR log level The map should allow developers to customise this in any way they wish. Also, added output capture logic. --- cmake/rsp/logging.cmake | 88 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 8 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 7d3f888..29ee9a0 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -48,6 +48,10 @@ if (NOT DEFINED RSP_LOG_LEVELS) # Detailed debug information. set(DEBUG_LEVEL "debug" CACHE STRING "RSP log level") + # PSR Log levels + # + # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel + # set(RSP_LOG_LEVELS "${EMERGENCY_LEVEL}" "${ALERT_LEVEL}" @@ -62,7 +66,31 @@ if (NOT DEFINED RSP_LOG_LEVELS) ) endif () -# TODO: Pre-defined message modes for each log-level! + +if (NOT DEFINED RSP_LOG_LEVELS_CMAKE) + + # A "map" of the PSR defined log levels and what each + # level corresponds to for cmake's modes / types. + # + # This "map" is used internally by log(), when no `mode` + # argument has been specified. + # + # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # + set(RSP_LOG_LEVELS_CMAKE + "${EMERGENCY_LEVEL} FATAL_ERROR" + "${ALERT_LEVEL} FATAL_ERROR" + "${CRITICAL_LEVEL} FATAL_ERROR" + "${ERROR_LEVEL} SEND_ERROR" + "${WARNING_LEVEL} WARNING" + "${NOTICE_LEVEL} NOTICE" + "${INFO_LEVEL} NOTICE" + "${DEBUG_LEVEL} DEBUG" + + CACHE STRING " RSP log levels map for cmake's message mode" + ) +endif () if (NOT DEFINED RSP_LOG_SHOW_TIMESTAMP) @@ -88,7 +116,24 @@ endif () if (NOT COMMAND "log") - #! log : TODO + #! log : Log a message with an arbitrary level + # + # @see rsp/output::output() + # + # @param level The log level (see RSP_LOG_LEVELS) + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # NOTICE, when not specified. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead of + # being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. # function(log level message) set(options "${RSP_CMAKE_MESSAGE_MODES}") @@ -105,10 +150,29 @@ if (NOT COMMAND "log") if (level_exists EQUAL -1) message(FATAL_ERROR "Log level '${log_level}' is NOT supported") endif () - - # TODO: Message mode - should perhaps use a list, in combination to given level - resolve_msg_mode() - + + # ---------------------------------------------------------------------------------------------- # + + # Resolve CMake's message mode, acc. to specified log level. + set(default_cmake_msg_mode "NOTICE") + foreach (lvl IN LISTS RSP_LOG_LEVELS_CMAKE) + string(REPLACE " " ";" parts "${lvl}") + + # name = PSR log level, value = CMake message mode + list(GET parts 0 name) + list(GET parts 1 value) + + if ("${name}" STREQUAL "${log_level}") + message(VERBOSE "Mapping ${log_level} level to cmake message mode ${value}") + + set(default_cmake_msg_mode "${value}") + break() + endif () + endforeach () + + # - Resolve the actual `msg_mode`. If none is given, then `default_cmake_msg_mode` is used! + resolve_msg_mode("${default_cmake_msg_mode}") + # ---------------------------------------------------------------------------------------------- # # Format message label, acc. to the log level @@ -161,13 +225,21 @@ if (NOT COMMAND "log") set(formatted_output "${buffer}") - # TODO: Capture to OUTPUT...? + # ---------------------------------------------------------------------------------------------- # + + # Assign to output variable, if requested and stop any further processing. + if (DEFINED INPUT_OUTPUT) + set("${INPUT_OUTPUT}" "${label}${formatted_output}") + return(PROPAGATE "${INPUT_OUTPUT}") + endif () + # ---------------------------------------------------------------------------------------------- # + + # Finally, output the log message output(${formatted_output} "${msg_mode}" LABEL "${label}" ) - endfunction() endif () From bb5709d90837620435aa300454ba7908e27c5351 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:11:03 +0100 Subject: [PATCH 21/57] Fix missing include guard --- cmake/rsp/output/utils.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/rsp/output/utils.cmake b/cmake/rsp/output/utils.cmake index 6abba0e..546a9e3 100644 --- a/cmake/rsp/output/utils.cmake +++ b/cmake/rsp/output/utils.cmake @@ -2,6 +2,11 @@ # Output Utils # -------------------------------------------------------------------------------------------------------------- # +include_guard(GLOBAL) + +# Debug +message(VERBOSE "rsp/output/utils module included") + # -------------------------------------------------------------------------------------------------------------- # # Message Modes # -------------------------------------------------------------------------------------------------------------- # From 9dfcb223d1d1ad159ccfdf317def1d580ca712d0 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:14:51 +0100 Subject: [PATCH 22/57] Refactor, extract formatting utils into sub-module --- cmake/rsp/logging.cmake | 262 +------------------------------- cmake/rsp/logging/utils.cmake | 273 ++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 260 deletions(-) create mode 100644 cmake/rsp/logging/utils.cmake diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 29ee9a0..651a642 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -16,99 +16,13 @@ message(VERBOSE "rsp/logging module included") include("rsp/helpers") include("rsp/output") +include("rsp/logging/utils") # -------------------------------------------------------------------------------------------------------------- # # Defaults... # -------------------------------------------------------------------------------------------------------------- # -if (NOT DEFINED RSP_LOG_LEVELS) - - # System is unusable. - set(EMERGENCY_LEVEL "emergency" CACHE STRING "RSP log level") - - # Action must be taken immediately. - set(ALERT_LEVEL "alert" CACHE STRING "RSP log level") - - # Critical conditions. - set(CRITICAL_LEVEL "critical" CACHE STRING "RSP log level") - - # Runtime errors that do not require immediate action but should typically - # be logged and monitored. - set(ERROR_LEVEL "error" CACHE STRING "RSP log level") - - # Exceptional occurrences that are not errors. - set(WARNING_LEVEL "warning" CACHE STRING "RSP log level") - - # Normal but significant events. - set(NOTICE_LEVEL "notice" CACHE STRING "RSP log level") - - # Interesting events. - set(INFO_LEVEL "info" CACHE STRING "RSP log level") - - # Detailed debug information. - set(DEBUG_LEVEL "debug" CACHE STRING "RSP log level") - - # PSR Log levels - # - # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel - # - set(RSP_LOG_LEVELS - "${EMERGENCY_LEVEL}" - "${ALERT_LEVEL}" - "${CRITICAL_LEVEL}" - "${ERROR_LEVEL}" - "${WARNING_LEVEL}" - "${NOTICE_LEVEL}" - "${INFO_LEVEL}" - "${DEBUG_LEVEL}" - - CACHE STRING "RSP log levels" - ) -endif () - - -if (NOT DEFINED RSP_LOG_LEVELS_CMAKE) - - # A "map" of the PSR defined log levels and what each - # level corresponds to for cmake's modes / types. - # - # This "map" is used internally by log(), when no `mode` - # argument has been specified. - # - # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel - # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages - # - set(RSP_LOG_LEVELS_CMAKE - "${EMERGENCY_LEVEL} FATAL_ERROR" - "${ALERT_LEVEL} FATAL_ERROR" - "${CRITICAL_LEVEL} FATAL_ERROR" - "${ERROR_LEVEL} SEND_ERROR" - "${WARNING_LEVEL} WARNING" - "${NOTICE_LEVEL} NOTICE" - "${INFO_LEVEL} NOTICE" - "${DEBUG_LEVEL} DEBUG" - - CACHE STRING " RSP log levels map for cmake's message mode" - ) -endif () - -if (NOT DEFINED RSP_LOG_SHOW_TIMESTAMP) - - # Show timestamp for log entries - set(RSP_LOG_SHOW_TIMESTAMP true CACHE BOOL " RSP show timestamp for log entries") -endif () - -if (NOT DEFINED RSP_LOG_TIMESTAMP_FORMAT) - - # Log timestamp format - set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE BOOL " RSP log timestamp format") -endif () - -if (NOT DEFINED RSP_LOG_INDENT) - - # Indentation for log context, timestamp,...etc - set(RSP_LOG_INDENT " " CACHE STRING " RSP ident for log context, timestamp...etc") -endif () +# See RSP_LOG_LEVELS and RSP_LOG_LEVELS_CMAKE, in `rsp/logging/utils`. # -------------------------------------------------------------------------------------------------------------- # # Log functions @@ -242,175 +156,3 @@ if (NOT COMMAND "log") ) endfunction() endif () - -# -------------------------------------------------------------------------------------------------------------- # -# Log formatting utils -# -------------------------------------------------------------------------------------------------------------- # - -if (NOT COMMAND "format_log_level_label") - - #! format_log_level_label : Formats a label, acc. to specified log level - # - # @param level Log level - # @param output The output variable to assign result to - # - # @return - # output The formatted label - # - function(format_log_level_label level output) - set(style_affix "_style") - - set("${EMERGENCY_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") - set("${ALERT_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") - set("${CRITICAL_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") - set("${ERROR_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") - set("${WARNING_LEVEL}${style_affix}" "${COLOR_YELLOW}${TEXT_BOLD}") - set("${NOTICE_LEVEL}${style_affix}" "${COLOR_BRIGHT_BLUE}") - set("${INFO_LEVEL}${style_affix}" "${COLOR_BLUE}") - set("${DEBUG_LEVEL}${style_affix}" "${COLOR_WHITE}") - - # Abort if no style can be found for requested level - if (NOT DEFINED "${level}${style_affix}") - message(FATAL_ERROR "Unable to find style for log level: ${level}") - endif () - - # Apply evt. additional formatting of label... - set(label "${level}") - - set("${output}" "${${level}${style_affix}}${label}${RESTORE}") - - return(PROPAGATE "${output}") - endfunction() -endif () - -if (NOT COMMAND "format_log_message") - - #! format_log_message : Formats given log message, acc. to given log level - # - # @param level Log level - # @param message The log message - # @param output The output variable to assign result to - # - # @return - # output The formatted message - # - function(format_log_message level message output) - set(style_affix "_style") - - set("${EMERGENCY_LEVEL}${style_affix}" "${TEXT_BOLD}${TEXT_ITALIC}") - set("${ALERT_LEVEL}${style_affix}" "${TEXT_BOLD}") - set("${CRITICAL_LEVEL}${style_affix}" "${TEXT_BOLD}") - set("${ERROR_LEVEL}${style_affix}" "") - set("${WARNING_LEVEL}${style_affix}" "${TEXT_ITALIC}") - set("${NOTICE_LEVEL}${style_affix}" "") - set("${INFO_LEVEL}${style_affix}" "") - set("${DEBUG_LEVEL}${style_affix}" "${TEXT_ITALIC}") - - # Abort if no style can be found for requested level - if (NOT DEFINED "${level}${style_affix}") - message(FATAL_ERROR "Unable to style message for log level: ${level}") - endif () - - # Apply evt. additional formatting of message... - set(formatted "${message}") - - set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") - - return(PROPAGATE "${output}") - endfunction() -endif () - - -if (NOT COMMAND "format_log_context") - - #! format_log_context : Formats evt. context variables, acc. to log level - # - # @param level Log level - # @param context List of variables - # @param output The output variable to assign result to - # - # @return - # output The formatted log context - # - function(format_log_context level context output) - set(style_affix "_style") - - set("${EMERGENCY_LEVEL}${style_affix}" "") - set("${ALERT_LEVEL}${style_affix}" "") - set("${CRITICAL_LEVEL}${style_affix}" "") - set("${ERROR_LEVEL}${style_affix}" "") - set("${WARNING_LEVEL}${style_affix}" "") - set("${NOTICE_LEVEL}${style_affix}" "") - set("${INFO_LEVEL}${style_affix}" "") - set("${DEBUG_LEVEL}${style_affix}" "") - - # Abort if no style can be found for requested level - if (NOT DEFINED "${level}${style_affix}") - message(FATAL_ERROR "Unable to style context for log level: ${level}") - endif () - - # ---------------------------------------------------------------------------------------------- # - - set(indent "${RSP_LOG_INDENT}") - set(buffer "") - - # ---------------------------------------------------------------------------------------------- # - - string(APPEND buffer "\n${indent}[") - - foreach (prop IN LISTS context) - - set(entry "${COLOR_BRIGHT_MAGENTA}${TEXT_ITALIC}${prop}${RESTORE} = ${${prop}}") - - string(REPEAT "${indent}" 2 i) - string(APPEND buffer "\n${i}${entry}") - endforeach () - - string(APPEND buffer "\n${indent}]") - - set(formatted "${COLOR_MAGENTA}Context${RESTORE}: ${buffer}") - - # ---------------------------------------------------------------------------------------------- # - - set("${output}" "\n${indent}${${level}${style_affix}}${formatted}${RESTORE}") - - return(PROPAGATE "${output}") - endfunction() -endif () - -if (NOT COMMAND "format_log_timestamp") - - #! format_log_timestamp : Formats current timestamp, acc. to given log level - # - # @param level Log level - # @param output The output variable to assign result to - # - # @return - # output The formatted timestamp - # - function(format_log_timestamp level output) - set(style_affix "_style") - - set("${EMERGENCY_LEVEL}${style_affix}" "") - set("${ALERT_LEVEL}${style_affix}" "") - set("${CRITICAL_LEVEL}${style_affix}" "") - set("${ERROR_LEVEL}${style_affix}" "") - set("${WARNING_LEVEL}${style_affix}" "") - set("${NOTICE_LEVEL}${style_affix}" "") - set("${INFO_LEVEL}${style_affix}" "") - set("${DEBUG_LEVEL}${style_affix}" "") - - # Abort if no style can be found for requested level - if (NOT DEFINED "${level}${style_affix}") - message(FATAL_ERROR "Unable to style timestamp for log level: ${level}") - endif () - - # Timestamp - string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") - set(formatted "${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") - - set("${output}" "\n${RSP_LOG_INDENT}${${level}${style_affix}}${formatted}${RESTORE}") - - return(PROPAGATE "${output}") - endfunction() -endif () \ No newline at end of file diff --git a/cmake/rsp/logging/utils.cmake b/cmake/rsp/logging/utils.cmake new file mode 100644 index 0000000..8e8eb46 --- /dev/null +++ b/cmake/rsp/logging/utils.cmake @@ -0,0 +1,273 @@ +# -------------------------------------------------------------------------------------------------------------- # +# Logging utilities +# -------------------------------------------------------------------------------------------------------------- # + +include_guard(GLOBAL) + +# Debug +message(VERBOSE "rsp/logging/utils module included") + +include("rsp/output") + +# -------------------------------------------------------------------------------------------------------------- # +# Defaults +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT DEFINED RSP_LOG_LEVELS) + + # System is unusable. + set(EMERGENCY_LEVEL "emergency" CACHE STRING "RSP log level") + + # Action must be taken immediately. + set(ALERT_LEVEL "alert" CACHE STRING "RSP log level") + + # Critical conditions. + set(CRITICAL_LEVEL "critical" CACHE STRING "RSP log level") + + # Runtime errors that do not require immediate action but should typically + # be logged and monitored. + set(ERROR_LEVEL "error" CACHE STRING "RSP log level") + + # Exceptional occurrences that are not errors. + set(WARNING_LEVEL "warning" CACHE STRING "RSP log level") + + # Normal but significant events. + set(NOTICE_LEVEL "notice" CACHE STRING "RSP log level") + + # Interesting events. + set(INFO_LEVEL "info" CACHE STRING "RSP log level") + + # Detailed debug information. + set(DEBUG_LEVEL "debug" CACHE STRING "RSP log level") + + # PSR Log levels + # + # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel + # + set(RSP_LOG_LEVELS + "${EMERGENCY_LEVEL}" + "${ALERT_LEVEL}" + "${CRITICAL_LEVEL}" + "${ERROR_LEVEL}" + "${WARNING_LEVEL}" + "${NOTICE_LEVEL}" + "${INFO_LEVEL}" + "${DEBUG_LEVEL}" + + CACHE STRING "RSP log levels" + ) +endif () + +if (NOT DEFINED RSP_LOG_LEVELS_CMAKE) + + # A "map" of the PSR defined log levels and what each + # level corresponds to for cmake's modes / types. + # + # This "map" is used internally by log(), when no `mode` + # argument has been specified. + # + # @see https://www.php-fig.org/psr/psr-3/#5-psrlogloglevel + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # + set(RSP_LOG_LEVELS_CMAKE + "${EMERGENCY_LEVEL} FATAL_ERROR" + "${ALERT_LEVEL} FATAL_ERROR" + "${CRITICAL_LEVEL} FATAL_ERROR" + "${ERROR_LEVEL} SEND_ERROR" + "${WARNING_LEVEL} WARNING" + "${NOTICE_LEVEL} NOTICE" + "${INFO_LEVEL} NOTICE" + "${DEBUG_LEVEL} DEBUG" + + CACHE STRING " RSP log levels map for cmake's message mode" + ) +endif () + +if (NOT DEFINED RSP_LOG_SHOW_TIMESTAMP) + + # Show timestamp for log entries + set(RSP_LOG_SHOW_TIMESTAMP true CACHE BOOL " RSP show timestamp for log entries") +endif () + +if (NOT DEFINED RSP_LOG_TIMESTAMP_FORMAT) + + # Log timestamp format + set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE BOOL " RSP log timestamp format") +endif () + +if (NOT DEFINED RSP_LOG_INDENT) + + # Indentation for log context, timestamp,...etc + set(RSP_LOG_INDENT " " CACHE STRING " RSP ident for log context, timestamp...etc") +endif () + +# -------------------------------------------------------------------------------------------------------------- # +# Functions & Macros +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT COMMAND "format_log_level_label") + + #! format_log_level_label : Formats a label, acc. to specified log level + # + # @param level Log level + # @param output The output variable to assign result to + # + # @return + # output The formatted label + # + function(format_log_level_label level output) + set(style_affix "_style") + + set("${EMERGENCY_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") + set("${ALERT_LEVEL}${style_affix}" "${COLOR_BRIGHT_RED}${TEXT_BOLD}") + set("${CRITICAL_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") + set("${ERROR_LEVEL}${style_affix}" "${COLOR_RED}${TEXT_BOLD}") + set("${WARNING_LEVEL}${style_affix}" "${COLOR_YELLOW}${TEXT_BOLD}") + set("${NOTICE_LEVEL}${style_affix}" "${COLOR_BRIGHT_BLUE}") + set("${INFO_LEVEL}${style_affix}" "${COLOR_BLUE}") + set("${DEBUG_LEVEL}${style_affix}" "${COLOR_WHITE}") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to find style for log level: ${level}") + endif () + + # Apply evt. additional formatting of label... + set(label "${level}") + + set("${output}" "${${level}${style_affix}}${label}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + +if (NOT COMMAND "format_log_message") + + #! format_log_message : Formats given log message, acc. to given log level + # + # @param level Log level + # @param message The log message + # @param output The output variable to assign result to + # + # @return + # output The formatted message + # + function(format_log_message level message output) + set(style_affix "_style") + + set("${EMERGENCY_LEVEL}${style_affix}" "${TEXT_BOLD}${TEXT_ITALIC}") + set("${ALERT_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${CRITICAL_LEVEL}${style_affix}" "${TEXT_BOLD}") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "${TEXT_ITALIC}") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "${TEXT_ITALIC}") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style message for log level: ${level}") + endif () + + # Apply evt. additional formatting of message... + set(formatted "${message}") + + set("${output}" "${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + +if (NOT COMMAND "format_log_context") + + #! format_log_context : Formats evt. context variables, acc. to log level + # + # @param level Log level + # @param context List of variables + # @param output The output variable to assign result to + # + # @return + # output The formatted log context + # + function(format_log_context level context output) + set(style_affix "_style") + + set("${EMERGENCY_LEVEL}${style_affix}" "") + set("${ALERT_LEVEL}${style_affix}" "") + set("${CRITICAL_LEVEL}${style_affix}" "") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style context for log level: ${level}") + endif () + + # ---------------------------------------------------------------------------------------------- # + + set(indent "${RSP_LOG_INDENT}") + set(buffer "") + + # ---------------------------------------------------------------------------------------------- # + + string(APPEND buffer "\n${indent}[") + + foreach (prop IN LISTS context) + + set(entry "${COLOR_BRIGHT_MAGENTA}${TEXT_ITALIC}${prop}${RESTORE} = ${${prop}}") + + string(REPEAT "${indent}" 2 i) + string(APPEND buffer "\n${i}${entry}") + endforeach () + + string(APPEND buffer "\n${indent}]") + + set(formatted "${COLOR_MAGENTA}Context${RESTORE}: ${buffer}") + + # ---------------------------------------------------------------------------------------------- # + + set("${output}" "\n${indent}${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () + +if (NOT COMMAND "format_log_timestamp") + + #! format_log_timestamp : Formats current timestamp, acc. to given log level + # + # @param level Log level + # @param output The output variable to assign result to + # + # @return + # output The formatted timestamp + # + function(format_log_timestamp level output) + set(style_affix "_style") + + set("${EMERGENCY_LEVEL}${style_affix}" "") + set("${ALERT_LEVEL}${style_affix}" "") + set("${CRITICAL_LEVEL}${style_affix}" "") + set("${ERROR_LEVEL}${style_affix}" "") + set("${WARNING_LEVEL}${style_affix}" "") + set("${NOTICE_LEVEL}${style_affix}" "") + set("${INFO_LEVEL}${style_affix}" "") + set("${DEBUG_LEVEL}${style_affix}" "") + + # Abort if no style can be found for requested level + if (NOT DEFINED "${level}${style_affix}") + message(FATAL_ERROR "Unable to style timestamp for log level: ${level}") + endif () + + # Timestamp + string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") + set(formatted "${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") + + set("${output}" "\n${RSP_LOG_INDENT}${${level}${style_affix}}${formatted}${RESTORE}") + + return(PROPAGATE "${output}") + endfunction() +endif () From 57ee993d2965633260a0d57c7023633053d84a33 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:23:43 +0100 Subject: [PATCH 23/57] Refactor, extract resolve list separator into macro --- cmake/rsp/output.cmake | 5 +---- cmake/rsp/output/utils.cmake | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index f5fc145..7d82459 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -99,10 +99,7 @@ if (NOT COMMAND "output") endif () # List separator - set(separator "${RSP_DEFAULT_LIST_SEPARATOR}") - if (DEFINED INPUT_LIST_SEPARATOR) - set(separator "${INPUT_LIST_SEPARATOR}") - endif () + resolve_list_separator() # ---------------------------------------------------------------------------------------------- # # Resolve message diff --git a/cmake/rsp/output/utils.cmake b/cmake/rsp/output/utils.cmake index 546a9e3..e8b9379 100644 --- a/cmake/rsp/output/utils.cmake +++ b/cmake/rsp/output/utils.cmake @@ -61,4 +61,26 @@ if (NOT COMMAND "resolve_msg_mode") endif () endforeach () endmacro() +endif () + +if (NOT COMMAND "resolve_list_separator") + + #! resolve_list_separator : Resolves requested list separator + # + # WARNING: Macro is intended to be used internally within misc. output + # functions. Sets a `variable` to requested separator, or defaults to + # RSP_DEFAULT_LIST_SEPARATOR, when none requested. + # + # @see RSP_DEFAULT_LIST_SEPARATOR + # + # @internal + # + macro(resolve_list_separator) + # List separator + set(separator "${RSP_DEFAULT_LIST_SEPARATOR}") + + if (DEFINED INPUT_LIST_SEPARATOR) + set(separator "${INPUT_LIST_SEPARATOR}") + endif () + endmacro() endif () \ No newline at end of file From 83a6c71dbd06d2ade8f82a7e9c6db93c0c01502e Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:24:23 +0100 Subject: [PATCH 24/57] Refactor log(), extract resolve cmake msg mode logic into macro --- cmake/rsp/logging.cmake | 19 +-------------- cmake/rsp/logging/utils.cmake | 45 +++++++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 651a642..e054498 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -68,24 +68,7 @@ if (NOT COMMAND "log") # ---------------------------------------------------------------------------------------------- # # Resolve CMake's message mode, acc. to specified log level. - set(default_cmake_msg_mode "NOTICE") - foreach (lvl IN LISTS RSP_LOG_LEVELS_CMAKE) - string(REPLACE " " ";" parts "${lvl}") - - # name = PSR log level, value = CMake message mode - list(GET parts 0 name) - list(GET parts 1 value) - - if ("${name}" STREQUAL "${log_level}") - message(VERBOSE "Mapping ${log_level} level to cmake message mode ${value}") - - set(default_cmake_msg_mode "${value}") - break() - endif () - endforeach () - - # - Resolve the actual `msg_mode`. If none is given, then `default_cmake_msg_mode` is used! - resolve_msg_mode("${default_cmake_msg_mode}") + resolve_cmake_message_mode() # ---------------------------------------------------------------------------------------------- # diff --git a/cmake/rsp/logging/utils.cmake b/cmake/rsp/logging/utils.cmake index 8e8eb46..e73d235 100644 --- a/cmake/rsp/logging/utils.cmake +++ b/cmake/rsp/logging/utils.cmake @@ -105,6 +105,43 @@ endif () # Functions & Macros # -------------------------------------------------------------------------------------------------------------- # +if (NOT COMMAND "resolve_cmake_message_mode") + + #! resolve_cmake_message_mode : Resolves the CMake message mode acc. to requested + # PSR log level. + # + # WARNING: Macro is intended to be used internally within misc. log + # functions. + # + # @see https://cmake.org/cmake/help/latest/command/message.html#general-messages + # @see rsp/output/utils::resolve_msg_mode + # + # @internal + # + macro(resolve_cmake_message_mode) + # Resolve CMake's message mode, acc. to specified log level. + set(default_cmake_msg_mode "NOTICE") + + foreach (lvl IN LISTS RSP_LOG_LEVELS_CMAKE) + string(REPLACE " " ";" parts "${lvl}") + + # name = PSR log level, value = CMake message mode + list(GET parts 0 name) + list(GET parts 1 value) + + if ("${name}" STREQUAL "${log_level}") + message(VERBOSE "Mapping ${log_level} level to cmake message mode ${value}") + + set(default_cmake_msg_mode "${value}") + break() + endif () + endforeach () + + # - Resolve the actual `msg_mode`. If none is given, then `default_cmake_msg_mode` is used! + resolve_msg_mode("${default_cmake_msg_mode}") + endmacro() +endif () + if (NOT COMMAND "format_log_level_label") #! format_log_level_label : Formats a label, acc. to specified log level @@ -113,7 +150,7 @@ if (NOT COMMAND "format_log_level_label") # @param output The output variable to assign result to # # @return - # output The formatted label + # output The formatted label # function(format_log_level_label level output) set(style_affix "_style") @@ -150,7 +187,7 @@ if (NOT COMMAND "format_log_message") # @param output The output variable to assign result to # # @return - # output The formatted message + # output The formatted message # function(format_log_message level message output) set(style_affix "_style") @@ -187,7 +224,7 @@ if (NOT COMMAND "format_log_context") # @param output The output variable to assign result to # # @return - # output The formatted log context + # output The formatted log context # function(format_log_context level context output) set(style_affix "_style") @@ -243,7 +280,7 @@ if (NOT COMMAND "format_log_timestamp") # @param output The output variable to assign result to # # @return - # output The formatted timestamp + # output The formatted timestamp # function(format_log_timestamp level output) set(style_affix "_style") From 0deb60b135c8f38e030361eab83b7880edf806da Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:25:00 +0100 Subject: [PATCH 25/57] Refactor, use resolve_list_separator() --- cmake/rsp/logging.cmake | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index e054498..c0e322f 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -76,10 +76,7 @@ if (NOT COMMAND "log") format_log_level_label("${log_level}" label) # List separator - set(separator "${RSP_DEFAULT_LIST_SEPARATOR}") - if (DEFINED INPUT_LIST_SEPARATOR) - set(separator "${INPUT_LIST_SEPARATOR}") - endif () + resolve_list_separator() # ---------------------------------------------------------------------------------------------- # From ae2c5beb20c4f6377ef280bb8d59fa55001c7f0e Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:26:27 +0100 Subject: [PATCH 26/57] Fix formatting --- cmake/rsp/logging.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index c0e322f..df2f91e 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -22,7 +22,7 @@ include("rsp/logging/utils") # Defaults... # -------------------------------------------------------------------------------------------------------------- # -# See RSP_LOG_LEVELS and RSP_LOG_LEVELS_CMAKE, in `rsp/logging/utils`. +# See `RSP_LOG_LEVELS` and `RSP_LOG_LEVELS_CMAKE`, in `rsp/logging/utils`. # -------------------------------------------------------------------------------------------------------------- # # Log functions From 8da06c2227b59cb88e763742a969e4c0677e8fcc Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 13:30:51 +0100 Subject: [PATCH 27/57] Add RSP_LOG_TIMESTAMP_UTC state This allows developers to use UTC timestamps, instead of local --- cmake/rsp/logging/utils.cmake | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cmake/rsp/logging/utils.cmake b/cmake/rsp/logging/utils.cmake index e73d235..d666228 100644 --- a/cmake/rsp/logging/utils.cmake +++ b/cmake/rsp/logging/utils.cmake @@ -92,7 +92,13 @@ endif () if (NOT DEFINED RSP_LOG_TIMESTAMP_FORMAT) # Log timestamp format - set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE BOOL " RSP log timestamp format") + set(RSP_LOG_TIMESTAMP_FORMAT "%Y-%m-%d %H:%M:%S.%f" CACHE STRING " RSP log timestamp format") +endif () + +if (NOT DEFINED RSP_LOG_TIMESTAMP_UTC) + + # Use UTC or local time... + set(RSP_LOG_TIMESTAMP_UTC false CACHE BOOL " RSP log timestamp UTC state (true if UTC, false if local)") endif () if (NOT DEFINED RSP_LOG_INDENT) @@ -299,8 +305,14 @@ if (NOT COMMAND "format_log_timestamp") message(FATAL_ERROR "Unable to style timestamp for log level: ${level}") endif () - # Timestamp - string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") + # Obtain current timestamp + if (RSP_LOG_TIMESTAMP_UTC) + string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}" UTC) + else() + string(TIMESTAMP now "${RSP_LOG_TIMESTAMP_FORMAT}") + endif () + + # Format output... set(formatted "${COLOR_MAGENTA}${TEXT_ITALIC}Timestamp${RESTORE}: ${now}") set("${output}" "\n${RSP_LOG_INDENT}${${level}${style_affix}}${formatted}${RESTORE}") From a98b654a03588e87fcfe162aca13fe31a230db07 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 14:58:05 +0100 Subject: [PATCH 28/57] Change list separator handling This should improve the formatting, slightly... But still isn't what it should be. Damn message() behaves really strange - cmake's "simple markup language" is NOT very well documented. --- cmake/rsp/output.cmake | 17 +---------------- cmake/rsp/output/utils.cmake | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/cmake/rsp/output.cmake b/cmake/rsp/output.cmake index 7d82459..df52e89 100644 --- a/cmake/rsp/output.cmake +++ b/cmake/rsp/output.cmake @@ -27,17 +27,6 @@ if (NOT DEFINED RSP_DEFAULT_LABEL_FORMAT) set(RSP_DEFAULT_LABEL_FORMAT "%label%: " CACHE STRING " RSP Default label format for output()") endif () -if (NOT DEFINED RSP_DEFAULT_LIST_SEPARATOR) - - # Default list separator - # - # Used when a list is requested printed - # - # @see output() - # - set(RSP_DEFAULT_LIST_SEPARATOR "\\n" CACHE STRING " RSP Default list separator for output()") -endif () - # -------------------------------------------------------------------------------------------------------------- # # Output related functions & macros # -------------------------------------------------------------------------------------------------------------- # @@ -117,11 +106,6 @@ if (NOT COMMAND "output") if (length GREATER 1) # Replace every semicolon with a newline character. string(REPLACE ";" "${separator}" resolved_msg "${resolved_msg}") - - # Debug - # foreach (item IN LISTS resolved_msg) - # message(NOTICE "Item: ${item}") - # endforeach () endif () # Use the resolved message to buffer. @@ -153,6 +137,7 @@ if (NOT COMMAND "output") string(ASCII 13 CR) set(formatted_output "${CR}${formatted_output}") string(REPLACE "\n\t" "\n " formatted_output "${formatted_output}") + string(REPLACE "\n" "\n " formatted_output "${formatted_output}") endif () # Finally, output the message... diff --git a/cmake/rsp/output/utils.cmake b/cmake/rsp/output/utils.cmake index e8b9379..939de2e 100644 --- a/cmake/rsp/output/utils.cmake +++ b/cmake/rsp/output/utils.cmake @@ -7,6 +7,21 @@ include_guard(GLOBAL) # Debug message(VERBOSE "rsp/output/utils module included") +# -------------------------------------------------------------------------------------------------------------- # +# Defaults... +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT DEFINED RSP_DEFAULT_LIST_SEPARATOR) + + # Default list separator + # + # Used when a list is requested printed + # + # @see output() + # + set(RSP_DEFAULT_LIST_SEPARATOR "\n") +endif () + # -------------------------------------------------------------------------------------------------------------- # # Message Modes # -------------------------------------------------------------------------------------------------------------- # From 1725b3c08c7ae93d92bcb7515d5a0e6dca44f4f4 Mon Sep 17 00:00:00 2001 From: alin Date: Mon, 3 Feb 2025 15:59:49 +0100 Subject: [PATCH 29/57] Add remaining logging functions from PSR Need to make a small macro to help forward calls to log(), or the overhead of boilerplate script would be too much... --- cmake/rsp/logging.cmake | 279 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 268 insertions(+), 11 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index df2f91e..864bb9e 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -28,6 +28,217 @@ include("rsp/logging/utils") # Log functions # -------------------------------------------------------------------------------------------------------------- # +if (NOT COMMAND "emergency") + + #! emergency : Log an "emergency" level message - System is unusable. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(emergency message) + forward_to_log(EMERGENCY_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "alert") + + #! alert : Log an "alert" level message - Action must be taken immediately. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(alert message) + forward_to_log(ALERT_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "critical") + + #! critical : Log an "critical" level message - Critical conditions. + # E.g. Application component unavailable, unexpected exception. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(critical message) + forward_to_log(CRITICAL_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "error") + + #! error : Log an "error" level message. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(error message) + forward_to_log(ERROR_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "warning") + + #! warning : Log an "warning" level message - Exceptional occurrences that are not errors. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(warning message) + forward_to_log(WARNING_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "notice") + + #! notice : Log an "notice" level message - Normal but significant events. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(notice message) + forward_to_log(NOTICE_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "info") + + #! info : Log an "info" level message - Normal but significant events. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # NOTICE, when not specified. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(info message) + forward_to_log(INFO_LEVEL ${message}) + endfunction() +endif () + +if (NOT COMMAND "debug") + + #! debug : Log an "debug" level message - Normal but significant events. + # + # @see log() + # + # @param message The message to output. If a variable is given, its value will be used. + # If a list is detected given, then each item in the list will be output, + # using the LIST_SEPARATOR. + # @param [] Option - Cmake's message type. Defaults to the `mode` that is + # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. + # NOTICE, when not specified. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. + # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. + # Defaults to RSP_DEFAULT_LIST_SEPARATOR. + # + # @return + # [OUTPUT] The resulting output variable, if OUTPUT was specified. + # + function(debug message) + forward_to_log(DEBUG_LEVEL ${message}) + endfunction() +endif () + if (NOT COMMAND "log") #! log : Log a message with an arbitrary level @@ -40,9 +251,10 @@ if (NOT COMMAND "log") # using the LIST_SEPARATOR. # @param [] Option - Cmake's message type. Defaults to the `mode` that is # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. - # NOTICE, when not specified. - # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead of - # being printed to stdout or stderr. + # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the + # log message. + # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead + # of being printed to stdout or stderr. # @param [LIST_SEPARATOR ] Optional - Separator to used, if a list variable is given as message. # Defaults to RSP_DEFAULT_LIST_SEPARATOR. # @@ -71,7 +283,7 @@ if (NOT COMMAND "log") resolve_cmake_message_mode() # ---------------------------------------------------------------------------------------------- # - + # Format message label, acc. to the log level format_log_level_label("${log_level}" label) @@ -102,7 +314,7 @@ if (NOT COMMAND "log") # ---------------------------------------------------------------------------------------------- # # Context - if (DEFINED INPUT_CONTEXT) + if (DEFINED INPUT_CONTEXT AND NOT INPUT_CONTEXT STREQUAL "") format_log_context("${log_level}" "${INPUT_CONTEXT}" formatted_context) string(APPEND buffer "${formatted_context}") endif () @@ -121,18 +333,63 @@ if (NOT COMMAND "log") # ---------------------------------------------------------------------------------------------- # + # Finally, output the log message + output(${formatted_output} + "${msg_mode}" + OUTPUT ${INPUT_OUTPUT} + LABEL "${label}" + ) + + # ---------------------------------------------------------------------------------------------- # + # Assign to output variable, if requested and stop any further processing. if (DEFINED INPUT_OUTPUT) - set("${INPUT_OUTPUT}" "${label}${formatted_output}") return(PROPAGATE "${INPUT_OUTPUT}") endif () + endfunction() +endif () + +# -------------------------------------------------------------------------------------------------------------- # +# Internals +# -------------------------------------------------------------------------------------------------------------- # + +if (NOT COMMAND forward_to_log) + + #! forward_to_log : Macro that forwards current function call to log() + # + # WARNING: Macro is intended to be used internally within various logging + # level functions, w.g. critical(), error(), warning()...etc + # It is responsible for forwarding arguments to the log() function and + # return evt. assigned OUTPUT. + # + # @see log() + # + # @internal + # + macro(forward_to_log log_level message) + set(options "${RSP_CMAKE_MESSAGE_MODES}") + set(oneValueArgs OUTPUT LIST_SEPARATOR) + set(multiValueArgs CONTEXT) + + cmake_parse_arguments(PARSE_ARGV 1 INPUT "${options}" "${oneValueArgs}" "${multiValueArgs}") # ---------------------------------------------------------------------------------------------- # - # Finally, output the log message - output(${formatted_output} - "${msg_mode}" - LABEL "${label}" + extract_value(log_level ${log_level}) + resolve_cmake_message_mode() + + # ---------------------------------------------------------------------------------------------- # + + log("${log_level}" ${message} "${msg_mode}" + OUTPUT ${INPUT_OUTPUT} + CONTEXT ${INPUT_CONTEXT} + LIST_SEPARATOR ${INPUT_LIST_SEPARATOR} ) - endfunction() + + # ---------------------------------------------------------------------------------------------- # + + if (DEFINED INPUT_OUTPUT) + return(PROPAGATE "${INPUT_OUTPUT}") + endif () + endmacro() endif () From 5f0f5cadc01176781fec5281a979d937d3288332 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 10:01:11 +0100 Subject: [PATCH 30/57] Add string contains / not contains asserts --- cmake/rsp/testing/asserts.cmake | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/cmake/rsp/testing/asserts.cmake b/cmake/rsp/testing/asserts.cmake index 3a6485e..86e2f26 100644 --- a/cmake/rsp/testing/asserts.cmake +++ b/cmake/rsp/testing/asserts.cmake @@ -573,6 +573,62 @@ if (NOT COMMAND "assert_string_not_empty") endfunction() endif () +if (NOT COMMAND "assert_string_contains") + + #! assert_string_contains : Assert given string contains given substring + # + # @see https://cmake.org/cmake/help/latest/command/string.html#length + # + # @param str The target string value + # @param sub_str The substring + # @param [MESSAGE ] Optional message to output if assertion fails + # + # @throws + # + function(assert_string_contains str sub_str) + set(oneValueArgs MESSAGE) + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "" ${ARGN}) + format_assert_message(msg "${INPUT_MESSAGE}") + + # ------------------------------------------------------------------------------------- # + + string(FIND "${str}" "${sub_str}" result) + + if (result EQUAL -1) + message(FATAL_ERROR "String '${str}' does not contain '${sub_str}'." "${msg}") + endif () + + endfunction() +endif () + +if (NOT COMMAND "assert_string_not_contains") + + #! assert_string_not_contains : Assert given string does not contain given substring + # + # @see https://cmake.org/cmake/help/latest/command/string.html#length + # + # @param str The target string value + # @param sub_str The substring + # @param [MESSAGE ] Optional message to output if assertion fails + # + # @throws + # + function(assert_string_not_contains str sub_str) + set(oneValueArgs MESSAGE) + cmake_parse_arguments(INPUT "" "${oneValueArgs}" "" ${ARGN}) + format_assert_message(msg "${INPUT_MESSAGE}") + + # ------------------------------------------------------------------------------------- # + + string(FIND "${str}" "${sub_str}" result) + + if (NOT result EQUAL -1) + message(FATAL_ERROR "String '${str}' contains '${sub_str}', but was not expected to." "${msg}") + endif () + + endfunction() +endif () + # TODO: ... # TODO: ...gt, gte, lt, lte... etc # TODO: ...regex From 5131ce9746c9d89a8cc2a016f6b6a2c126779310 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 10:01:25 +0100 Subject: [PATCH 31/57] Add tests for string contains / not contains asserts --- tests/unit/testing/asserts_test.cmake | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/unit/testing/asserts_test.cmake b/tests/unit/testing/asserts_test.cmake index 8686ab4..1f03289 100644 --- a/tests/unit/testing/asserts_test.cmake +++ b/tests/unit/testing/asserts_test.cmake @@ -265,6 +265,22 @@ function(asserts_str_not_empty) assert_string_not_empty("abc" MESSAGE "values") endfunction() +define_test("can assert string contains" "asserts_str_contains") +function(asserts_str_contains) + set(str "None of these machines experience harmless, crazy teleporters.") + set(sub_str "harmless") + + assert_string_contains(${str} ${sub_str} MESSAGE "assert failed to find sub-string") +endfunction() + +define_test("can assert string not contains" "asserts_str_not_contains") +function(asserts_str_not_contains) + set(str "None of these machines experience harmless, crazy teleporters.") + set(sub_str "does not exist...") + + assert_string_not_contains(${str} ${sub_str} MESSAGE "assertion failure...") +endfunction() + # TODO: ...gt, gte, lt, lte... etc # TODO: ...regex From ff7c38f0121d3d45ce3dfb494bcda2d98c8b9da1 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 10:01:51 +0100 Subject: [PATCH 32/57] Add examples / ref. for string contains / not contains asserts --- docs/+current/modules/testing/cmake/-asserts.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/+current/modules/testing/cmake/-asserts.md b/docs/+current/modules/testing/cmake/-asserts.md index 4b57d3f..d0eab61 100644 --- a/docs/+current/modules/testing/cmake/-asserts.md +++ b/docs/+current/modules/testing/cmake/-asserts.md @@ -155,6 +155,22 @@ Opposite of `assert_string_empty()`. assert_string_not_empty("${my_string}") ``` +### `assert_string_contains()` + +Assert given string contains substring. + +```cmake +assert_string_contains("Name: John Doe" "John") +``` + +### `assert_string_not_contains()` + +Assert given string does not contain substring. + +```cmake +assert_string_not_contains("Name: John Doe" "Jimmy") +``` + ## Lists ### `assert_in_list()` From a7b8d9a5be6b4cdfd314aa96e0d4277f13fed1af Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 10:28:00 +0100 Subject: [PATCH 33/57] Fix strange name conflict For reasons that are beyond me, the variable "name" was suddenly shown in a log's context, when developer passed a "name" variable! I think this is because of the macro's variable scope handling. --- cmake/rsp/logging/utils.cmake | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cmake/rsp/logging/utils.cmake b/cmake/rsp/logging/utils.cmake index d666228..3ecb15e 100644 --- a/cmake/rsp/logging/utils.cmake +++ b/cmake/rsp/logging/utils.cmake @@ -131,14 +131,17 @@ if (NOT COMMAND "resolve_cmake_message_mode") foreach (lvl IN LISTS RSP_LOG_LEVELS_CMAKE) string(REPLACE " " ";" parts "${lvl}") - # name = PSR log level, value = CMake message mode - list(GET parts 0 name) - list(GET parts 1 value) + # psr = PSR log level, value = CMake message mode + list(GET parts 0 psr) + list(GET parts 1 msg_mode_cmake) - if ("${name}" STREQUAL "${log_level}") - message(VERBOSE "Mapping ${log_level} level to cmake message mode ${value}") + if ("${psr}" STREQUAL "${log_level}") + message(VERBOSE "Mapping ${log_level} level to cmake message mode ${msg_mode_cmake}") + + set(default_cmake_msg_mode "${msg_mode_cmake}") + unset(psr) + unset(msg_mode_cmake) - set(default_cmake_msg_mode "${value}") break() endif () endforeach () From 1f02da537031904fdceb2f6ef2a7516243da9fc4 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 10:42:02 +0100 Subject: [PATCH 34/57] Add tests for log functions --- tests/unit/logging/log_test.cmake | 121 ++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tests/unit/logging/log_test.cmake diff --git a/tests/unit/logging/log_test.cmake b/tests/unit/logging/log_test.cmake new file mode 100644 index 0000000..4f6c239 --- /dev/null +++ b/tests/unit/logging/log_test.cmake @@ -0,0 +1,121 @@ +include("rsp/testing") +include("rsp/logging") + +define_test_case( + "Log Test" + LABELS "logging;log" +) + +# -------------------------------------------------------------------------------------------------------------- # +# Data Providers +# -------------------------------------------------------------------------------------------------------------- # + +function(provides_log_functions output) + set("${output}" + "${EMERGENCY_LEVEL}" + "${ALERT_LEVEL}" + "${CRITICAL_LEVEL}" + "${ERROR_LEVEL}" + "${WARNING_LEVEL}" + "${NOTICE_LEVEL}" + "${INFO_LEVEL}" + "${DEBUG_LEVEL}" + ) + return (PROPAGATE "${output}") +endfunction() + +# -------------------------------------------------------------------------------------------------------------- # +# Actual tests +# -------------------------------------------------------------------------------------------------------------- # + +define_test("can log a simple message" "can_log_simple_msg" DATA_PROVIDER "provides_log_functions") +function(can_log_simple_msg log_function) + + set(msg "Where is the swashbuckling mainland?") + cmake_language(CALL "${log_function}" "${msg}" OUTPUT result) + + assert_string_contains("${result}" "${msg}" MESSAGE "Incorrect message logged for ${log_function}") +endfunction() + +define_test("can log a variable" "can_log_variable_msg" DATA_PROVIDER "provides_log_functions") +function(can_log_variable_msg log_function) + + set(msg "You have to travel, and praise issue by your disappearing.") + cmake_language(CALL "${log_function}" msg OUTPUT result) + + assert_string_contains("${result}" "${msg}" MESSAGE "Incorrect message logged for ${log_function}") +endfunction() + +define_test("can log a list" "can_log_list_msg" DATA_PROVIDER "provides_log_functions") +function(can_log_list_msg log_function) + + set(my_list "aaa;bbb;ccc;ddd") + cmake_language(CALL "${log_function}" my_list OUTPUT result) + + foreach (item IN LISTS my_list) + assert_string_contains("${result}" "${item}" MESSAGE "Incorrect message logged for ${log_function}") + endforeach () +endfunction() + +define_test("can log a list using custom separator" "can_log_list_separator_msg" DATA_PROVIDER "provides_log_functions") +function(can_log_list_separator_msg log_function) + + set(my_list "aaa;bbb;ccc;ddd") + set(list_separator ", ") + cmake_language(CALL "${log_function}" my_list OUTPUT result LIST_SEPARATOR "${list_separator}") + + string(REPLACE ";" "${list_separator}" expected "${my_list}") + assert_string_contains("${result}" "${expected}" MESSAGE "Incorrect message logged for ${log_function}") +endfunction() + +define_test("can log a message with context" "can_log_msg_context" DATA_PROVIDER "provides_log_functions") +function(can_log_msg_context log_function) + + set(msg "Where is the swashbuckling mainland?") + set(foo "bar") + set(name "John Doe") + set(psr "alpha") + set(age 34) + + cmake_language(CALL "${log_function}" "${msg}" CONTEXT foo name psr age OUTPUT result) + + assert_string_contains("${result}" "${msg}" MESSAGE "Incorrect message logged for ${log_function}") + assert_string_contains("${result}" "${foo}" MESSAGE "Context item (foo) not logged for ${log_function}") + assert_string_contains("${result}" "${name}" MESSAGE "Context item (name) not logged for ${log_function}") + assert_string_contains("${result}" "${age}" MESSAGE "Context item (age) not logged for ${log_function}") +endfunction() + +define_test("log message contains a timestamp" "log_msg_has_timestamp" DATA_PROVIDER "provides_log_functions") +function(log_msg_has_timestamp log_function) + + cmake_language(CALL "${log_function}" "foo" OUTPUT result) + + string(TOLOWER "${result}" result) + assert_string_contains("${result}" "timestamp" MESSAGE "No timestamp logged for ${log_function}") +endfunction() + +define_test("can set cmake message mode" "can_log_with_cmake_mgs_mode" DATA_PROVIDER "provides_log_functions") +function(can_log_with_cmake_mgs_mode log_function) + + # Use "status" mode here, such that all log functions can be invoked + # without side-effects. + set(mode "STATUS") + + # If no failure, then test passes + cmake_language(CALL "${log_function}" "Test of CMake message mode: ${mode}" ${mode}) +endfunction() + +define_test( + "can use cmake message mode to stop build" + "can_stop_build_with_mgs_mode" + DATA_PROVIDER "provides_log_functions" + EXPECT_FAILURE +) +function(can_stop_build_with_mgs_mode log_function) + + # Here, each call to a log function should result in cmake stopping + # the build process entirely, when mode is set to "fatal error"! + set(mode "FATAL_ERROR") + + cmake_language(CALL "${log_function}" "Test of CMake message mode: ${mode}" ${mode}) +endfunction() \ No newline at end of file From ac94155fc9b4f5a30d68d0252ba18cab9b3d76f4 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 13:34:12 +0100 Subject: [PATCH 35/57] Add docs index page for output module --- docs/+current/modules/output/index.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docs/+current/modules/output/index.md diff --git a/docs/+current/modules/output/index.md b/docs/+current/modules/output/index.md new file mode 100644 index 0000000..d2fa56d --- /dev/null +++ b/docs/+current/modules/output/index.md @@ -0,0 +1,16 @@ +--- +title: Output +description: How to use the output module. +keywords: output, cmake +author: RSP Systems A/S +--- + +# Output + +This module offers a few console output utilities. + +## How to include + +```cmake +include("rsp/output") +``` \ No newline at end of file From f757e0c8bf18d72d5466e656a8ad60572b6f7fea Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 13:34:21 +0100 Subject: [PATCH 36/57] Add doc for output ANSI --- docs/+current/modules/output/ansi.md | 177 +++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 docs/+current/modules/output/ansi.md diff --git a/docs/+current/modules/output/ansi.md b/docs/+current/modules/output/ansi.md new file mode 100644 index 0000000..ef9e2c5 --- /dev/null +++ b/docs/+current/modules/output/ansi.md @@ -0,0 +1,177 @@ +--- +title: ANSI +description: How to use the ansi module. +keywords: output, ansi, cmake +author: RSP Systems A/S +--- + +# ANSI + +The `rsp/output` modules comes with support for [ANSI](https://en.wikipedia.org/wiki/ANSI_escape_code), which will +allow you to format and style your console output. + +[TOC] + +## How to enable + +To enable ANSI output, call the `enable_ansi()`. It is **recommended** that you only do so, if your project is the +top-level cmake project. + +```cmake +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include("rsp/output") + + enable_ansi() +endif() +``` + +!!! warning "Warning" + The `enable_ansi()` caches preset of ANSI escape sequences. This means that even if you remove + the function call, ANSI will remain enabled, unless you explicitly [disable it](#how-to-disable) again. + +## How to disable + +```cmake +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include("rsp/output") + + disable_ansi() +endif() +``` + +## Example + +Once you have enabled ANSI for your project, then you can use a series of predefined properties (_ANSI escape sequences_) +are available throughout your cmake scripts. These can be used to format your console output messages. + +```cmake +message("${COLOR_GREEN}${TEXT_ITALIC}Build success${RESTORE}, for ${PROJECT_NAME}") +``` + +The above shown example should output a message, where the first part is in green coloured, and italic styled text. +The second part should be in normal styled text. + +> Build success, for < your-cmake-project-name > + +## Restore + +Use the `RESTORE` (_[`ESC[0m`](https://en.wikipedia.org/wiki/ANSI_escape_code#Control_Sequence_Introducer_commands)_) +property if you wish to reset any previous set colour or style. + +```cmake +message("${TEXT_BOLD}Component:${RESTORE} ready") +``` + +> **Component:** ready + +## Text Styles + +The following shows the default preset [text styles](https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters): + +| Property | Name | Control Sequence Code | Example | +|------------------------------|---------------------------|----------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `TEXT_BOLD` | bold | 1 | **My style** | +| `TEXT_BOLD_RESTORE` | bold (_restore_) | 22 | | +| `TEXT_DIM` | dim | 2 | My style | +| `TEXT_DIM_RESTORE` | dim (_restore_) | 22 | | +| `TEXT_ITALIC` | italic | 3 | _My style_ | +| `TEXT_ITALIC_RESTORE` | italic (_restore_) | 23 | | +| `TEXT_UNDERLINE` | underline | 4 | My style | +| `TEXT_UNDERLINE_RESTORE` | underline (_restore_) | 24 | | +| `TEXT_BLINK` | blink | 5 | My style | +| `TEXT_INVERSE` | inverse | 7 | My style | +| `TEXT_INVERSE_RESTORE` | inverse (_restore_) | 27 | | +| `TEXT_HIDDEN` | hidden | 8 | | +| `TEXT_HIDDEN_RESTORE` | inverse (_restore_) | 28 | | +| `TEXT_STRIKETHROUGH` | strikethrough | 9 | My style | +| `TEXT_STRIKETHROUGH_RESTORE` | strikethrough (_restore_) | 29 | | + +!!! note "Note" + _Depending on your terminal, not all text styles might be supported!_ + +## Colours + +The following default foreground and background [colours](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors) are +supported: + +| Property | Name | Control Sequence Code | Example | +|---------------------------|-------------------------------|----------------------:|--------------------------------------------------------------------------------| +| `COLOR_BLACK` | black | 30 |
My Style
| +| `COLOR_BG_BLACK` | black (_background_) | 40 |
My Style
| +| `COLOR_BRIGHT_BLACK` | black bright | 90 |
My Style
| +| `COLOR_BRIGHT_BG_BLACK` | black bright (_background_) | 100 |
My Style
| +| `COLOR_RED` | red | 31 |
My Style
| +| `COLOR_BG_RED` | red (_background_) | 41 |
My Style
| +| `COLOR_BRIGHT_RED` | red bright | 91 |
My Style
| +| `COLOR_BRIGHT_BG_RED` | red bright (_background_) | 101 |
My Style
| +| `COLOR_GREEN` | green | 32 |
My Style
| +| `COLOR_BG_GREEN` | green (_background_) | 42 |
My Style
| +| `COLOR_BRIGHT_GREEN` | green bright | 92 |
My Style
| +| `COLOR_BRIGHT_BG_GREEN` | green bright (_background_) | 102 |
My Style
| +| `COLOR_YELLOW` | yellow | 33 |
My Style
| +| `COLOR_BG_YELLOW` | yellow (_background_) | 43 |
My Style
| +| `COLOR_BRIGHT_YELLOW` | yellow bright | 93 |
My Style
| +| `COLOR_BRIGHT_BG_YELLOW` | yellow bright (_background_) | 103 |
My Style
| +| `COLOR_BLUE` | blue | 34 |
My Style
| +| `COLOR_BG_BLUE` | blue (_background_) | 44 |
My Style
| +| `COLOR_BRIGHT_BLUE` | blue bright | 94 |
My Style
| +| `COLOR_BRIGHT_BG_BLUE` | blue bright (_background_) | 104 |
My Style
| +| `COLOR_MAGENTA` | magenta | 35 |
My Style
| +| `COLOR_BG_MAGENTA` | magenta (_background_) | 45 |
My Style
| +| `COLOR_BRIGHT_MAGENTA` | magenta bright | 95 |
My Style
| +| `COLOR_BRIGHT_BG_MAGENTA` | magenta bright (_background_) | 105 |
My Style
| +| `COLOR_CYAN` | cyan | 36 |
My Style
| +| `COLOR_BG_CYAN` | cyan (_background_) | 46 |
My Style
| +| `COLOR_BRIGHT_CYAN` | cyan bright | 96 |
My Style
| +| `COLOR_BRIGHT_BG_CYAN` | cyan bright (_background_) | 106 |
My Style
| +| `COLOR_WHITE` | white | 37 |
My Style
| +| `COLOR_BG_WHITE` | white (_background_) | 47 |
My Style
| +| `COLOR_BRIGHT_WHITE` | white bright | 97 |
My Style
| +| `COLOR_BRIGHT_BG_WHITE` | white bright (_background_) | 107 |
My Style
| +| `COLOR_DEFAULT` | default (_reset foreground_) | 39 | My Style | +| `COLOR_BG_DEFAULT` | default (_reset background_) | 49 | My Style | + +!!! note "Note" + _Depending on your terminal, colours might be rendered different from the above shown examples!_ + +## Your own styles + +To customise the default preset, set the `RSP_ANSI_PRESET` list property, before [enabling ANSI](#how-to-enable). +Use the following format for declaring your own styles and colours: + +``` +" [|]..." +``` +* _``_: name of your style or colour. +* _``_: control sequence [code](https://en.wikipedia.org/wiki/ANSI_escape_code#Select_Graphic_Rendition_parameters). +* _`|`_: separator (_when you define a style that uses multiple codes_) + + +```cmake +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include("rsp/output") + + set(RSP_ANSI_PRESET + "RESTORE 0" + + "MY_STYLE 1|31|23" # Set style to bold, italic, and red foreground. + + "TEXT_BOLD 1" + "TEXT_DIM 2" + + # ... remaining not shown ... + + # RECOMMENDED: you should cache your preset! + CACHE STRING "My custom ANSI preset" + ) + + enable_ansi() +endif() +``` + +!!! info "Tip: disable and re-enable" + _If you have customised the default provided ANSI preset, and your styles and colours have not taken effect, + it could be caused of by previous cached preset._ + + _To resolve such an issue, [disable](#how-to-disable) and then [re-enable](#how-to-enable) ANSI. Doing so will clear previous + cached properties and cache your custom preset._ From 16935fe056d45a88c1d3eceb83a2fd33424b4275 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 13:42:48 +0100 Subject: [PATCH 37/57] Improve examples for text styles Also, added missing TEXT_BLINK_RESTORE property --- docs/+current/modules/output/ansi.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/+current/modules/output/ansi.md b/docs/+current/modules/output/ansi.md index ef9e2c5..2163842 100644 --- a/docs/+current/modules/output/ansi.md +++ b/docs/+current/modules/output/ansi.md @@ -71,20 +71,21 @@ The following shows the default preset [text styles](https://en.wikipedia.org/wi | Property | Name | Control Sequence Code | Example | |------------------------------|---------------------------|----------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------| | `TEXT_BOLD` | bold | 1 | **My style** | -| `TEXT_BOLD_RESTORE` | bold (_restore_) | 22 | | +| `TEXT_BOLD_RESTORE` | bold (_restore_) | 22 | My style | | `TEXT_DIM` | dim | 2 | My style | -| `TEXT_DIM_RESTORE` | dim (_restore_) | 22 | | +| `TEXT_DIM_RESTORE` | dim (_restore_) | 22 | My style | | `TEXT_ITALIC` | italic | 3 | _My style_ | -| `TEXT_ITALIC_RESTORE` | italic (_restore_) | 23 | | +| `TEXT_ITALIC_RESTORE` | italic (_restore_) | 23 | My style | | `TEXT_UNDERLINE` | underline | 4 | My style | -| `TEXT_UNDERLINE_RESTORE` | underline (_restore_) | 24 | | -| `TEXT_BLINK` | blink | 5 | My style | +| `TEXT_UNDERLINE_RESTORE` | underline (_restore_) | 24 | My style | +| `TEXT_BLINK` | blink | 5 | My style | +| `TEXT_BLINK_RESTORE` | blink (_restore_) | 25 | My style | | `TEXT_INVERSE` | inverse | 7 | My style | -| `TEXT_INVERSE_RESTORE` | inverse (_restore_) | 27 | | -| `TEXT_HIDDEN` | hidden | 8 | | -| `TEXT_HIDDEN_RESTORE` | inverse (_restore_) | 28 | | +| `TEXT_INVERSE_RESTORE` | inverse (_restore_) | 27 | My style | +| `TEXT_HIDDEN` | hidden | 8 | My style | | +| `TEXT_HIDDEN_RESTORE` | inverse (_restore_) | 28 | My style | | `TEXT_STRIKETHROUGH` | strikethrough | 9 | My style | -| `TEXT_STRIKETHROUGH_RESTORE` | strikethrough (_restore_) | 29 | | +| `TEXT_STRIKETHROUGH_RESTORE` | strikethrough (_restore_) | 29 | My style | !!! note "Note" _Depending on your terminal, not all text styles might be supported!_ From 3f42b45335a5300a4972bd44117bd4c8d0ef73d9 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 14:03:01 +0100 Subject: [PATCH 38/57] Shorten intro text --- docs/+current/modules/output/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/+current/modules/output/index.md b/docs/+current/modules/output/index.md index d2fa56d..bfe124e 100644 --- a/docs/+current/modules/output/index.md +++ b/docs/+current/modules/output/index.md @@ -7,7 +7,7 @@ author: RSP Systems A/S # Output -This module offers a few console output utilities. +This module offers console output utilities. ## How to include From 202768e31ea7dccfea2ddfb0cedb34821f6f2d7f Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 14:05:30 +0100 Subject: [PATCH 39/57] Fix custom styles example --- docs/+current/modules/output/ansi.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/+current/modules/output/ansi.md b/docs/+current/modules/output/ansi.md index 2163842..b0f10f4 100644 --- a/docs/+current/modules/output/ansi.md +++ b/docs/+current/modules/output/ansi.md @@ -137,8 +137,8 @@ supported: ## Your own styles -To customise the default preset, set the `RSP_ANSI_PRESET` list property, before [enabling ANSI](#how-to-enable). -Use the following format for declaring your own styles and colours: +To customise the default preset, set the `RSP_ANSI_PRESET` list property, before including the `rsp/output` module, and +before [enabling ANSI](#how-to-enable). Use the following format for declaring your own styles and colours: ``` " [|]..." @@ -150,8 +150,7 @@ Use the following format for declaring your own styles and colours: ```cmake if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - include("rsp/output") - + # Define your own ANSI preset... set(RSP_ANSI_PRESET "RESTORE 0" @@ -166,6 +165,8 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) CACHE STRING "My custom ANSI preset" ) + include("rsp/output") + enable_ansi() endif() ``` From 41f9c0097051d0d370d889c6672652e55f2df650 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 14:44:32 +0100 Subject: [PATCH 40/57] Add doc for output() --- docs/+current/modules/output/helpers.md | 127 ++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 docs/+current/modules/output/helpers.md diff --git a/docs/+current/modules/output/helpers.md b/docs/+current/modules/output/helpers.md new file mode 100644 index 0000000..86181b3 --- /dev/null +++ b/docs/+current/modules/output/helpers.md @@ -0,0 +1,127 @@ +--- +title: Helpers +description: How to use the output helpers. +keywords: output, helpers, cmake +author: RSP Systems A/S +--- + +# Output Helpers + +[TOC] + +## `output()` + +Outputs a message to `stdout` or `stderr` +(_[message mode](https://cmake.org/cmake/help/latest/command/message.html#general-messages) specific_). +Behind the scene, the `output()` function is a wrapper for cmake's [`message()`](https://cmake.org/cmake/help/latest/command/message.html). + +It accepts the following parameters: + +* < message >: _`string`, `variable` or `list` message to output._ +* < mode >: (_option_), _cmake [message mode](https://cmake.org/cmake/help/latest/command/message.html#general-messages), e.g. `WARNING`, `NOTICE`, `STATUS`, ...etc._ +_Defaults to `NOTICE` is mode is not specified._ +* `OUTPUT`: (_optional_), _output variable. If specified, message is assigned to variable, instead of being printed to `stdout` or `stderr`._ +* `LABEL`: (_optional_), _A label to display before the message._ +* `LABEL_FORMAT`: (_optional_), _A format string in which the actual label is inserted. Use the `%label%` token, in which the actual label is inserted._ +* `LIST_SEPARATOR`: (_optional_), _Separator to used, if a list variable is given as message. Defaults to `\n` (newline)._ + +### Example + +```cmake +output("Building package assets" NOTICE LABEL "info") +``` + +The above example will print the following, using cmake's `NOTICE` mode. + +```txt +info: Building package assets +``` + +### Capture Output + +If you specify a variable for the `OUTPUT` parameter, then your message is assigned to that variable, instead of +being printed in the console. + +```cmake +output("Building package assets" NOTICE OUTPUT result) + +message("result = ${result}") +``` + +```txt +result = Building package assets +``` + +!!! note "Note" + When you specify the `OUTPUT` parameter, then the message `< mode >` argument is ignored. + As such, the following will never yield and error: + + ```cmake + # FATAL_ERROR message mode used, but OUTPUT specified... this never fails! + output("Should fail..." FATAL_ERROR OUTPUT output) + + # Message is printed... + message("${output} but doesn't") + ``` + +### Label & Label Format + +When you specify a `LABEL`, then it will be printed before the actual message. +By default, the format for the rendered label is: `%label%: `, where the `%label%` token is where the label is injected. +You can customise this format, by specifying the `LABEL_FORMAT` parameter. + +```cmake +output("Building package assets" NOTICE LABEL "✓" LABEL_FORMAT "[ %label% ] ") +``` + +```txt +[ ✓ ] Building package assets +``` + +You can also change the default label format, by setting the `RSP_DEFAULT_LABEL_FORMAT` property. + +```cmake +set(RSP_DEFAULT_LABEL_FORMAT "( %label% ) - " CACHE STRING "Default label format...") + +# ...Later in your cmake script... +output("Building package assets" NOTICE LABEL "ok") +``` + +```txt +( ok ) - Building package assets +``` + +!!! warning "Troubleshooting" + _If your custom label format does not take effect, then it is most likely because `RSP_DEFAULT_LABEL_FORMAT` is + cached property._ + + _Consider setting `RSP_DEFAULT_LABEL_FORMAT` before you include the `rsp/module`, or + [force](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry) cache the property._ + +### Lists + +`output()` is also able to print lists. By default, each item is printed on a new line. + +```cmake +set(my_list "a;b;c") + +output(my_list) +``` + +```txt +a +b +c +``` + +Use the `LIST_SEPARATOR` parameter to use a custom list item separator: + +```cmake +set(my_list "a;b;c") + +output(my_list LIST_SEPARATOR ", ") +``` + +```txt +a, b, c +``` \ No newline at end of file From 7da55c0242e35f097b031810d7f2552722311c9d Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 14:46:10 +0100 Subject: [PATCH 41/57] Fix typo --- docs/+current/modules/output/helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/+current/modules/output/helpers.md b/docs/+current/modules/output/helpers.md index 86181b3..e1520a4 100644 --- a/docs/+current/modules/output/helpers.md +++ b/docs/+current/modules/output/helpers.md @@ -95,7 +95,7 @@ output("Building package assets" NOTICE LABEL "ok") _If your custom label format does not take effect, then it is most likely because `RSP_DEFAULT_LABEL_FORMAT` is cached property._ - _Consider setting `RSP_DEFAULT_LABEL_FORMAT` before you include the `rsp/module`, or + _Consider setting `RSP_DEFAULT_LABEL_FORMAT` before you include the `rsp/output` module, or [force](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry) cache the property._ ### Lists From c0fa1ca9b29259ca422aee785771c313036e77f8 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 14:53:23 +0100 Subject: [PATCH 42/57] Highlight output module --- docs/+current/01_release-notes.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/+current/01_release-notes.md b/docs/+current/01_release-notes.md index ce45fe7..dc2365b 100644 --- a/docs/+current/01_release-notes.md +++ b/docs/+current/01_release-notes.md @@ -76,6 +76,20 @@ message("${my_package}_SEMVER") # 2.0.0-beta.3+AF1004 See [version module](./modules/version/index.md) for additional information. +### Output + +Various output and [ANSI](./modules/output/ansi.md) utilities. + +```cmake +output("Building package assets" NOTICE LABEL "✓" LABEL_FORMAT "[ %label% ] ") +``` + +```txt +[ ✓ ] Building package assets +``` + +See [output module](./modules/output/index.md) for additional information. + ### Cache (_available since `v0.1`_) From 51bb85579a35f21b58e49d67183646fa0e005468 Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 15:57:59 +0100 Subject: [PATCH 43/57] Add doc index page for logging module --- docs/+current/modules/logging/index.md | 39 ++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/+current/modules/logging/index.md diff --git a/docs/+current/modules/logging/index.md b/docs/+current/modules/logging/index.md new file mode 100644 index 0000000..3bdbc76 --- /dev/null +++ b/docs/+current/modules/logging/index.md @@ -0,0 +1,39 @@ +--- +title: Logging +description: How to use the logging module. +keywords: logging, log, cmake +author: RSP Systems A/S +--- + +# Logging + +The `logging` module offers an adaptation and implementation of the +[PSR-3: Logger Interface](https://www.php-fig.org/psr/psr-3/). It offers a series of predefined logging functions, +that are able to print a pre-formatted message to the console. +Behind the scene, the [`output()`](../output/helpers.md#output) function is used. + +## How to include + +```cmake +include("rsp/logging") +``` + +## Example + +```cmake +warning("Unable to find config.json") +``` + +The above shown example will print a message in the console, that is similar to the following: + +```txt +CMake Warning at cmake/rsp/output.cmake:144 (message): +warning: Unable to find config.json + + Timestamp: 2025-02-04 15:31:52.120599 +Call Stack (most recent call first): + cmake/rsp/logging.cmake:337 (output) + cmake/rsp/logging.cmake:383 (log) + cmake/rsp/logging.cmake:158 (forward_to_log) + CMakeLists.txt:10 (warning) +``` \ No newline at end of file From 4e9f2e20b410be97f633c3b72a5628f1d5bddf0c Mon Sep 17 00:00:00 2001 From: alin Date: Tue, 4 Feb 2025 15:58:18 +0100 Subject: [PATCH 44/57] Add description of the supported log levels --- docs/+current/modules/logging/01_levels.md | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 docs/+current/modules/logging/01_levels.md diff --git a/docs/+current/modules/logging/01_levels.md b/docs/+current/modules/logging/01_levels.md new file mode 100644 index 0000000..422beaa --- /dev/null +++ b/docs/+current/modules/logging/01_levels.md @@ -0,0 +1,70 @@ +--- +title: Log Levels +description: How customise log levels. +keywords: logging, log, levels, setup, cmake +author: RSP Systems A/S +--- + +# Log Levels + +[TOC] + +## PSR Log Levels + +The logging module defines the following log levels (_severities_), from +[PSR-3: Logger Interface](https://www.php-fig.org/psr/psr-3/) - (_In accordance with [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424)_). + +* `EMERGENCY_LEVEL`: _Emergency: system is unusable._ +* `ALERT_LEVEL`: _Alert: action must be taken immediately._ +* `CRITICAL_LEVEL`: _Critical: critical conditions._ +* `ERROR_LEVEL`: _Error: error conditions._ +* `WARNING_LEVEL`: _Warning: warning conditions._ +* `NOTICE_LEVEL`: _Notice: normal but significant condition._ +* `INFO_LEVEL`: _Informational: informational messages._ +* `DEBUG_LEVEL`: _Debug: debug-level messages._ + +## CMake Message Modes + +All the PSR log levels are associated with the following cmake [message modes](https://cmake.org/cmake/help/latest/command/message.html#general-messages). +This means that cmake's [message log level](https://cmake.org/cmake/help/latest/command/cmake_language.html#get-message-log-level) +is respected, when using any of the logging functions that are offered by this module. + +| Psr Log Level | CMake Message Mode | +|-------------------|--------------------| +| `EMERGENCY_LEVEL` | `FATAL_ERROR` | +| `ALERT_LEVEL` | `FATAL_ERROR` | +| `CRITICAL_LEVEL` | `FATAL_ERROR` | +| `ERROR_LEVEL` | `SEND_ERROR` | +| `WARNING_LEVEL` | `WARNING` | +| `NOTICE_LEVEL` | `NOTICE` | +| `INFO_LEVEL` | `NOTICE` | +| `DEBUG_LEVEL` | `DEBUG` | + +## Customize + +To change the default PSR log level / cmake message mode association, set the `RSP_LOG_LEVELS_CMAKE` property, +before you include the `rsp/logging` module +or by [force](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry) caching the property. + +Use the following format for associating the levels: + +```txt +" " +``` +* _``_: PSR log level name (_lowercase_). +* _``_: cmake's [message modes](https://cmake.org/cmake/help/latest/command/message.html#general-messages). + +```cmake +set(RSP_LOG_LEVELS_CMAKE + "${EMERGENCY_LEVEL} FATAL_ERROR" + "${ALERT_LEVEL} FATAL_ERROR" + "${CRITICAL_LEVEL} FATAL_ERROR" + "${ERROR_LEVEL} FATAL_ERROR" + "${WARNING_LEVEL} WARNING" + "${NOTICE_LEVEL} NOTICE" + "${INFO_LEVEL} STATUS" + "${DEBUG_LEVEL} STATUS" + + CACHE STRING "Log levels / message mode" +) +``` \ No newline at end of file From 0c9ec20496075b23bd8ee5316efdc03e32a8abf6 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 08:40:29 +0100 Subject: [PATCH 45/57] Improve sentence --- docs/+current/modules/logging/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/+current/modules/logging/index.md b/docs/+current/modules/logging/index.md index 3bdbc76..d4b6351 100644 --- a/docs/+current/modules/logging/index.md +++ b/docs/+current/modules/logging/index.md @@ -24,7 +24,7 @@ include("rsp/logging") warning("Unable to find config.json") ``` -The above shown example will print a message in the console, that is similar to the following: +The above shown example will print a message similar to this: ```txt CMake Warning at cmake/rsp/output.cmake:144 (message): From 724aa810e5d412ea285714a7d2d5e8009ee0960f Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 08:51:47 +0100 Subject: [PATCH 46/57] Improve descriptions of the levels and modes --- docs/+current/modules/logging/01_levels.md | 44 +++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/+current/modules/logging/01_levels.md b/docs/+current/modules/logging/01_levels.md index 422beaa..9692631 100644 --- a/docs/+current/modules/logging/01_levels.md +++ b/docs/+current/modules/logging/01_levels.md @@ -14,35 +14,37 @@ author: RSP Systems A/S The logging module defines the following log levels (_severities_), from [PSR-3: Logger Interface](https://www.php-fig.org/psr/psr-3/) - (_In accordance with [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424)_). -* `EMERGENCY_LEVEL`: _Emergency: system is unusable._ -* `ALERT_LEVEL`: _Alert: action must be taken immediately._ -* `CRITICAL_LEVEL`: _Critical: critical conditions._ -* `ERROR_LEVEL`: _Error: error conditions._ -* `WARNING_LEVEL`: _Warning: warning conditions._ -* `NOTICE_LEVEL`: _Notice: normal but significant condition._ -* `INFO_LEVEL`: _Informational: informational messages._ -* `DEBUG_LEVEL`: _Debug: debug-level messages._ +| Psr Log Level | Description | +|-------------------|-----------------------------------| +| `EMERGENCY_LEVEL` | System is unusable. | +| `ALERT_LEVEL` | Action must be taken immediately. | +| `CRITICAL_LEVEL` | Critical conditions. | +| `ERROR_LEVEL` | Error conditions. | +| `WARNING_LEVEL` | Warning conditions. | +| `NOTICE_LEVEL` | Normal but significant condition. | +| `INFO_LEVEL` | Informational messages. | +| `DEBUG_LEVEL` | Debug-level messages. | ## CMake Message Modes All the PSR log levels are associated with the following cmake [message modes](https://cmake.org/cmake/help/latest/command/message.html#general-messages). -This means that cmake's [message log level](https://cmake.org/cmake/help/latest/command/cmake_language.html#get-message-log-level) +This means that cmake's [current log level](https://cmake.org/cmake/help/latest/command/cmake_language.html#get-message-log-level) is respected, when using any of the logging functions that are offered by this module. -| Psr Log Level | CMake Message Mode | -|-------------------|--------------------| -| `EMERGENCY_LEVEL` | `FATAL_ERROR` | -| `ALERT_LEVEL` | `FATAL_ERROR` | -| `CRITICAL_LEVEL` | `FATAL_ERROR` | -| `ERROR_LEVEL` | `SEND_ERROR` | -| `WARNING_LEVEL` | `WARNING` | -| `NOTICE_LEVEL` | `NOTICE` | -| `INFO_LEVEL` | `NOTICE` | -| `DEBUG_LEVEL` | `DEBUG` | +| Psr Log Level | CMake Message Mode | Mode Description | +|-------------------|--------------------|----------------------------------------------------------| +| `EMERGENCY_LEVEL` | `FATAL_ERROR` | CMake Error, stops processing and generation. | +| `ALERT_LEVEL` | `FATAL_ERROR` | CMake Error, stops processing and generation. | +| `CRITICAL_LEVEL` | `FATAL_ERROR` | CMake Error, stops processing and generation. | +| `ERROR_LEVEL` | `SEND_ERROR` | CMake Error, continues processing, but skips generation. | +| `WARNING_LEVEL` | `WARNING` | CMake Warning, continues processing. | +| `NOTICE_LEVEL` | `NOTICE` | Important message printed to stderr. | +| `INFO_LEVEL` | `NOTICE` | Important message printed to stderr. | +| `DEBUG_LEVEL` | `DEBUG` | Detailed informational messages intended for developers. | ## Customize -To change the default PSR log level / cmake message mode association, set the `RSP_LOG_LEVELS_CMAKE` property, +To change the default PSR log level / cmake message mode association, set the `RSP_LOG_LEVELS_CMAKE` list property, before you include the `rsp/logging` module or by [force](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry) caching the property. @@ -51,7 +53,7 @@ Use the following format for associating the levels: ```txt " " ``` -* _``_: PSR log level name (_lowercase_). +* _``_: PSR log level name. * _``_: cmake's [message modes](https://cmake.org/cmake/help/latest/command/message.html#general-messages). ```cmake From c09ff51ece1f705ab3a8448f73070ef24b794de4 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 09:39:30 +0100 Subject: [PATCH 47/57] Fix typo --- cmake/rsp/logging.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 864bb9e..8f586e5 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -82,7 +82,7 @@ endif () if (NOT COMMAND "critical") - #! critical : Log an "critical" level message - Critical conditions. + #! critical : Log a "critical" level message - Critical conditions. # E.g. Application component unavailable, unexpected exception. # # @see log() From 18fea52e361220c649d6a4d88fefba82f80c4653 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 09:57:57 +0100 Subject: [PATCH 48/57] Fix typos --- cmake/rsp/logging.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 8f586e5..7bbd14a 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -135,7 +135,7 @@ endif () if (NOT COMMAND "warning") - #! warning : Log an "warning" level message - Exceptional occurrences that are not errors. + #! warning : Log a "warning" level message - Exceptional occurrences that are not errors. # # @see log() # @@ -161,7 +161,7 @@ endif () if (NOT COMMAND "notice") - #! notice : Log an "notice" level message - Normal but significant events. + #! notice : Log a "notice" level message - Normal but significant events. # # @see log() # From 2a4537374213c05d79dbd0b1904190ca93af3be9 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 10:55:20 +0100 Subject: [PATCH 49/57] Fix descriptions --- cmake/rsp/logging.cmake | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmake/rsp/logging.cmake b/cmake/rsp/logging.cmake index 7bbd14a..659d083 100644 --- a/cmake/rsp/logging.cmake +++ b/cmake/rsp/logging.cmake @@ -187,7 +187,7 @@ endif () if (NOT COMMAND "info") - #! info : Log an "info" level message - Normal but significant events. + #! info : Log an "info" level message - Informational events. # # @see log() # @@ -196,7 +196,6 @@ if (NOT COMMAND "info") # using the LIST_SEPARATOR. # @param [] Option - Cmake's message type. Defaults to the `mode` that is # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. - # NOTICE, when not specified. # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the # log message. # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead @@ -214,7 +213,7 @@ endif () if (NOT COMMAND "debug") - #! debug : Log an "debug" level message - Normal but significant events. + #! debug : Log a "debug" level message - Debugging information or events. # # @see log() # @@ -223,7 +222,6 @@ if (NOT COMMAND "debug") # using the LIST_SEPARATOR. # @param [] Option - Cmake's message type. Defaults to the `mode` that is # associated with the specified `level`, defined in RSP_LOG_LEVELS_CMAKE. - # NOTICE, when not specified. # @param [CONTEXT ...] Optional - Evt. variables to output in a "context" associated with the # log message. # @param [OUTPUT ] Optional - If specified, message is assigned to output variable instead From 4525276d184536f91fdae9edf190ea758448b85d Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 11:35:38 +0100 Subject: [PATCH 50/57] Fix typo --- docs/+current/modules/output/helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/+current/modules/output/helpers.md b/docs/+current/modules/output/helpers.md index e1520a4..9828202 100644 --- a/docs/+current/modules/output/helpers.md +++ b/docs/+current/modules/output/helpers.md @@ -54,7 +54,7 @@ result = Building package assets !!! note "Note" When you specify the `OUTPUT` parameter, then the message `< mode >` argument is ignored. - As such, the following will never yield and error: + As such, the following will never yield an error: ```cmake # FATAL_ERROR message mode used, but OUTPUT specified... this never fails! From 505f000e0ab3ceeed43bcbd46a0141158a21b442 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 11:36:46 +0100 Subject: [PATCH 51/57] Add descriptions and examples for each log function --- docs/+current/modules/logging/02_functions.md | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 docs/+current/modules/logging/02_functions.md diff --git a/docs/+current/modules/logging/02_functions.md b/docs/+current/modules/logging/02_functions.md new file mode 100644 index 0000000..079d3f1 --- /dev/null +++ b/docs/+current/modules/logging/02_functions.md @@ -0,0 +1,241 @@ +--- +title: Log Functions +description: How use log functions. +keywords: logging, log, functions, cmake +author: RSP Systems A/S +--- + +# Log Functions + +[TOC] + +## `emergency()` + +Logs an "emergency" level message. + +```cmake +emergency("External power supply is unavailable") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `alert()` + +Logs an "alert" level message. + +```cmake +alert("Storage disk is above 90% full, cleanup is required") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `critical()` + +Logs a "critical" level message. + +```cmake +critical("Acme 3.7V Li-Po Battery Driver is unavailable, unable to continue build") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `error()` + +Log an "error" level message. + +```cmake +error("Acme LDSv6 Driver failed loading device.ini") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `warning()` + +Logs a "warning" level message. + +```cmake +warning("No configuration found for Acme VCMx Driver, using driver defaults") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `notice()` + +Logs a "notice" level message. + +```cmake +notice("Acme CPU32v6xx Power Control Driver build completed") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `info()` + +Log an "info" level message. + +```cmake +info("Started building external system assets") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `debug()` + +Log a "debug" level message. + +```cmake +debug("Network: eth4, via pci@0004:01:00.0 (serial: e4:1d:2d:67:83:56)") +``` + +The following parameters are supported: + +* < message > +* < mode >: (_option_) +* `CONTEXT`: (_optional_) +* `OUTPUT`: (_optional_) +* `LIST_SEPARATOR`: (_optional_) + +_See [`log()`](#log) function for parameter descriptions and examples._ + +## `log()` + +Log a message with an arbitrary level. + +```cmake +log(INFO_LEVEL "Removing expired tmp files from cache storage") +``` + +Behind the scene, `log()` uses the [`output()`](../output/helpers.md#output) function to print messages to the console. +It supports the following parameters: + +* < level >: _The PSR log level (see [`RSP_LOG_LEVELS`](./01_levels.md#psr-log-levels))_ +* < message >: _`string`, `variable` or `list` message to output._ +* < mode >: (_option_), _cmake [message mode](https://cmake.org/cmake/help/latest/command/message.html#general-messages), e.g. `WARNING`, `NOTICE`, `STATUS`, ...etc._ + _Defaults to the mode that is associated with the given log level (see [`RSP_LOG_LEVELS_CMAKE`](./01_levels.md#cmake-message-modes))._ +* `CONTEXT`: (_optional_), _Evt. variables to output in a "context" associated with the log message._ +* `OUTPUT`: (_optional_), _output variable. If specified, message is assigned to variable, instead of being printed to `stdout` or `stderr`._ +* `LIST_SEPARATOR`: (_optional_), _Separator to used, if a list variable is given as message. Defaults to `\n` (newline)._ + +### Mode + +Unless you specify the cmake [message mode](https://cmake.org/cmake/help/latest/command/message.html#general-messages), `log()` will automatically apply the mode that is associated with +the specified PSR log level (_defined by the [`RSP_LOG_LEVELS_CMAKE`](./01_levels.md#cmake-message-modes) property_). + + +Consequently, in situations when you need to deviate from the default associated mode, simply specify the desired +message mode. + +```cmake +# Log a "warning" level message, but use a STATUS cmake message mode +log(WARNING_LEVEL "Config NOT found for VCMx Driver, using defaults" STATUS) +``` + +The above example will print a message similar to this: + +```txt +-- warning: Config NOT found for VCMx Driver, using defaults + Timestamp: 2025-02-05 11:33:32.190620 +``` + +### Context + +The `CONTEXT` parameter allows you to associate one or more variables with the given log entry. + +```cmake +log( + NOTICE_LEVEL "Assets build completed" + CONTEXT + assets_dir + my_assets_list +) +``` + +The above shown example will print a message similar to this: + +```txt +notice: Assets build completed + Context: + [ + assets_dir = build/resources/config + my_assets_list = graft.conf;ports.init;power.json + ] + Timestamp: 2025-02-05 11:08:40.912747 +``` + +### Timestamp + +By default, a timestamp is appended at the end of each logged message. +You can modify this behaviour, and the timestamp format by changing the following predefined properties: + +| Property | Default | Description | +|----------------------------|------------------------|-----------------------------------------------------------------------------------------------------------------------------| +| `RSP_LOG_SHOW_TIMESTAMP` | `true` | State whether to append timestamp for log entries, or not | +| `RSP_LOG_TIMESTAMP_FORMAT` | `%Y-%m-%d %H:%M:%S.%f` | Timestamp format. See [CMake documentation](https://cmake.org/cmake/help/latest/command/string.html#timestamp) for details. | +| `RSP_LOG_TIMESTAMP_UTC` | `false` | True if timestamp is UTC, false if timestamp is local | + +!!! note "Note" + Properties should be set before you include the `rsp/logging` module or by [force](https://cmake.org/cmake/help/latest/command/set.html#set-cache-entry) caching the property. + +### Output + +See ["Capture Output"](../output/helpers.md#capture-output) for additional information about the `OUTPUT` parameter. + +### Lists + +See [output "Lists"](../output/helpers.md#lists) for additional information about the `LIST_SEPARATOR` parameter. \ No newline at end of file From 4fcb60c15f34b89956c3092cfbb19926c632dd9d Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 11:38:39 +0100 Subject: [PATCH 52/57] Change release notes --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 247cbc2..a9fc596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `VERSION` file. * RSP's GCC strict compile options, in `gcc.cmake` (_exposed via `compiler.cmake` module_). * Caching utilities, `cache.cmake`. +* `output()` helper, in `output.cmake`. +* Support for ANSI, in `output.cmake`. +* PSR inspired logging functions, in `logging.cmake`. * A "mini" testing framework for cmake modules and scripts, in `testing.cmake`. * `RSP_CMAKE_SCRIPTS_BUILD_TESTS` project option for building tests. * `tests.yaml` and `deploy-docs.yaml` GitHub Actions workflows. From 093bc036c25b432ab051f7463fc31a10682bb6c2 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 11:40:14 +0100 Subject: [PATCH 53/57] Enable ANSI, when project is top-level --- CMakeLists.txt | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ebe6bda..9e0c913 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,19 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) include("dev-dependencies.cmake") endif() +# -------------------------------------------------------------------------------------------------------------- # +# Post-dependencies project setup +# -------------------------------------------------------------------------------------------------------------- # + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + include("rsp/debug") + include("rsp/output") + include("rsp/logging") + + enable_ansi() + # disable_ansi() +endif() + # -------------------------------------------------------------------------------------------------------------- # # Tests # -------------------------------------------------------------------------------------------------------------- # @@ -78,13 +91,5 @@ endif () # Misc. # -------------------------------------------------------------------------------------------------------------- # -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - include("rsp/debug") - include("rsp/output") - - enable_ansi() - - # output_ansi_demo() - - # dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME) -endif() +# output_ansi_demo() +# dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME) From 9670949ee7840dc774528e0ee336c66e42a0414e Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 14:29:19 +0100 Subject: [PATCH 54/57] Change root CMakeLists, add RSP_ENABLE_ANSI output option Project will now enable or disable ANSI output, depending on the option. Defaults to false. --- CMakeLists.txt | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e0c913..fb2e730 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.30) option(RSP_CMAKE_SCRIPTS_BUILD_TESTS "Build tests for the RSP CMake Scripts project" off) +option(RSP_ENABLE_ANSI "Enable ANSI output" false) # -------------------------------------------------------------------------------------------------------------- # # Setup @@ -65,17 +66,25 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) include("dev-dependencies.cmake") endif() +# -------------------------------------------------------------------------------------------------------------- # +# Toggle ANSI output +# -------------------------------------------------------------------------------------------------------------- # + +include("rsp/output") + +if (RSP_ENABLE_ANSI) + enable_ansi() +else () + disable_ansi() +endif () + # -------------------------------------------------------------------------------------------------------------- # # Post-dependencies project setup # -------------------------------------------------------------------------------------------------------------- # if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) include("rsp/debug") - include("rsp/output") include("rsp/logging") - - enable_ansi() - # disable_ansi() endif() # -------------------------------------------------------------------------------------------------------------- # @@ -91,5 +100,13 @@ endif () # Misc. # -------------------------------------------------------------------------------------------------------------- # +log( + NOTICE_LEVEL "Assets build completed" + CONTEXT + CMAKE_BINARY_DIR + PROJECT_NAME + CMAKE_PROJECT_NAME +) + # output_ansi_demo() # dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME) From 3b04f10741af30d0b922d6e754da091295e0c677 Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 14:46:49 +0100 Subject: [PATCH 55/57] Change how to enable / disable ANSI output Now using the newly added RSP_ENABLE_ANSI option. --- docs/+current/modules/output/ansi.md | 33 ++++++++++------------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/docs/+current/modules/output/ansi.md b/docs/+current/modules/output/ansi.md index b0f10f4..3416e66 100644 --- a/docs/+current/modules/output/ansi.md +++ b/docs/+current/modules/output/ansi.md @@ -12,31 +12,20 @@ allow you to format and style your console output. [TOC] -## How to enable +## How to enable / disable -To enable ANSI output, call the `enable_ansi()`. It is **recommended** that you only do so, if your project is the -top-level cmake project. +To enable ANSI output, set the `RSP_ENABLE_ANSI` option to `true`, when importing this project. +By default, the option is set to `false`. ```cmake -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - include("rsp/output") - - enable_ansi() -endif() -``` - -!!! warning "Warning" - The `enable_ansi()` caches preset of ANSI escape sequences. This means that even if you remove - the function call, ANSI will remain enabled, unless you explicitly [disable it](#how-to-disable) again. - -## How to disable - -```cmake -if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - include("rsp/output") - - disable_ansi() -endif() +CPMAddPackage( + NAME rsp-cmake-scripts + GITHUB_REPOSITORY "https://github.com/rsps/cmake-scripts.git" + VERSION x.y.z + OPTIONS + "RSP_ENABLE_ANSI true" + # ...remaining not shown ... +) ``` ## Example From 7a4dfb4c3ec5d119b7a96169aafd52e7815760fd Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 14:50:59 +0100 Subject: [PATCH 56/57] Highlight logging module --- docs/+current/01_release-notes.md | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/+current/01_release-notes.md b/docs/+current/01_release-notes.md index dc2365b..0e664d9 100644 --- a/docs/+current/01_release-notes.md +++ b/docs/+current/01_release-notes.md @@ -90,6 +90,39 @@ output("Building package assets" NOTICE LABEL "✓" LABEL_FORMAT "[ %label% ] ") See [output module](./modules/output/index.md) for additional information. +### Logging + +[PSR-3](https://www.php-fig.org/psr/psr-3/) inspired logging utilities. + +```cmake +warning("Build incomplete" + CONTEXT + PROJECT_NAME + CMAKE_BINARY_DIR + CMAKE_PROJECT_NAME +) +``` + +```txt +CMake Warning at cmake/rsp/output.cmake:144 (message): +warning: Build incomplete + + Context: + [ + PROJECT_NAME = my-project + CMAKE_BINARY_DIR = /home/user/code/my-project/build + CMAKE_PROJECT_NAME = my-project + ] + Timestamp: 2025-02-05 14:49:16.087085 +Call Stack (most recent call first): + cmake/rsp/logging.cmake:335 (output) + cmake/rsp/logging.cmake:381 (log) + cmake/rsp/logging.cmake:158 (forward_to_log) + CMakeLists.txt:103 (warning) +``` + +See [logging module](./modules/logging/index.md) for additional information. + ### Cache (_available since `v0.1`_) From 6ce50daed6f25af7db2a3d362906d34b336312fa Mon Sep 17 00:00:00 2001 From: alin Date: Wed, 5 Feb 2025 14:51:15 +0100 Subject: [PATCH 57/57] Cleanup --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb2e730..f570192 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,13 +100,5 @@ endif () # Misc. # -------------------------------------------------------------------------------------------------------------- # -log( - NOTICE_LEVEL "Assets build completed" - CONTEXT - CMAKE_BINARY_DIR - PROJECT_NAME - CMAKE_PROJECT_NAME -) - # output_ansi_demo() # dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME)