From 799dacf48518b4541e84b0bf24a15ef587fe28b6 Mon Sep 17 00:00:00 2001 From: Nazmul Idris Date: Mon, 16 Dec 2024 12:39:12 -0600 Subject: [PATCH] [all][script][log] Promote scripting.rs from rust_scratch/tls Create 2 new crates: - script - log - support UnicodeString for output - update all calls to tracing::*() to work seamlessly with CustomEventFormatter across the entire r3bl codebase (especially r3bl_tui examples) - add of the `options: impl Into` pattern for configuring the log functionality. this is ergonomic for simple cases (for the caller) and provides sophisticated configuration possibilities due to its composability. Reorganize all the existing crates as well, to ensure that known duplicated functions are removed. Add new glyphs that are unicode character based, not emojis, so that the fg, bg color and attributes are applied in output. Emojis come with their own colors, so the fg and bg colors don't get applied. --- .idea/icon.svg | 50 ++ .idea/r3bl-open-core.iml | 2 + .vscode/settings.json | 3 + CHANGELOG.md | 51 ++ Cargo.lock | 243 +++++++--- Cargo.toml | 2 + cmdr/Cargo.toml | 13 +- cmdr/src/bin/edi.rs | 4 +- cmdr/src/bin/giti.rs | 5 +- core/Cargo.toml | 12 +- core/src/common/mod.rs | 4 + core/src/common/ordered_map.rs | 125 +++++ .../text_default_styles.rs} | 0 core/src/glyphs.rs | 54 +++ core/src/lib.rs | 6 +- core/src/logging/logging_api.rs | 60 --- core/src/logging/simple_file_logging_impl.rs | 39 -- core/src/misc/mod.rs | 4 + core/src/misc/string_helpers.rs | 170 +++++++ .../src => core/src/misc}/temp_dir.rs | 34 +- core/src/storage/kv.rs | 27 +- core/src/terminal_io/shared_writer.rs | 14 +- core/src/tui_core/graphemes/access.rs | 11 + core/src/tui_core/graphemes/convert.rs | 13 +- core/src/tui_core/tui_style/tui_style_impl.rs | 10 +- log/Cargo.toml | 63 +++ {core => log}/src/bin/tracing_test_bin.rs | 18 +- core/src/logging/mod.rs => log/src/lib.rs | 12 +- log/src/log_support/custom_event_formatter.rs | 403 +++++++++++++++ .../src/log_support}/mod.rs | 8 +- log/src/log_support/public_api.rs | 409 ++++++++++++++++ .../rolling_file_appender_impl.rs | 0 .../src/log_support}/tracing_config.rs | 102 ++-- .../src/log_support/tracing_init.rs | 72 ++- script/Cargo.toml | 54 +++ script/src/apt_install.rs | 111 +++++ script/src/command_runner.rs | 367 ++++++++++++++ script/src/directory_change.rs | 187 +++++++ script/src/directory_create.rs | 147 ++++++ script/src/download.rs | 79 +++ script/src/environment.rs | 124 +++++ script/src/fs_path.rs | 459 ++++++++++++++++++ script/src/github_api.rs | 82 ++++ script/src/http_client.rs | 34 ++ script/src/lib.rs | 40 ++ script/src/permissions.rs | 95 ++++ terminal_async/Cargo.toml | 9 +- terminal_async/examples/terminal_async.rs | 15 +- test_fixtures/src/lib.rs | 2 - tui/Cargo.toml | 8 +- .../demo/ex_app_no_layout/app_main.rs | 4 +- .../single_column_component.rs | 20 +- .../demo/ex_app_with_2col_layout/app_main.rs | 53 +- .../column_render_component.rs | 25 +- tui/examples/demo/ex_rc/app_main.rs | 4 +- tui/examples/demo/main.rs | 12 +- .../editor_buffer/editor_buffer_struct.rs | 1 + tui/src/tui/layout/flex_box.rs | 65 ++- tui/src/tui/layout/flex_box_id.rs | 6 +- tui/src/tui/misc/format_option.rs | 16 +- .../syntax_highlighting/intermediate_types.rs | 4 +- .../async_event_stream_ext.rs | 112 ----- tui/src/tui/terminal_lib_backends/keypress.rs | 2 +- .../terminal_lib_backends/render_pipeline.rs | 2 +- .../tui/terminal_window/main_event_loop.rs | 70 ++- .../tui/terminal_window/shared_global_data.rs | 11 +- .../tui/terminal_window/static_global_data.rs | 128 ++++- tuify/Cargo.toml | 5 +- tuify/examples/main_interactive.rs | 9 +- tuify/src/bin/rt.rs | 9 +- tuify/src/test_utils.rs | 36 -- 71 files changed, 3832 insertions(+), 618 deletions(-) create mode 100644 .idea/icon.svg create mode 100644 core/src/common/ordered_map.rs rename core/src/{logging/color_text_default_styles.rs => common/text_default_styles.rs} (100%) create mode 100644 core/src/glyphs.rs delete mode 100644 core/src/logging/logging_api.rs delete mode 100644 core/src/logging/simple_file_logging_impl.rs create mode 100644 core/src/misc/string_helpers.rs rename {test_fixtures/src => core/src/misc}/temp_dir.rs (79%) create mode 100644 log/Cargo.toml rename {core => log}/src/bin/tracing_test_bin.rs (69%) rename core/src/logging/mod.rs => log/src/lib.rs (72%) create mode 100644 log/src/log_support/custom_event_formatter.rs rename {core/src/tracing_logging => log/src/log_support}/mod.rs (83%) create mode 100644 log/src/log_support/public_api.rs rename {core/src/tracing_logging => log/src/log_support}/rolling_file_appender_impl.rs (100%) rename {core/src/tracing_logging => log/src/log_support}/tracing_config.rs (67%) rename core/src/tracing_logging/init_tracing.rs => log/src/log_support/tracing_init.rs (82%) create mode 100644 script/Cargo.toml create mode 100644 script/src/apt_install.rs create mode 100644 script/src/command_runner.rs create mode 100644 script/src/directory_change.rs create mode 100644 script/src/directory_create.rs create mode 100644 script/src/download.rs create mode 100644 script/src/environment.rs create mode 100644 script/src/fs_path.rs create mode 100644 script/src/github_api.rs create mode 100644 script/src/http_client.rs create mode 100644 script/src/lib.rs create mode 100644 script/src/permissions.rs delete mode 100644 tui/src/tui/terminal_lib_backends/async_event_stream_ext.rs diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 000000000..5d230ee5d --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.idea/r3bl-open-core.iml b/.idea/r3bl-open-core.iml index dbd55ae8f..ad3ff7b3b 100644 --- a/.idea/r3bl-open-core.iml +++ b/.idea/r3bl-open-core.iml @@ -20,6 +20,8 @@ + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 3bcb83893..dc4bd9c0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "Blackbox", "BOOKM", "boop", + "callsite", "CBOR", "chrono", "cicd", @@ -57,6 +58,7 @@ "enigo", "enumflags", "flamegraph", + "formatcp", "FOSS", "frontmatter", "Giti", @@ -135,6 +137,7 @@ "tempfile", "terminusdb", "thingbuf", + "Tifinagh", "Tilix", "TODONE", "todos", diff --git a/CHANGELOG.md b/CHANGELOG.md index e9744723a..ea3df91fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,10 @@ - [v0.0.3 2024-09-12](#v003-2024-09-12) - [v0.0.2 2024-07-13](#v002-2024-07-13) - [v0.0.1 2024-07-12](#v001-2024-07-12) +- [r3bl_log](#r3bl_log) + - [v_next_release_log](#v_next_release_r3bl_log) +- [r3bl_script](#r3bl_script) + - [v_next_release_script](#v_next_release_r3bl_script) - [r3bl_terminal_async](#r3bl_terminal_async) - [v0.6.0 2024-10-21](#v060-2024-10-21) - [v0.5.7 2024-09-12](#v057-2024-09-12) @@ -778,11 +782,20 @@ Changed: replace them with doc comments that compile successfully. - Added: + - `UnicodeString` now implements `std::fmt::Display`, so it is no longer necessary to + use `UnicodeString.string` to get to the underlying string. This is just more + ergonomic. This is added in `convert.rs`. More work needs to be done to introduce + different types for holding "width / height" aka "column / row counts", and "column / + row index". Currently there is a `Size` struct, and `Position` struct, but most of the + APIs don't really use one or another, they just use `ChUnit` directly. - `lolcat_api` enhancements that now allow for an optional default style to be passed in to `ColorWheel::lolcat_into_string` and `ColorWheel::colorize_into_string`, that will be applied to the generated lolcat output. - `convert_to_ansi_color_styles` module that adds the ability to convert a `TuiStyle` into a `Vec` of `r3bl_ansi_term::Style`. + - `string_helpers.rs` has new utility functions to check whether a given string contains + any ANSI escape codes `contains_ansi_escape_sequence`. And to remove needless escaped + `\"` characters from a string `remove_escaped_quotes`. - A new declarative macro `create_global_singleton!` that takes a struct (which must implement `Default` trait) and allows it to be simply turned into a singleton. - You can still use the struct directly. Or just use the supplied generated associated @@ -986,6 +999,44 @@ links for this release: [crates.io](https://crates.io/crates/r3bl_test_fixtures) crates in this monorepo to use them. These fixtures are migrated from `r3bl_terminal_async` crate, where they were gestated, before being graduated for use by the entire monorepo. +## `r3bl_log` + +### v_next_release_r3bl_log + +This is the first release of this crate. It is a top level crate in the `r3bl-open-core` +that is meant to hold all the logging related functionality for all the other crates in +this monorepo. It uses `tracing` under the covers to provide structured logging. It also +provides a custom formatter that is a `tracing-subscriber` crate plugin. + +Added: + - Moved all the tracing and logging functionality from `r3bl_core` in here. + - Make the public API more ergonomic and use the `options: impl Into` + pattern for all the functions that need to be configured. This makes it easy to define + simple configuration options, while allowing for easy composition of more complex + options. We really like this pattern and intend to refactor the entire codebase over + time to use this. + +Changed: + - `WriterConfig` can now be merged with other instances. This was a requirement for the + `TracingConfig` to be able to merge multiple `WriterConfig` instances into a single + `WriterConfig` instance. The code is in `src/log_support/public_api.rs` since this + functionality is related to making the API easier to use by callers. This is in + support of the `options: impl Into` pattern. + - Two `TracingConfig` instances can be added together to create a new `TracingConfig` + instance. This is needed for composability and an easy to use API for callers. Lots of + converts are provide to make it easy to convert from a variety of configuration types + into a `TracingConfig` instance. The code is in `src/log_support/public_api.rs`. This + is in support of the `options: impl Into` pattern. + +## `r3bl_script` + +### v_next_release_r3bl_script + +This is the first release of this crate. It is a top level crate in the `r3bl-open-core` +that is meant to hold all the scripting related functionality for all the other crates in +this monorepo. It provides a way to run scripts in a safe and secure way, that is meant to +be a replacement for writing scripts in `fish` or `bash` or `nushell` syntax. + ## `r3bl_terminal_async` ### v0.6.0 (2024-10-21) diff --git a/Cargo.lock b/Cargo.lock index efc877bec..b57476771 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,7 +134,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -264,9 +264,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -297,7 +297,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", - "terminal_size 0.4.0", + "terminal_size", ] [[package]] @@ -309,7 +309,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -343,6 +343,26 @@ dependencies = [ "csscolorparser", ] +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "copypasta" version = "0.8.2" @@ -700,7 +720,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1061,7 +1081,7 @@ dependencies = [ "serde", "serde_json", "sled", - "thiserror", + "thiserror 1.0.64", "toml", ] @@ -1166,9 +1186,9 @@ dependencies = [ [[package]] name = "miette" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "backtrace", "backtrace-ext", @@ -1178,21 +1198,21 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size 0.3.0", + "terminal_size", "textwrap", - "thiserror", + "thiserror 1.0.64", "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1402,7 +1422,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1537,7 +1557,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1634,9 +1654,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1673,6 +1693,7 @@ dependencies = [ "r3bl_analytics_schema", "r3bl_ansi_color", "r3bl_core", + "r3bl_log", "r3bl_macro", "r3bl_tui", "r3bl_tuify", @@ -1682,7 +1703,7 @@ dependencies = [ "serde", "serde_json", "serial_test", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", "tracing-appender", @@ -1713,7 +1734,6 @@ dependencies = [ name = "r3bl_core" version = "0.10.0" dependencies = [ - "assert_cmd", "async-stream", "bincode", "chrono", @@ -1736,8 +1756,7 @@ dependencies = [ "strip-ansi", "strum", "strum_macros", - "tempfile", - "thiserror", + "thiserror 1.0.64", "time", "tokio", "tracing", @@ -1746,6 +1765,31 @@ dependencies = [ "tracing-subscriber", "unicode-segmentation", "unicode-width 0.2.0", + "uuid", +] + +[[package]] +name = "r3bl_log" +version = "0.1.0" +dependencies = [ + "assert_cmd", + "chrono", + "const_format", + "crossterm", + "miette", + "pretty_assertions", + "r3bl_ansi_color", + "r3bl_core", + "r3bl_macro", + "r3bl_test_fixtures", + "serial_test", + "textwrap", + "thiserror 1.0.64", + "tokio", + "tracing", + "tracing-appender", + "tracing-core", + "tracing-subscriber", ] [[package]] @@ -1757,7 +1801,31 @@ dependencies = [ "proc-macro2", "quote", "r3bl_core", - "syn 2.0.82", + "syn 2.0.90", +] + +[[package]] +name = "r3bl_script" +version = "0.1.0" +dependencies = [ + "chrono", + "crossterm", + "futures-util", + "miette", + "r3bl_ansi_color", + "r3bl_core", + "r3bl_macro", + "reqwest", + "serde_json", + "serial_test", + "strum", + "strum_macros", + "textwrap", + "thiserror 2.0.7", + "tokio", + "tracing", + "tracing-core", + "tracing-subscriber", ] [[package]] @@ -1772,12 +1840,13 @@ dependencies = [ "pretty_assertions", "r3bl_ansi_color", "r3bl_core", + "r3bl_log", "r3bl_test_fixtures", "r3bl_tui", "r3bl_tuify", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", "tracing-appender", @@ -1801,7 +1870,7 @@ dependencies = [ "strip-ansi-escapes", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.64", "tokio", "tokio-test", "tracing", @@ -1815,6 +1884,7 @@ name = "r3bl_tui" version = "0.6.0" dependencies = [ "chrono", + "const_format", "copypasta-ext", "crossterm", "futures-util", @@ -1823,6 +1893,7 @@ dependencies = [ "pretty_assertions", "r3bl_ansi_color", "r3bl_core", + "r3bl_log", "r3bl_macro", "r3bl_terminal_async", "r3bl_test_fixtures", @@ -1835,7 +1906,7 @@ dependencies = [ "strum_macros", "syntect", "textwrap", - "thiserror", + "thiserror 1.0.64", "tokio", "tracing", "tracing-appender", @@ -1854,6 +1925,7 @@ dependencies = [ "pretty_assertions", "r3bl_ansi_color", "r3bl_core", + "r3bl_log", "reedline", "serde", "serde_json", @@ -1921,7 +1993,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.64", ] [[package]] @@ -1939,7 +2011,7 @@ dependencies = [ "strip-ansi-escapes", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.64", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -1975,9 +2047,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64", "bytes", @@ -2052,9 +2124,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "rustls-pki-types", @@ -2186,14 +2258,14 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -2215,9 +2287,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ "futures", "log", @@ -2229,13 +2301,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2441,7 +2513,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2484,9 +2556,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.82" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2519,7 +2591,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "thiserror", + "thiserror 1.0.64", "walkdir", "yaml-rust", ] @@ -2558,16 +2630,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "terminal_size" version = "0.4.0" @@ -2602,7 +2664,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2613,7 +2675,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "test-case-core", ] @@ -2634,7 +2696,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +dependencies = [ + "thiserror-impl 2.0.7", ] [[package]] @@ -2645,7 +2716,18 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] @@ -2706,9 +2788,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -2731,7 +2813,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -2746,12 +2828,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -2809,9 +2890,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2825,27 +2906,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.64", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2864,9 +2945,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term 0.46.0", "sharded-slab", @@ -2933,6 +3014,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "untrusted" version = "0.9.0" @@ -2975,7 +3062,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -3072,7 +3159,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -3106,7 +3193,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3515,7 +3602,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 275291dee..c7fc6adf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ members = [ "test_fixtures", "tui", "tuify", + "log", + "script", ] # Make sure to keep these in sync with `run` nushell script `workspace_folders`. resolver = "2" diff --git a/cmdr/Cargo.toml b/cmdr/Cargo.toml index 6f1f949b8..d3ee1ef32 100644 --- a/cmdr/Cargo.toml +++ b/cmdr/Cargo.toml @@ -43,12 +43,13 @@ path = "src/lib.rs" [dependencies] # R3BL crates (from this mono repo). -r3bl_ansi_color = { path = "../ansi_color", version = "0.7.0" } # version is requried to publish to crates.io -r3bl_core = { path = "../core", version = "0.10.0" } # version is requried to publish to crates.io -r3bl_macro = { path = "../macro", version = "0.10.0" } # version is requried to publish to crates.io -r3bl_tui = { path = "../tui", version = "0.6.0" } # version is requried to publish to crates.io -r3bl_tuify = { path = "../tuify", version = "0.2.0" } # version is requried to publish to crates.io -r3bl_analytics_schema = { path = "../analytics_schema", version = "0.0.2" } # version is requried to publish to crates.io +r3bl_ansi_color = { path = "../ansi_color", version = "0.7.0" } # version is required to publish to crates.io +r3bl_core = { path = "../core", version = "0.10.0" } # version is required to publish to crates.io +r3bl_macro = { path = "../macro", version = "0.10.0" } # version is required to publish to crates.io +r3bl_tui = { path = "../tui", version = "0.6.0" } # version is required to publish to crates.io +r3bl_tuify = { path = "../tuify", version = "0.2.0" } # version is required to publish to crates.io +r3bl_analytics_schema = { path = "../analytics_schema", version = "0.0.2" } # version is required to publish to crates.io +r3bl_log = { path = "../log", version = "0.1.0" } # version is required to publish to crates.io # Reqwest (HTTP client). reqwest = { version = "0.12.8", features = ["json"] } diff --git a/cmdr/src/bin/edi.rs b/cmdr/src/bin/edi.rs index 386ee4645..f911ac224 100644 --- a/cmdr/src/bin/edi.rs +++ b/cmdr/src/bin/edi.rs @@ -22,12 +22,12 @@ use r3bl_ansi_color::{AnsiStyledText, Style}; use r3bl_cmdr::{edi::launcher, report_analytics, upgrade_check, AnalyticsAction}; use r3bl_core::{call_if_true, throws, - try_initialize_global_logging, ColorWheel, CommonResult, GradientGenerationPolicy, TextColorizationPolicy, UnicodeString}; +use r3bl_log::try_initialize_logging_global; use r3bl_tuify::{select_from_list, SelectionMode, StyleSheet, LIZARD_GREEN, SLATE_GRAY}; use crate::clap_config::CLIArg; @@ -42,7 +42,7 @@ async fn main() -> CommonResult<()> { // Start logging. let enable_logging = cli_arg.global_options.enable_logging; call_if_true!(enable_logging, { - try_initialize_global_logging(tracing_core::LevelFilter::DEBUG).ok(); + try_initialize_logging_global(tracing_core::LevelFilter::DEBUG).ok(); tracing::debug!("Start logging... cli_args {:?}", cli_arg); }); diff --git a/cmdr/src/bin/giti.rs b/cmdr/src/bin/giti.rs index 043423343..6ba1423b1 100644 --- a/cmdr/src/bin/giti.rs +++ b/cmdr/src/bin/giti.rs @@ -34,7 +34,8 @@ use r3bl_cmdr::{color_constants::DefaultColors::{FrozenBlue, GuardsRed, Moonligh report_analytics, upgrade_check, AnalyticsAction}; -use r3bl_core::{call_if_true, throws, try_initialize_global_logging, CommonResult}; +use r3bl_core::{call_if_true, throws, CommonResult}; +use r3bl_log::try_initialize_logging_global; use r3bl_tuify::{select_from_list_with_multi_line_header, SelectionMode, StyleSheet}; #[tokio::main] @@ -47,7 +48,7 @@ async fn main() -> CommonResult<()> { let enable_logging = cli_arg.global_options.enable_logging; call_if_true!(enable_logging, { - try_initialize_global_logging(tracing_core::LevelFilter::DEBUG).ok(); + try_initialize_logging_global(tracing_core::LevelFilter::DEBUG).ok(); tracing::debug!("Start logging... cli_args {:?}", cli_arg); }); diff --git a/core/Cargo.toml b/core/Cargo.toml index f53e794f4..aa8114fe5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -91,17 +91,11 @@ async-stream = "0.3.6" # Dynamically name variables in declarative macros. paste = "1.0.15" +# For SharedWriter ids. +uuid = { version = "1.11.0", features = ["v4"] } + [dev-dependencies] # for assert_eq! macro pretty_assertions = "1.4.1" serial_test = "3.1.1" - -# Testing - temp files and folders. -tempfile = "3.13.0" - -# Bin targets for testing stdout and stderr. -assert_cmd = "2.0.16" -[[bin]] -name = "tracing_test_bin" -path = "src/bin/tracing_test_bin.rs" diff --git a/core/src/common/mod.rs b/core/src/common/mod.rs index cd73afe8e..0f4677724 100644 --- a/core/src/common/mod.rs +++ b/core/src/common/mod.rs @@ -20,9 +20,13 @@ pub mod common_enums; pub mod common_math; pub mod common_result_and_error; pub mod miette_setup_global_report_handler; +pub mod ordered_map; +pub mod text_default_styles; // Re-export. pub use common_enums::*; pub use common_math::*; pub use common_result_and_error::*; pub use miette_setup_global_report_handler::*; +pub use ordered_map::*; +pub use text_default_styles::*; diff --git a/core/src/common/ordered_map.rs b/core/src/common/ordered_map.rs new file mode 100644 index 000000000..1c1b6cd9b --- /dev/null +++ b/core/src/common/ordered_map.rs @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 R3BL LLC + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::collections::HashMap; + +#[derive(Debug, Default)] +pub struct OrderedMap { + keys: Vec, + map: HashMap, +} + +impl OrderedMap { + pub fn new() -> Self { + OrderedMap { + keys: Vec::new(), + map: HashMap::new(), + } + } + + pub fn insert(&mut self, key: K, value: V) { + if !self.map.contains_key(&key) { + self.keys.push(key.clone()); + } + self.map.insert(key, value); + } + + pub fn get(&self, key: &K) -> Option<&V> { self.map.get(key) } + + pub fn iter(&self) -> impl Iterator { + self.keys + .iter() + .filter_map(move |key| self.map.get(key).map(|value| (key, value))) + } +} + +#[cfg(test)] +mod tests_ordered_map { + use super::*; + + #[test] + fn test_ordered_map_insert() { + let mut map = OrderedMap::new(); + map.insert("key2", "value2"); + map.insert("key1", "value1"); + map.insert("key3", "value3"); + + let mut iter = map.iter(); + assert_eq!(iter.next(), Some((&"key2", &"value2"))); + assert_eq!(iter.next(), Some((&"key1", &"value1"))); + assert_eq!(iter.next(), Some((&"key3", &"value3"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_ordered_map_delete() { + let mut map = OrderedMap::new(); + map.insert("key1", "value1"); + map.insert("key2", "value2"); + map.insert("key3", "value3"); + + // Delete a key and check if it is removed. + map.map.remove("key2"); + let mut iter = map.iter(); + assert_eq!(iter.next(), Some((&"key1", &"value1"))); + assert_eq!(iter.next(), Some((&"key3", &"value3"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_ordered_map_update() { + let mut map = OrderedMap::new(); + map.insert("key1", "value1"); + map.insert("key2", "value2"); + map.insert("key3", "value3"); + + // Update a value and check if it is updated. + map.insert("key2", "new_value2"); + let mut iter = map.iter(); + assert_eq!(iter.next(), Some((&"key1", &"value1"))); + assert_eq!(iter.next(), Some((&"key2", &"new_value2"))); + assert_eq!(iter.next(), Some((&"key3", &"value3"))); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_ordered_map_get() { + let mut map = OrderedMap::new(); + map.insert("key1", "value1"); + map.insert("key2", "value2"); + map.insert("key3", "value3"); + + assert_eq!(map.get(&"key1"), Some(&"value1")); + assert_eq!(map.get(&"key2"), Some(&"value2")); + assert_eq!(map.get(&"key3"), Some(&"value3")); + assert_eq!(map.get(&"key4"), None); + } + + #[test] + fn test_ordered_map_iter() { + let mut map = OrderedMap::new(); + map.insert("key1", "value1"); + map.insert("key2", "value2"); + map.insert("key3", "value3"); + + let mut iter = map.iter(); + assert_eq!(iter.next(), Some((&"key1", &"value1"))); + assert_eq!(iter.next(), Some((&"key2", &"value2"))); + assert_eq!(iter.next(), Some((&"key3", &"value3"))); + assert_eq!(iter.next(), None); + } +} diff --git a/core/src/logging/color_text_default_styles.rs b/core/src/common/text_default_styles.rs similarity index 100% rename from core/src/logging/color_text_default_styles.rs rename to core/src/common/text_default_styles.rs diff --git a/core/src/glyphs.rs b/core/src/glyphs.rs new file mode 100644 index 000000000..7dda1d68c --- /dev/null +++ b/core/src/glyphs.rs @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024 R3BL LLC + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! More info: +//! - [List of all symbols](https://symbl.cc/en/unicode-table/#miscellaneous-technical) +//! - [Box drawing characters](https://symbl.cc/en/unicode/blocks/box-drawing/) +//! - [Block element characters](https://symbl.cc/en/unicode/blocks/block-elements/) +//! - [Geometric shape characters](https://symbl.cc/en/unicode/blocks/geometric-shapes/) +//! - [Arrow characters](https://symbl.cc/en/unicode/blocks/arrows/) +//! - [Supplemental arrow characters-A](https://symbl.cc/en/unicode/blocks/supplemental-arrows-a/) +//! - [Supplemental arrow characters-B](https://symbl.cc/en/unicode/blocks/supplemental-arrows-b/) +//! - [Dingbat characters](https://symbl.cc/en/unicode/blocks/dingbats/) +//! - [Braille pattern characters](https://symbl.cc/en/unicode/blocks/braille-patterns/) +//! - [Miscellaneous symbol and arrow characters](https://symbl.cc/en/unicode/blocks/miscellaneous-symbols-and-arrows/) +//! - [Tifinagh characters](https://symbl.cc/en/unicode/blocks/tifinagh/) +//! - [Ideographic characters](https://symbl.cc/en/unicode/blocks/ideographic-description-characters/) + +// 01: [x] impl glyphs + +pub const SMILE_GLYPHS: &str = "(◕‿◕)"; +pub const TOP_UNDERLINE: char = '‾'; +pub const SPACER_GLYPH: char = ' '; +pub const ELLIPSIS: char = '…'; +pub const ERROR_GLYPH: char = '❌'; +pub const RENDER_GLYPH: char = '◧'; +pub const PAINT_GLYPH: char = '■'; +pub const LIGHT_CHECK_MARK_GLYPH: char = '🗸'; +pub const HEAVY_CHECK_MARK_GLYPH: char = '✓'; +pub const STATS_GLYPH: char = '♛'; +pub const CLOCK_TICK_GLYPH: char = '✲'; +pub const SHUTDOWN_GLYPH: char = '∎'; +pub const PAREN_LEFT_GLYPH: char = '❬'; +pub const PAREN_RIGHT_GLYPH: char = '❭'; +pub const FANCY_BULLET_GLYPH: char = '⮻'; +pub const CUT_GLYPH: char = '✀'; +pub const FOCUS_GLYPH: char = '⭆'; // '⬕'; //'◕'; +pub const DOT_GLYPH: char = '●'; +pub const POINTER_DOTTED_GLYPH: char = 'ⴾ'; +pub const GAME_CHAR_GLYPH: char = '𜱐'; +pub const TIRE_MARKS_GLYPH: char = '␩'; diff --git a/core/src/lib.rs b/core/src/lib.rs index 4676a211f..c4c60fc2c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -124,21 +124,19 @@ // Connect to source file. pub mod common; pub mod decl_macros; -pub mod logging; +pub mod glyphs; pub mod misc; pub mod storage; pub mod term; pub mod terminal_io; -pub mod tracing_logging; pub mod tui_core; // Re-export. pub use common::*; pub use decl_macros::*; -pub use logging::*; +pub use glyphs::*; pub use misc::*; pub use storage::*; pub use term::*; pub use terminal_io::*; -pub use tracing_logging::*; pub use tui_core::*; diff --git a/core/src/logging/logging_api.rs b/core/src/logging/logging_api.rs deleted file mode 100644 index cdd962dbb..000000000 --- a/core/src/logging/logging_api.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 R3BL LLC - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -//! This is just a shim (thin wrapper) around the [crate::tracing_logging] module. -//! -//! You can use the functions in this module or just use the [mod@crate::init_tracing] -//! functions directly, along with using [tracing::info!], [tracing::debug!], etc. macros. -//! -//! This file is here as a convenience for backward compatibility w/ the old logging -//! system. - -use crate::{ok, TracingConfig, WriterConfig}; - -const LOG_FILE_NAME: &str = "log.txt"; - -/// Logging is **DISABLED** by **default**. -/// -/// If you don't call this function w/ a value other than -/// [tracing_core::LevelFilter::OFF], then logging won't be enabled. It won't matter if -/// you call any of the other logging functions in this module, or directly use the -/// [tracing::info!], [tracing::debug!], etc. macros. -/// -/// This is a convenience method to setup Tokio [`tracing_subscriber`] with `stdout` as -/// the output destination. This method also ensures that the [`crate::SharedWriter`] is -/// used for concurrent writes to `stdout`. You can also use the [`TracingConfig`] struct -/// to customize the behavior of the tracing setup, by choosing whether to display output -/// to `stdout`, `stderr`, or a [`crate::SharedWriter`]. By default, both display and file -/// logging are enabled. You can also customize the log level, and the file path and -/// prefix for the log file. -pub fn try_initialize_global_logging( - level_filter: tracing_core::LevelFilter, -) -> miette::Result<()> { - // Early return if the level filter is off. - if matches!(level_filter, tracing_core::LevelFilter::OFF) { - return ok!(); - } - - // Try to initialize the tracing system w/ (rolling) file log output. - TracingConfig { - level_filter, - writer_config: WriterConfig::File(LOG_FILE_NAME.to_string()), - } - .install_global()?; - - ok!() -} diff --git a/core/src/logging/simple_file_logging_impl.rs b/core/src/logging/simple_file_logging_impl.rs deleted file mode 100644 index 11dd95e3f..000000000 --- a/core/src/logging/simple_file_logging_impl.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2024 R3BL LLC - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -use std::{fs::OpenOptions, io::Write, path::Path}; - -/// This is a simple function that logs a message to a file. This is meant to be used when -/// there are no other logging facilities available. -/// -/// # Arguments -/// * `file_path` - The path to the file to log to. If `None`, the default path is `debug.log`. -/// * `message` - The message to log. -pub fn file_log(file_path: Option<&Path>, message: &str) { - let file_path = file_path.unwrap_or(Path::new("debug.log")); - let message = if message.ends_with('\n') { - message.to_string() - } else { - format!("{}\n", message) - }; - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(file_path) - .unwrap(); - file.write_all(message.as_bytes()).unwrap(); -} diff --git a/core/src/misc/mod.rs b/core/src/misc/mod.rs index beb4899f8..3546c4f2d 100644 --- a/core/src/misc/mod.rs +++ b/core/src/misc/mod.rs @@ -18,7 +18,11 @@ // Attach sources. pub mod calc_str_len; pub mod friendly_random_id; +pub mod string_helpers; +pub mod temp_dir; // Re-export. pub use calc_str_len::*; pub use friendly_random_id::*; +pub use string_helpers::*; +pub use temp_dir::*; diff --git a/core/src/misc/string_helpers.rs b/core/src/misc/string_helpers.rs new file mode 100644 index 000000000..82da58f32 --- /dev/null +++ b/core/src/misc/string_helpers.rs @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2024 R3BL LLC + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::{ch, ChUnit, UnicodeString, ELLIPSIS, SPACER}; + +/// Tests whether the given text contains an ANSI escape sequence. +pub fn contains_ansi_escape_sequence(text: &str) -> bool { + text.chars().any(|it| it == '\x1b') +} + +#[test] +fn test_contains_ansi_escape_sequence() { + use r3bl_ansi_color::{AnsiStyledText, Color, Style}; + + use crate::assert_eq2; + + assert_eq2!( + contains_ansi_escape_sequence( + "\x1b[31mThis is red text.\x1b[0m And this is normal text." + ), + true + ); + + assert_eq2!(contains_ansi_escape_sequence("This is normal text."), false); + + assert_eq2!( + contains_ansi_escape_sequence( + &AnsiStyledText { + text: "Print a formatted (bold, italic, underline) string w/ ANSI color codes.", + style: &[ + Style::Bold, + Style::Italic, + Style::Underline, + Style::Foreground(Color::Rgb(50, 50, 50)), + Style::Background(Color::Rgb(100, 200, 1)), + ], + } + .to_string() + ), + true + ); +} + +/// Replace escaped quotes with unescaped quotes. The escaped quotes are generated +/// when [std::fmt::Debug] is used to format the output using [format!], eg: +/// ``` +/// use r3bl_core::remove_escaped_quotes; +/// +/// let s = format!("{:?}", "Hello\", world!"); +/// assert_eq!(s, "\"Hello\\\", world!\""); +/// let s = remove_escaped_quotes(&s); +/// assert_eq!(s, "Hello, world!"); +/// ``` +pub fn remove_escaped_quotes(s: &str) -> String { + s.replace("\\\"", "\"").replace("\"", "") +} + +/// Take into account the fact that there maybe emoji in the string. +pub fn truncate_from_right( + string: &str, + display_width: impl Into, + pad: bool, +) -> String { + let display_width = display_width.into(); + let string = UnicodeString::from(string); + let string_display_width = string.display_width; + + // Handle truncation. + if string_display_width > display_width { + let suffix = UnicodeString::from(ELLIPSIS.to_string()); + let suffix_display_width = suffix.display_width; + + let trunc_string = string.truncate_to_fit_size(crate::size!( + col_count: display_width - suffix_display_width, + row_count: 1 + )); + // The above statement is Equivalent to: + // let trunc_string = + // string.truncate_end_by_n_col(display_width - suffix_display_width - 1); + + format!("{}{}", trunc_string, suffix.string) + } + // Handle padding. + else if pad { + let mut padded_string = string.to_string(); + let display_width_to_pad = ch!(@to_usize display_width - string_display_width); + let display_width_to_pad = display_width_to_pad as f64; + let spacer_display_width = + ch!(@to_usize UnicodeString::from(SPACER.to_string()).display_width); + let spacer_display_width = spacer_display_width as f64; + let repeat_count = (display_width_to_pad / spacer_display_width).ceil() as usize; + padded_string.push_str(&SPACER.repeat(repeat_count)); + padded_string + } + // No post processing needed. + else { + string.to_string() + } +} + +pub fn truncate_from_left(text: &str, display_width: usize, pad: bool) -> String { + let display_width = ch!(display_width); + let text = UnicodeString::from(text); + let text_width = text.display_width; + + if text_width > display_width { + let suffix = UnicodeString::from(ELLIPSIS.to_string()); + let suffix_width = suffix.display_width; + + let truncate_cols_from_left = text_width - display_width; + let truncated_text = + text.truncate_start_by_n_col(truncate_cols_from_left + suffix_width); + + format!("{}{}", suffix.string, truncated_text) + } else if pad { + let mut padded_text = text.to_string(); + let width_to_pad = ch!(@to_usize display_width - text_width); + let spacer_width = ch!(@to_usize UnicodeString::from(SPACER).display_width); + let repeat_count = (width_to_pad as f64 / spacer_width as f64).ceil() as usize; + padded_text.insert_str(0, &SPACER.repeat(repeat_count)); + padded_text + } else { + text.to_string() + } +} + +#[cfg(test)] +mod tests_truncate_or_pad { + use super::*; + + #[test] + fn test_truncate_or_pad_from_right() { + let long_string = "Hello, world!"; + let short_string = "Hi!"; + let width = 10; + + assert_eq!(truncate_from_right(long_string, width, true), "Hello, wo…"); + assert_eq!(truncate_from_right(short_string, width, true), "Hi! "); + + assert_eq!(truncate_from_right(long_string, width, false), "Hello, wo…"); + assert_eq!(truncate_from_right(short_string, width, false), "Hi!"); + } + + #[test] + fn test_truncate_or_pad_from_left() { + let long_string = "Hello, world!"; + let short_string = "Hi!"; + let width = 10; + + assert_eq!(truncate_from_left(long_string, width, true), "…o, world!"); + assert_eq!(truncate_from_left(short_string, width, true), " Hi!"); + + assert_eq!(truncate_from_left(long_string, width, false), "…o, world!"); + assert_eq!(truncate_from_left(short_string, width, false), "Hi!"); + } +} diff --git a/test_fixtures/src/temp_dir.rs b/core/src/misc/temp_dir.rs similarity index 79% rename from test_fixtures/src/temp_dir.rs rename to core/src/misc/temp_dir.rs index fd0418690..5c62eb1ab 100644 --- a/test_fixtures/src/temp_dir.rs +++ b/core/src/misc/temp_dir.rs @@ -20,12 +20,20 @@ use std::{fmt::{Display, Formatter}, path::Path}; use miette::IntoDiagnostic; -use r3bl_core::friendly_random_id; + +use crate::friendly_random_id; pub struct TempDir { inner: std::path::PathBuf, } +impl TempDir { + /// Join a path to the temporary directory. + pub fn join>(&self, path: P) -> std::path::PathBuf { + self.inner.join(path) + } +} + /// Create a temporary directory. The directory is automatically deleted when the /// [TempDir] struct is dropped. pub fn create_temp_dir() -> miette::Result { @@ -51,7 +59,7 @@ impl Drop for TempDir { /// # Example /// /// ```no_run -/// use r3bl_test_fixtures::create_temp_dir; +/// use r3bl_core::create_temp_dir; /// let root = create_temp_dir().unwrap(); /// let new_dir = root.join("test_set_file_executable"); /// ``` @@ -68,7 +76,7 @@ impl Deref for TempDir { /// # Example /// /// ```no_run -/// use r3bl_test_fixtures::create_temp_dir; +/// use r3bl_core::create_temp_dir; /// let root = create_temp_dir().unwrap(); /// println!("Temp dir: {}", root); /// ``` @@ -88,7 +96,7 @@ impl Display for TempDir { /// # Example /// /// ```no_run -/// use r3bl_test_fixtures::create_temp_dir; +/// use r3bl_core::create_temp_dir; /// let root = create_temp_dir().unwrap(); /// std::fs::create_dir_all(root.join("test_set_file_executable")).unwrap(); /// std::fs::remove_dir_all(root).unwrap(); @@ -98,7 +106,7 @@ impl AsRef for TempDir { } #[cfg(test)] -mod tests { +mod tests_temp_dir { use crossterm::style::Stylize as _; use super::*; @@ -114,6 +122,22 @@ mod tests { assert!(temp_dir.inner.exists()); } + #[test] + fn test_temp_dir_join() { + let temp_dir = create_temp_dir().unwrap(); + let expected_prefix = temp_dir.inner.display().to_string(); + + let new_sub_dir = temp_dir.join("test_set_file_executable"); + let expected_postfix = new_sub_dir.display().to_string(); + + let expected_full_path = new_sub_dir.display().to_string(); + + assert!(temp_dir.exists()); + assert!(!new_sub_dir.exists()); + assert!(expected_full_path.starts_with(&expected_prefix)); + assert!(expected_full_path.ends_with(&expected_postfix)); + } + #[test] fn test_temp_dir_drop() { let temp_dir = create_temp_dir().unwrap(); diff --git a/core/src/storage/kv.rs b/core/src/storage/kv.rs index 3aa8206bc..e5e46ed08 100644 --- a/core/src/storage/kv.rs +++ b/core/src/storage/kv.rs @@ -353,19 +353,16 @@ use kv_error::*; #[cfg(test)] mod kv_tests { - use std::{collections::HashMap, - path::{Path, PathBuf}}; + use std::{collections::HashMap, path::Path}; use serial_test::serial; - use tempfile::tempdir; use tracing::{instrument, Level}; use super::*; + use crate::create_temp_dir; fn check_folder_exists(path: &Path) -> bool { path.exists() && path.is_dir() } - fn join_path_with_str(path: &Path, str: &str) -> PathBuf { path.join(str) } - fn setup_tracing() { let _ = tracing_subscriber::fmt() .with_max_level(Level::INFO) @@ -378,21 +375,13 @@ mod kv_tests { .try_init(); } - fn get_path(dir: &tempfile::TempDir, folder_name: &str) -> PathBuf { - join_path_with_str(dir.path(), folder_name) - } - - fn create_temp_folder() -> tempfile::TempDir { - tempdir().expect("Failed to create temp dir") - } - #[instrument] fn perform_db_operations() -> miette::Result<()> { let bucket_name = "bucket".to_string(); - // Setup temp folder. - let dir = create_temp_folder(); - let path_buf = get_path(&dir, "db_folder"); + // Setup temp dir (this will be dropped when `dir` is out of scope). + let root_temp_dir = create_temp_dir()?; + let path_buf = root_temp_dir.join("db_folder"); setup_tracing(); @@ -470,9 +459,9 @@ mod kv_tests { fn perform_db_operations_error_conditions() -> miette::Result<()> { let bucket_name = "bucket".to_string(); - // Setup temp folder. - let dir = create_temp_folder(); - let path_buf = get_path(&dir, "db_folder"); + // Setup temp dir (this will be dropped when `dir` is out of scope). + let root_temp_dir = create_temp_dir()?; + let path_buf = root_temp_dir.join("db_folder"); setup_tracing(); diff --git a/core/src/terminal_io/shared_writer.rs b/core/src/terminal_io/shared_writer.rs index 32f049ee9..e6bb71118 100644 --- a/core/src/terminal_io/shared_writer.rs +++ b/core/src/terminal_io/shared_writer.rs @@ -43,6 +43,7 @@ pub type Text = Vec; /// - `manage_shared_writer_output::flush_internal()`. /// /// If you want to output data without a newline, you can call [`SharedWriter::flush()`]. +#[derive(Debug)] pub struct SharedWriter { /// Holds the data to be written to the terminal. pub buffer: Text, @@ -56,6 +57,13 @@ pub struct SharedWriter { /// struct will report errors when [`std::io::Write::write()`] fails, due to the /// receiver end of the channel being closed. pub silent_error: bool, + + /// Unique identifier for the `SharedWriter` instance. + pub uuid: uuid::Uuid, +} + +impl PartialEq for SharedWriter { + fn eq(&self, other: &Self) -> bool { self.uuid == other.uuid } } /// Signals that can be sent to the `line` channel, which is monitored by the task. @@ -77,6 +85,7 @@ impl SharedWriter { buffer: Default::default(), line_state_control_channel_sender: line_sender, silent_error: false, + uuid: uuid::Uuid::new_v4(), } } } @@ -84,8 +93,8 @@ impl SharedWriter { /// Custom [Clone] implementation for [`SharedWriter`]. This ensures that each new /// instance gets its own buffer to write data into. And a [Clone] of the /// [Self::line_state_control_channel_sender], so all the [`LineStateControlSignal`]s end -/// up in the same `line` [tokio::sync::mpsc::channel] that lives in the -/// `Readline` instance (in the `r3bl_terminal_async` crate). +/// up in the same `line` [tokio::sync::mpsc::channel] that lives in the `Readline` +/// instance (in the `r3bl_terminal_async` crate). impl Clone for SharedWriter { fn clone(&self) -> Self { Self { @@ -94,6 +103,7 @@ impl Clone for SharedWriter { .line_state_control_channel_sender .clone(), silent_error: true, + uuid: self.uuid, } } } diff --git a/core/src/tui_core/graphemes/access.rs b/core/src/tui_core/graphemes/access.rs index 41359cceb..abb572074 100644 --- a/core/src/tui_core/graphemes/access.rs +++ b/core/src/tui_core/graphemes/access.rs @@ -52,11 +52,22 @@ impl UnicodeString { display_width } + /// The `size` is a column index and row index. Not width or height. + /// - To convert width -> size / column index subtract 1. + /// - To convert size / column index to width add 1. + /// + /// Note the [Self::truncate_end_by_n_col] and [Self::truncate_start_by_n_col] + /// functions take a width. pub fn truncate_to_fit_size(&self, size: Size) -> &str { let display_cols: ChUnit = size.col_count; self.truncate_end_to_fit_width(display_cols) } + /// The `n_display_col` is a width, not a [Size]. + /// - To convert width -> size / column index subtract 1. + /// - To convert size / column index to width add 1. + /// + /// Note the [Self::truncate_to_fit_size] function takes a size / column index. pub fn truncate_end_by_n_col(&self, n_display_col: ChUnit) -> &str { let mut countdown_col_count = n_display_col; let mut string_end_byte_index = 0; diff --git a/core/src/tui_core/graphemes/convert.rs b/core/src/tui_core/graphemes/convert.rs index 490fa7042..d0aec83e4 100644 --- a/core/src/tui_core/graphemes/convert.rs +++ b/core/src/tui_core/graphemes/convert.rs @@ -19,7 +19,7 @@ use std::borrow::Cow; use crate::UnicodeString; -// Convert to UnicodeString +/// Convert to UnicodeString. impl From<&str> for UnicodeString { fn from(s: &str) -> Self { UnicodeString::new(s) } } @@ -40,12 +40,12 @@ impl From<&String> for UnicodeString { fn from(s: &String) -> Self { UnicodeString::new(s) } } -// Convert to String +/// Convert to String. impl From for String { fn from(s: UnicodeString) -> Self { s.string } } -// UnicodeStringExt +/// UnicodeStringExt trait. pub trait UnicodeStringExt { fn unicode_string(&self) -> UnicodeString; } @@ -61,3 +61,10 @@ impl UnicodeStringExt for &str { impl UnicodeStringExt for String { fn unicode_string(&self) -> UnicodeString { UnicodeString::from(self) } } + +/// Implement Display trait. +impl std::fmt::Display for UnicodeString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.string) + } +} diff --git a/core/src/tui_core/tui_style/tui_style_impl.rs b/core/src/tui_core/tui_style/tui_style_impl.rs index 2af8e8303..9cd84f0f0 100644 --- a/core/src/tui_core/tui_style/tui_style_impl.rs +++ b/core/src/tui_core/tui_style/tui_style_impl.rs @@ -274,11 +274,11 @@ mod style_helpers { write!( f, - "Style {{ {} | fg: {:?} | bg: {:?} | padding: {:?} }}", - msg_vec.join(" + "), - self.color_fg, - self.color_bg, - *self.padding.unwrap_or_else(|| ch!(0)) + "Style {{ {attrs} | fg: {fg:?} | bg: {bg:?} | padding: {p:?} }}", + attrs = msg_vec.join(" + "), + fg = self.color_fg, + bg = self.color_bg, + p = *self.padding.unwrap_or_else(|| ch!(0)) ) } } diff --git a/log/Cargo.toml b/log/Cargo.toml new file mode 100644 index 000000000..49766174b --- /dev/null +++ b/log/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "r3bl_log" +version = "0.1.0" +edition = "2024" +resolver = "2" +description = "Tokio tracing plugins for formatted log output for R3BL TUI crates" +# At most 5 keywords w/ no spaces, each has max length of 20 char. +keywords = ["log", "tracing", "ANSI", "terminal", "formatted"] +categories = ["command-line-interface", "command-line-utilities"] +readme = "README.md" # This is not included in cargo docs. +# Email address(es) has to be verified at https://crates.io/me/ +authors = [ + "Nazmul Idris ", + "Nadia Idris ", +] +repository = "https://github.com/r3bl-org/r3bl-open-core/tree/main/log" +documentation = "https://docs.rs/r3bl_log" +homepage = "https://r3bl.com" +license = "Apache-2.0" + +[dependencies] +# Tokio / Tracing / Logging. +# https://tokio.rs/tokio/topics/tracing +# https://tokio.rs/tokio/topics/tracing-next-steps +tokio = { version = "1.40.0", features = ["full", "tracing"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +tracing-appender = "0.2.3" +tracing-core = "0.1.32" + +# CustomEventFormatter. +chrono = "0.4.39" +textwrap = { version = "0.16.1", features = ["unicode-linebreak"] } + +# Error handling. +thiserror = "1.0.64" +miette = { version = "7.2.0", features = ["fancy"] } +pretty_assertions = "1.4.1" + +# r3bl-open-core. +r3bl_ansi_color = { path = "../ansi_color", version = "0.7.0" } # Convert between ansi and rgb. +r3bl_core = { path = "../core", version = "0.10.0" } # Core functionality. +r3bl_macro = { path = "../macro", version = "0.10.0" } # Macros for r3bl-open-core. +r3bl_test_fixtures = { path = "../test_fixtures", version = "0.1.0" } # Test fixtures. + +# Terminal color output. +crossterm = "0.28.1" + +# Allow const strings to be formatted with format!. +const_format = "0.2.34" + +[dev-dependencies] + +# for assert_eq! macro +pretty_assertions = "1.4.1" +serial_test = "3.1.1" + +# Bin targets for testing stdout and stderr. +assert_cmd = "2.0.16" + +[[bin]] +name = "tracing_test_bin" +path = "src/bin/tracing_test_bin.rs" diff --git a/core/src/bin/tracing_test_bin.rs b/log/src/bin/tracing_test_bin.rs similarity index 69% rename from core/src/bin/tracing_test_bin.rs rename to log/src/bin/tracing_test_bin.rs index 12a0233a6..907d68360 100644 --- a/core/src/bin/tracing_test_bin.rs +++ b/log/src/bin/tracing_test_bin.rs @@ -15,16 +15,26 @@ * limitations under the License. */ -use r3bl_core::{DisplayPreference, TracingConfig, WriterConfig}; +use r3bl_log::{DisplayPreference, TracingConfig, WriterConfig}; use tracing_core::LevelFilter; -/// `assert_cmd` : +/// This test works with the binary under test, which is `tracing_stdout_test_bin`. That +/// binary takes 1 string argument: "stdout" or "stderr". It uses the `assert_cmd` crate +/// to verify that the [DisplayPreference::Stdout] and [DisplayPreference::Stderr] work as +/// expected. There is no easy way to actually test `stdout` and `stderr` without spawning +/// a new process, so this is the best way to test it. +/// +/// +/// This is the binary under test, which is tested by the `test_tracing_bin_stdio` test +/// module. /// -/// This is the binary under test, which is tested by the `test_tracing_stdout` test. /// It takes 1 argument: "stdout" or "stderr". Depending on the argument, it will /// display the logs to stdout or stderr. /// -/// See: `init_tracing.rs` and `test_tracing_bin_stdio()` test. +/// See: +/// 1. Test module: `test_tracing_bin_stdio` +/// 2. Binary under test: `tracing_test_bin.rs` <- you are here. +/// 3. `assert_cmd` : fn main() { // Get the argument passed to the binary. let arg = std::env::args().nth(1).unwrap_or_default(); diff --git a/core/src/logging/mod.rs b/log/src/lib.rs similarity index 72% rename from core/src/logging/mod.rs rename to log/src/lib.rs index 1ce0c7f0b..4afa5851f 100644 --- a/core/src/logging/mod.rs +++ b/log/src/lib.rs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 R3BL LLC + * Copyright (c) 2024 R3BL LLC * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,12 +15,8 @@ * limitations under the License. */ -// Attach. -pub mod color_text_default_styles; -pub mod logging_api; -pub mod simple_file_logging_impl; +// Attach sources. +pub mod log_support; // Re-export. -pub use color_text_default_styles::*; -pub use logging_api::*; -pub use simple_file_logging_impl::*; +pub use log_support::*; diff --git a/log/src/log_support/custom_event_formatter.rs b/log/src/log_support/custom_event_formatter.rs new file mode 100644 index 000000000..7e1735e2c --- /dev/null +++ b/log/src/log_support/custom_event_formatter.rs @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2024 R3BL LLC + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! The reason for the strange logic in the +//! [VisitEventAndPopulateOrderedMapWithFields::record_debug] function and the +//! [CustomEventFormatter::format_event] skipping empty field values (ie, empty body +//! lines) is that we wanted to be able to have a `message` field where a String can be +//! used instead of "stringify!" which just dumps the string literal. This does not allow +//! the message to be a variable, which means it can't be composed using other glyphs, +//! such as the ones from [r3bl_core::glyphs]. To get around this limitation, the +//! following logic was implemented. +//! +//! The tracing crate deals with records that have fields. Each field has a name and +//! value. The `message` field name is special and is automatically injected in cases +//! where a call to `info!`, `warn!`, `error!`, etc. only has 1 expression, eg: +//! `info!(foobar);`, `info!("foobar");` or `info!(format!("{}{}", "foo", "bar"));`. +//! +//! So in order to be able to create "dynamic" headings or field names, you have to +//! explicitly use the `message` field name. Its value can then be any expression. There +//! are lots of examples in the tests below. + +use std::fmt; + +use chrono::Local; +use const_format::formatcp; +use crossterm::style::Stylize; +use custom_event_formatter_constants::*; +use r3bl_ansi_color::{AnsiStyledText, Color, Style}; +use r3bl_core::{ColorWheel, + OrderedMap, + UnicodeString, + ch, + get_terminal_width, + glyphs, + remove_escaped_quotes, + truncate_from_right}; +use r3bl_macro::tui_style; +use textwrap::{Options, WordSeparator, wrap}; +use tracing::{Event, + Subscriber, + field::{Field, Visit}}; +use tracing_subscriber::{fmt::{FormatEvent, FormatFields}, + registry::LookupSpan}; + +pub struct CustomEventFormatter; + +// Colors: +pub mod custom_event_formatter_constants { + use super::*; + + pub const FIRST_LINE_PREFIX: &str = formatcp!( + "{sp}{ch}{sp}", + sp = glyphs::SPACER_GLYPH, + ch = glyphs::FANCY_BULLET_GLYPH + ); + pub const SUBSEQUENT_LINE_PREFIX: &str = formatcp!("{sp}", sp = glyphs::SPACER_GLYPH); + pub const LEVEL_SUFFIX: &str = ":"; + + pub const ERROR_SIGIL: &str = "E"; + pub const WARN_SIGIL: &str = "W"; + pub const INFO_SIGIL: &str = "I"; + pub const DEBUG_SIGIL: &str = "D"; + pub const TRACE_SIGIL: &str = "T"; + + pub const ENTRY_SEPARATOR_CHAR: &str = formatcp!("{ch}", ch = glyphs::TOP_UNDERLINE); + + pub const BODY_FG_COLOR: Color = Color::Rgb(175, 175, 175); + pub const BODY_FG_COLOR_BRIGHT: Color = Color::Rgb(200, 200, 200); + pub const HEADING_BG_COLOR: Color = Color::Rgb(70, 70, 90); + + pub const INFO_FG_COLOR: Color = Color::Rgb(233, 150, 122); + pub const ERROR_FG_COLOR: Color = Color::Rgb(255, 182, 193); //Color::Rgb(220, 92, 92); + pub const WARN_FG_COLOR: Color = Color::Rgb(255, 140, 0); + pub const DEBUG_FG_COLOR: Color = Color::Rgb(255, 255, 0); + pub const TRACE_FG_COLOR: Color = Color::Rgb(186, 85, 211); +} + +impl FormatEvent for CustomEventFormatter +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + /// Format the event into 2 lines: + /// 1. Timestamp, span context, level, and message truncated to the available visible + /// width. + /// 2. Body that is text wrapped to the visible width. + /// + /// This function takes into account text that can contain emoji. + /// + /// The reason for the strange logic in + /// [VisitEventAndPopulateOrderedMapWithFields::record_debug] and the + /// [CustomEventFormatter::format_event] skipping empty field value lines is that we + /// wanted to be able to have a `message` field where a String can be used instead of + /// "stringify!" which just dumps the string literal. This does not allow the message + /// to be a variable, which means it can't be composed using other glyphs, such as the + /// ones from [r3bl_core::glyphs]. To get around this limitation, the following logic + /// was implemented. + fn format_event( + &self, + ctx: &tracing_subscriber::fmt::FmtContext<'_, S, N>, + mut writer: tracing_subscriber::fmt::format::Writer<'_>, + event: &Event<'_>, + ) -> fmt::Result { + // Get spacer. + let spacer = r3bl_core::glyphs::SPACER_GLYPH; + let spacer_display_width = UnicodeString::from(spacer.to_string()).display_width; + + // Length accumulator (for line width calculations). + let mut line_width_used = ch!(0); + + // Custom timestamp. + let timestamp = Local::now(); + let timestamp_str = format!( + "{sp}{ts}{sp}", + ts = timestamp.format("%I:%M%P"), + sp = spacer + ); + line_width_used += UnicodeString::from(×tamp_str).display_width; + let timestamp_str_fmt = AnsiStyledText { + text: ×tamp_str, + style: &[ + Style::Foreground(BODY_FG_COLOR_BRIGHT), + Style::Background(HEADING_BG_COLOR), + ], + }; + write!(writer, "\n{timestamp_str_fmt}")?; + + // Custom span context. + if let Some(scope) = ctx.lookup_current() { + let scope_str = format!("[{}] ", scope.name()); + line_width_used += UnicodeString::from(&scope_str).display_width; + let scope_str_fmt = AnsiStyledText { + text: &scope_str, + style: &[ + Style::Foreground(BODY_FG_COLOR_BRIGHT), + Style::Background(HEADING_BG_COLOR), + Style::Italic, + ], + }; + write!(writer, "{scope_str_fmt}")?; + } + + // Custom metadata formatting. For eg: + // + // metadata: Metadata { + // name: "event src/bin/gen-certs.rs:110", + // target: "gen_certs", + // level: Level( + // Debug, + // ), + // module_path: "gen_certs", + // location: src/bin/gen-certs.rs:110, + // fields: {msg, body}, + // callsite: Identifier(0x5a46fb928d40), + // kind: Kind(EVENT), + // } + let mut style_acc: Vec