Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c915e13
Add top-level output module (incomplete)
aedart Jan 29, 2025
ff5d39a
Add ANSI sub-module
aedart Jan 30, 2025
e5e9302
Include ANSI sub-module
aedart Jan 30, 2025
d021ca3
Use requires_arguments() for assert compare values
aedart Jan 30, 2025
e2d1366
Shorten "escape character" input variable
aedart Jan 30, 2025
63ddee8
Add tests for ANSI output utils
aedart Jan 30, 2025
3e85525
Fix RSP_ANSI_PRESET not defined for entire project
aedart Jan 30, 2025
13fff16
Add demo output function
aedart Jan 30, 2025
c424c56
Enable ANSI, when project is the root project
aedart Jan 30, 2025
3a47b80
Change release notes
aedart Jan 30, 2025
bec7b41
Add output() function
aedart Jan 31, 2025
8761ada
Fix ANSI sequence variables only available for current scope
aedart Jan 31, 2025
cbfe749
Add tests for output()
aedart Jan 31, 2025
96bec1f
Refactor output(), extract message mode handling into macro
aedart Jan 31, 2025
c15906e
Add logging module (incomplete)
aedart Jan 31, 2025
5bf7d6f
Preserve message formatting, even when cmake's warn and above takes over
aedart Feb 3, 2025
f5f920c
Add RSP_LOG_INDENT
aedart Feb 3, 2025
b9f9748
Improve function description
aedart Feb 3, 2025
0a1db69
Change resolve_msg_mode() to accept a default mode
aedart Feb 3, 2025
6e3040e
Resolve cmake message mode, acc. to PSR log level
aedart Feb 3, 2025
bb5709d
Fix missing include guard
aedart Feb 3, 2025
9dfcb22
Refactor, extract formatting utils into sub-module
aedart Feb 3, 2025
57ee993
Refactor, extract resolve list separator into macro
aedart Feb 3, 2025
83a6c71
Refactor log(), extract resolve cmake msg mode logic into macro
aedart Feb 3, 2025
0deb60b
Refactor, use resolve_list_separator()
aedart Feb 3, 2025
ae2c5be
Fix formatting
aedart Feb 3, 2025
8da06c2
Add RSP_LOG_TIMESTAMP_UTC state
aedart Feb 3, 2025
a98b654
Change list separator handling
aedart Feb 3, 2025
1725b3c
Add remaining logging functions from PSR
aedart Feb 3, 2025
5f0f5ca
Add string contains / not contains asserts
aedart Feb 4, 2025
5131ce9
Add tests for string contains / not contains asserts
aedart Feb 4, 2025
ff7c38f
Add examples / ref. for string contains / not contains asserts
aedart Feb 4, 2025
a7b8d9a
Fix strange name conflict
aedart Feb 4, 2025
1f02da5
Add tests for log functions
aedart Feb 4, 2025
ac94155
Add docs index page for output module
aedart Feb 4, 2025
f757e0c
Add doc for output ANSI
aedart Feb 4, 2025
16935fe
Improve examples for text styles
aedart Feb 4, 2025
3f42b45
Shorten intro text
aedart Feb 4, 2025
202768e
Fix custom styles example
aedart Feb 4, 2025
41f9c00
Add doc for output()
aedart Feb 4, 2025
7da55c0
Fix typo
aedart Feb 4, 2025
c0fa1ca
Highlight output module
aedart Feb 4, 2025
51bb855
Add doc index page for logging module
aedart Feb 4, 2025
4e9f2e2
Add description of the supported log levels
aedart Feb 4, 2025
0c9ec20
Improve sentence
aedart Feb 5, 2025
724aa81
Improve descriptions of the levels and modes
aedart Feb 5, 2025
c09ff51
Fix typo
aedart Feb 5, 2025
18fea52
Fix typos
aedart Feb 5, 2025
2a45373
Fix descriptions
aedart Feb 5, 2025
4525276
Fix typo
aedart Feb 5, 2025
505f000
Add descriptions and examples for each log function
aedart Feb 5, 2025
4fcb60c
Change release notes
aedart Feb 5, 2025
093bc03
Enable ANSI, when project is top-level
aedart Feb 5, 2025
9670949
Change root CMakeLists, add RSP_ENABLE_ANSI output option
aedart Feb 5, 2025
3b04f10
Change how to enable / disable ANSI output
aedart Feb 5, 2025
7a4dfb4
Highlight logging module
aedart Feb 5, 2025
6ce50da
Cleanup
aedart Feb 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ 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.
* 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.
Expand Down
27 changes: 24 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,6 +66,27 @@ 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/logging")
endif()

# -------------------------------------------------------------------------------------------------------------- #
# Tests
# -------------------------------------------------------------------------------------------------------------- #
Expand All @@ -78,6 +100,5 @@ endif ()
# Misc.
# -------------------------------------------------------------------------------------------------------------- #

# include("rsp/debug")

# dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME)
# output_ansi_demo()
# dump(CMAKE_MODULE_PATH FOO BAR PROJECT_NAME)
393 changes: 393 additions & 0 deletions cmake/rsp/logging.cmake

Large diffs are not rendered by default.

325 changes: 325 additions & 0 deletions cmake/rsp/logging/utils.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
# -------------------------------------------------------------------------------------------------------------- #
# 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 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)

# 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 "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}")

# psr = PSR log level, value = CMake message mode
list(GET parts 0 psr)
list(GET parts 1 msg_mode_cmake)

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)

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
#
# @param <string> level Log level
# @param <variable> 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 <string> level Log level
# @param <string> message The log message
# @param <variable> 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 <string> level Log level
# @param <list> context List of variables
# @param <variable> 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 <string> level Log level
# @param <variable> 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 ()

# 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}")

return(PROPAGATE "${output}")
endfunction()
endif ()
Loading