diff --git a/Cargo.lock b/Cargo.lock index 6ea2d48..232a617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,5 +7,5 @@ name = "containers" version = "0.1.0" [[package]] -name = "log" +name = "mw_log_fmt" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 34c94b2..22653f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,15 +2,9 @@ resolver = "2" # Split to default members without tests and examples. # Used when executing cargo from project root. -default-members = [ - "src/containers", - "src/log" -] +default-members = ["src/containers", "src/log/mw_log_fmt"] # Include tests and examples as a member for IDE support and Bazel builds. -members = [ - "src/containers", - "src/log" -] +members = ["src/containers", "src/log/mw_log_fmt"] [workspace.package] @@ -21,6 +15,7 @@ authors = ["S-CORE Contributors"] [workspace.dependencies] +mw_log_fmt = { path = "src/log/mw_log_fmt" } [workspace.lints.clippy] @@ -30,3 +25,4 @@ alloc_instead_of_core = "warn" [workspace.lints.rust] unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] } +missing_docs = "warn" diff --git a/src/log/BUILD b/src/log/BUILD index aa991bd..e69de29 100644 --- a/src/log/BUILD +++ b/src/log/BUILD @@ -1,24 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") - -rust_library( - name = "log", - srcs = glob(["src/**/*.rs"]), - visibility = ["//visibility:public"], -) - -rust_test( - name = "log_tests", - crate = ":log", -) diff --git a/src/log/Cargo.toml b/src/log/Cargo.toml deleted file mode 100644 index b5403bb..0000000 --- a/src/log/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "log" -version.workspace = true -edition.workspace = true -license-file.workspace = true -authors.workspace = true - -[dependencies] - -[lints] -workspace = true diff --git a/src/log/mw_log_fmt/BUILD b/src/log/mw_log_fmt/BUILD new file mode 100644 index 0000000..ba40e92 --- /dev/null +++ b/src/log/mw_log_fmt/BUILD @@ -0,0 +1,30 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "mw_log_fmt", + srcs = glob(["**/*.rs"]), + crate_features = ["std"], + # TODO: expose required interface through `mw_log`. + visibility = ["//visibility:public"], +) + +rust_test( + name = "tests", + crate = "mw_log_fmt", + tags = [ + "unit_tests", + "ut", + ], +) diff --git a/src/log/mw_log_fmt/Cargo.toml b/src/log/mw_log_fmt/Cargo.toml new file mode 100644 index 0000000..13cab94 --- /dev/null +++ b/src/log/mw_log_fmt/Cargo.toml @@ -0,0 +1,28 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +[package] +name = "mw_log_fmt" +version.workspace = true +authors.workspace = true +readme.workspace = true +edition.workspace = true + +[lib] +path = "lib.rs" + +[features] +std = [] + +[lints] +workspace = true diff --git a/src/log/mw_log_fmt/fmt.rs b/src/log/mw_log_fmt/fmt.rs new file mode 100644 index 0000000..c516cf9 --- /dev/null +++ b/src/log/mw_log_fmt/fmt.rs @@ -0,0 +1,288 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +use crate::FormatSpec; +use core::marker::PhantomData; +use core::ptr::NonNull; + +/// The type returned by writer methods. +pub type Result = core::result::Result<(), Error>; + +/// The type of the writer. +pub type Writer<'a> = &'a mut dyn ScoreWrite; + +/// The error type which is returned from writing a message. +/// +/// This type does not support transmission of an error other than an error occurred. +/// This is because, despite the existence of this error, writing is considered an infallible operation. +/// `fmt()` implementors should not return this `Error` unless the received it from their [`ScoreWrite`] implementation. +#[derive(Copy, Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Error; + +/// A trait for writing into message frames. +/// +/// This trait accepts multiple data types. +/// Implementation is responsible for output formatting based on provided spec. +pub trait ScoreWrite { + /// Write a `bool` into this writer. + fn write_bool(&mut self, v: &bool, spec: &FormatSpec) -> Result; + /// Write a `f32` into this writer. + fn write_f32(&mut self, v: &f32, spec: &FormatSpec) -> Result; + /// Write a `f64` into this writer. + fn write_f64(&mut self, v: &f64, spec: &FormatSpec) -> Result; + /// Write a `i8` into this writer. + fn write_i8(&mut self, v: &i8, spec: &FormatSpec) -> Result; + /// Write a `i16` into this writer. + fn write_i16(&mut self, v: &i16, spec: &FormatSpec) -> Result; + /// Write a `i32` into this writer. + fn write_i32(&mut self, v: &i32, spec: &FormatSpec) -> Result; + /// Write a `i64` into this writer. + fn write_i64(&mut self, v: &i64, spec: &FormatSpec) -> Result; + /// Write a `u8` into this writer. + fn write_u8(&mut self, v: &u8, spec: &FormatSpec) -> Result; + /// Write a `u16` into this writer. + fn write_u16(&mut self, v: &u16, spec: &FormatSpec) -> Result; + /// Write a `u32` into this writer. + fn write_u32(&mut self, v: &u32, spec: &FormatSpec) -> Result; + /// Write a `u64` into this writer. + fn write_u64(&mut self, v: &u64, spec: &FormatSpec) -> Result; + /// Write a `&str` into this writer. + fn write_str(&mut self, v: &str, spec: &FormatSpec) -> Result; +} + +/// Data placeholder in message. +pub struct Placeholder<'a> { + value: NonNull<()>, + formatter: fn(NonNull<()>, Writer, &FormatSpec) -> Result, + spec: FormatSpec, + _lifetime: PhantomData<&'a ()>, +} + +impl<'a> Placeholder<'a> { + /// Create the placeholder to be represented using `ScoreDebug`. + pub const fn new_debug(value: &'a T, spec: FormatSpec) -> Self { + let value = NonNull::from_ref(value).cast(); + let formatter = |v: NonNull<()>, f: Writer, spec: &FormatSpec| { + // Borrow checker will ensure that value won't be mutated for as long as the returned `Self` instance is alive. + let typed = unsafe { v.cast::().as_ref() }; + typed.fmt(f, spec) + }; + Self { + value, + formatter, + spec, + _lifetime: PhantomData, + } + } + + /// Create the placeholder to be represented using `ScoreDisplay`. + pub const fn new_display(value: &'a T, spec: FormatSpec) -> Self { + let value = NonNull::from_ref(value).cast(); + let formatter = |v: NonNull<()>, f: Writer, spec: &FormatSpec| { + // Borrow checker will ensure that value won't be mutated for as long as the returned `Self` instance is alive. + let typed = unsafe { v.cast::().as_ref() }; + typed.fmt(f, spec) + }; + Self { + value, + formatter, + spec, + _lifetime: PhantomData, + } + } + + /// Get format spec of this placeholder. + pub fn format_spec(&self) -> &FormatSpec { + &self.spec + } + + /// Write requested representation of data to the provided writer. + pub fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + (self.formatter)(self.value, f, spec) + } +} + +/// Message fragment. +/// A string literal or data placeholder. +pub enum Fragment<'a> { + /// Fragment is a string literal, with no additional formatting. + Literal(&'a str), + /// Fragment is a placeholder for provided data. + Placeholder(Placeholder<'a>), +} + +/// Array of message parts. +/// Consists of [`Fragment`] entities. +#[derive(Copy, Clone)] +pub struct Arguments<'a>(pub &'a [Fragment<'a>]); + +impl ScoreDebug for Arguments<'_> { + fn fmt(&self, f: Writer, _spec: &FormatSpec) -> Result { + write(f, *self) + } +} + +impl ScoreDisplay for Arguments<'_> { + fn fmt(&self, f: Writer, _spec: &FormatSpec) -> Result { + write(f, *self) + } +} + +/// `ScoreDebug` provides the output in a programmer-facing, debugging context. +/// Replacement for [`core::fmt::Debug`]. +pub trait ScoreDebug { + /// Write debug representation of `self` to the provided writer. + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result; +} + +/// `ScoreDisplay` provides the output in a user-facing context. +/// Replacement for [`core::fmt::Display`]. +pub trait ScoreDisplay { + /// Write display representation of `self` to the provided writer. + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result; +} + +/// Write [`Arguments`] into provided `output` writer. +/// +/// The arguments will be formatted according to provided format spec. +pub fn write(output: Writer, args: Arguments<'_>) -> Result { + for fragment in args.0 { + match fragment { + Fragment::Literal(s) => output.write_str(s, &FormatSpec::new()), + Fragment::Placeholder(ph) => ph.fmt(output, &ph.spec), + }?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::test_utils::StringWriter; + use crate::{write, Arguments, FormatSpec, Fragment, Placeholder, ScoreDebug, ScoreDisplay}; + + #[test] + fn test_arguments_display() { + let mut w = StringWriter::new(); + let fragments = [ + Fragment::Literal("test_"), + Fragment::Placeholder(Placeholder::new_display(&true, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123.4f32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&432.2f64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-100i8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1234i16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-123456i32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1200000000000000000i64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123u8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1234u16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123456u32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1200000000000000000u64, FormatSpec::new())), + Fragment::Literal("_string"), + ]; + let args = Arguments(&fragments); + + let result = ScoreDisplay::fmt(&args, &mut w, &FormatSpec::new()); + assert!(result == Ok(())); + assert!(w.get() == "test_true123.4432.2-100-1234-123456-120000000000000000012312341234561200000000000000000_string") + } + + #[test] + fn test_arguments_debug() { + let mut w = StringWriter::new(); + let fragments = [ + Fragment::Literal("test_"), + Fragment::Placeholder(Placeholder::new_display(&true, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123.4f32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&432.2f64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-100i8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1234i16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-123456i32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1200000000000000000i64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123u8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1234u16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123456u32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1200000000000000000u64, FormatSpec::new())), + Fragment::Literal("_string"), + ]; + let args = Arguments(&fragments); + + let result = ScoreDebug::fmt(&args, &mut w, &FormatSpec::new()); + assert!(result == Ok(())); + assert!(w.get() == "test_true123.4432.2-100-1234-123456-120000000000000000012312341234561200000000000000000_string") + } + + #[test] + fn test_write_empty() { + let mut w = StringWriter::new(); + let args = Arguments(&[]); + assert!(write(&mut w, args) == Ok(())); + } + + #[test] + fn test_write_literals_only() { + let mut w = StringWriter::new(); + let args = Arguments(&[Fragment::Literal("test_"), Fragment::Literal("string")]); + assert!(write(&mut w, args) == Ok(())); + assert!(w.get() == "test_string"); + } + + #[test] + fn test_write_placeholders_only() { + let mut w = StringWriter::new(); + let fragments = [ + Fragment::Placeholder(Placeholder::new_display(&true, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123.4f32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&432.2f64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-100i8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1234i16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-123456i32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&-1200000000000000000i64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123u8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1234u16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&123456u32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&1200000000000000000u64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_display(&"test", FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&true, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&123.4f32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&432.2f64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&-100i8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&-1234i16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&-123456i32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&-1200000000000000000i64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&123u8, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&1234u16, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&123456u32, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&1200000000000000000u64, FormatSpec::new())), + Fragment::Placeholder(Placeholder::new_debug(&"test", FormatSpec::new())), + ]; + let args = Arguments(&fragments); + assert!(write(&mut w, args) == Ok(())); + + let exp_display_pattern = "true123.4432.2-100-1234-123456-120000000000000000012312341234561200000000000000000test"; + let exp_debug_pattern = "true123.4432.2-100-1234-123456-120000000000000000012312341234561200000000000000000\"test\""; + assert!(w.get() == format!("{exp_display_pattern}{exp_debug_pattern}")); + } + + #[test] + fn test_write_mixed() { + let mut w = StringWriter::new(); + let fragments = [ + Fragment::Literal("test_"), + Fragment::Placeholder(Placeholder::new_display(&123i8, FormatSpec::new())), + Fragment::Literal("_string"), + ]; + let args = Arguments(&fragments); + assert!(write(&mut w, args) == Ok(())); + assert!(w.get() == "test_123_string"); + } +} diff --git a/src/log/mw_log_fmt/fmt_impl.rs b/src/log/mw_log_fmt/fmt_impl.rs new file mode 100644 index 0000000..d24a3e6 --- /dev/null +++ b/src/log/mw_log_fmt/fmt_impl.rs @@ -0,0 +1,218 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Implementation of `ScoreDisplay` and `ScoreDebug` for basic types. + +use crate::fmt; +use crate::fmt::*; +use crate::fmt_spec::FormatSpec; + +macro_rules! impl_fmt_for_t { + ($t:ty, $fn:ident, $($fmt:ident),*) => { + $( + impl $fmt for $t { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.$fn(self, spec) + } + } + )* + }; +} + +impl_fmt_for_t!(bool, write_bool, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(f32, write_f32, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(f64, write_f64, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(i8, write_i8, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(i16, write_i16, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(i32, write_i32, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(i64, write_i64, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(u8, write_u8, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(u16, write_u16, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(u32, write_u32, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(u64, write_u64, ScoreDebug, ScoreDisplay); +impl_fmt_for_t!(&str, write_str, ScoreDisplay); + +impl ScoreDebug for &str { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.write_str("\"", spec)?; + f.write_str(self, spec)?; + f.write_str("\"", spec) + } +} + +macro_rules! impl_fmt_for_t_casted { + ($ti:ty, $to:ty, $fn:ident, $($fmt:ident),*) => { + $( + impl $fmt for $ti { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + let casted = <$to>::try_from(*self).map_err(|_| fmt::Error)?; + f.$fn(&casted, spec) + } + } + )* + }; +} + +#[cfg(target_pointer_width = "32")] +impl_fmt_for_t_casted!(isize, i32, write_i32, ScoreDebug, ScoreDisplay); +#[cfg(target_pointer_width = "64")] +impl_fmt_for_t_casted!(isize, i64, write_i64, ScoreDebug, ScoreDisplay); +#[cfg(target_pointer_width = "32")] +impl_fmt_for_t_casted!(usize, u32, write_u32, ScoreDebug, ScoreDisplay); +#[cfg(target_pointer_width = "64")] +impl_fmt_for_t_casted!(usize, u64, write_u64, ScoreDebug, ScoreDisplay); + +#[cfg(test)] +mod tests { + use crate::test_utils::{common_test_debug, common_test_display}; + + #[test] + fn test_bool_display() { + common_test_display(true); + } + + #[test] + fn test_bool_debug() { + common_test_debug(true); + } + + #[test] + fn test_f32_display() { + common_test_display(123.4f32); + } + + #[test] + fn test_f32_debug() { + common_test_debug(123.4f32); + } + + #[test] + fn test_f64_display() { + common_test_display(123.4f64); + } + + #[test] + fn test_f64_debug() { + common_test_debug(123.4f64); + } + + #[test] + fn test_i8_display() { + common_test_display(-123i8); + } + + #[test] + fn test_i8_debug() { + common_test_debug(-123i8); + } + + #[test] + fn test_i16_display() { + common_test_display(-1234i16); + } + + #[test] + fn test_i16_debug() { + common_test_debug(-1234i16); + } + + #[test] + fn test_i32_display() { + common_test_display(-123456i32); + } + + #[test] + fn test_i32_debug() { + common_test_debug(-123456i32); + } + + #[test] + fn test_i64_display() { + common_test_display(-1200000000000000000i64); + } + + #[test] + fn test_i64_debug() { + common_test_debug(-1200000000000000000i64); + } + + #[test] + fn test_u8_display() { + common_test_display(123u8); + } + + #[test] + fn test_u8_debug() { + common_test_debug(123u8); + } + + #[test] + fn test_u16_display() { + common_test_display(1234u16); + } + + #[test] + fn test_u16_debug() { + common_test_debug(1234u16); + } + + #[test] + fn test_u32_display() { + common_test_display(123456u32); + } + + #[test] + fn test_u32_debug() { + common_test_debug(123456u32); + } + + #[test] + fn test_u64_display() { + common_test_display(1200000000000000000u64); + } + + #[test] + fn test_u64_debug() { + common_test_debug(1200000000000000000u64); + } + + #[test] + fn test_str_display() { + common_test_display("test"); + } + + #[test] + fn test_str_debug() { + common_test_debug("test"); + } + + #[test] + fn test_isize_display() { + common_test_display(-1200000000000000000isize); + } + + #[test] + fn test_isize_debug() { + common_test_debug(-1200000000000000000isize); + } + + #[test] + fn test_usize_display() { + common_test_display(1200000000000000000usize); + } + + #[test] + fn test_usize_debug() { + common_test_debug(1200000000000000000usize); + } +} diff --git a/src/log/mw_log_fmt/fmt_impl_std.rs b/src/log/mw_log_fmt/fmt_impl_std.rs new file mode 100644 index 0000000..6e31326 --- /dev/null +++ b/src/log/mw_log_fmt/fmt_impl_std.rs @@ -0,0 +1,139 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Implementation of `ScoreDisplay` and `ScoreDebug` for `std` types. + +use crate::fmt; +use crate::fmt::*; +use crate::fmt_spec::FormatSpec; +use std::path::{Display, Path, PathBuf}; + +impl ScoreDisplay for String { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.write_str(self, spec) + } +} + +impl ScoreDebug for String { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.write_str("\"", spec)?; + f.write_str(self, spec)?; + f.write_str("\"", spec) + } +} + +impl ScoreDisplay for &String { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.write_str(self, spec) + } +} + +impl ScoreDebug for &String { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> fmt::Result { + f.write_str("\"", spec)?; + f.write_str(self, spec)?; + f.write_str("\"", spec) + } +} + +impl<'a> ScoreDisplay for Display<'a> { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + f.write_str(self.to_string().as_str(), spec) + } +} + +impl<'a> ScoreDebug for Display<'a> { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + f.write_str("\"", spec)?; + f.write_str(self.to_string().as_str(), spec)?; + f.write_str("\"", spec) + } +} + +impl ScoreDebug for Path { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + ScoreDebug::fmt(&self.display(), f, spec) + } +} + +impl ScoreDebug for &Path { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + ScoreDebug::fmt(&self.display(), f, spec) + } +} + +impl ScoreDebug for PathBuf { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + ScoreDebug::fmt(&self.display(), f, spec) + } +} + +impl ScoreDebug for &PathBuf { + fn fmt(&self, f: Writer, spec: &FormatSpec) -> Result { + ScoreDebug::fmt(&self.display(), f, spec) + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::{common_test_debug, common_test_display}; + use std::path::{Path, PathBuf}; + + #[test] + fn test_string_display() { + common_test_display(String::from("test")); + } + + #[test] + fn test_string_debug() { + common_test_debug(String::from("test")); + } + + #[test] + fn test_string_ref_display() { + let value = String::from("test"); + common_test_display(&value); + } + + #[test] + fn test_string_ref_debug() { + let value = String::from("test"); + common_test_debug(&value); + } + + #[test] + fn test_path_display_display() { + common_test_display(Path::new("/tmp/test_path").display()); + } + + #[test] + fn test_path_display_debug() { + common_test_debug(Path::new("/tmp/test_path").display()); + } + + #[test] + fn test_path_ref_debug() { + common_test_debug(Path::new("/tmp/test_path")); + } + + #[test] + fn test_pathbuf_debug() { + common_test_debug(PathBuf::from("/tmp/test_path")); + } + + #[test] + fn test_pathbuf_ref_debug() { + let value = PathBuf::from("/tmp/test_path"); + common_test_debug(&value); + } +} diff --git a/src/log/mw_log_fmt/fmt_spec.rs b/src/log/mw_log_fmt/fmt_spec.rs new file mode 100644 index 0000000..a0d2295 --- /dev/null +++ b/src/log/mw_log_fmt/fmt_spec.rs @@ -0,0 +1,376 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Alignment of written data. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Alignment { + /// Align to left (`<`). + Left, + /// Align to right (`<`). + Right, + /// Align to center (`<`). + Center, +} + +/// Add sign character for numeric values. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Sign { + /// Always show sign (`+`). + Plus, + /// Unused (`-`). + Minus, +} + +/// Format integer values as hexadecimal for `ScoreDebug` implementations. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DebugAsHex { + /// Format integer values to lower hex. + Lower, + /// Format integer values to upper hex. + Upper, +} + +/// Display data in a provided format. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum DisplayHint { + /// `{}` or `{:}`. + NoHint, + /// `{:o}`. + Octal, + /// `{:x}`. + LowerHex, + /// `{:X}`. + UpperHex, + /// `{:p}`. + Pointer, + /// `{:b}`. + Binary, + /// `{:e}`. + LowerExp, + /// `{:E}`. + UpperExp, +} + +/// Format spec. +/// +/// format_spec := [[fill]align][sign]['#']['0'][width]['.' precision][type] +/// fill := character +/// align := '<' | '^' | '>' +/// sign := '+' | '-' +/// width := count +/// precision := count | '*' +/// type := '?' | 'x?' | 'X?' | 'o' | 'x' | 'X' | 'p' | 'b' | 'e' | 'E' +/// parameter := argument '$' +#[derive(Clone)] +pub struct FormatSpec { + display_hint: DisplayHint, + fill: char, + align: Option, + sign: Option, + alternate: bool, + zero_pad: bool, + debug_as_hex: Option, + width: Option, + precision: Option, +} + +impl FormatSpec { + /// Create format spec with default parameters. + /// + /// - `display_hint`: `DisplayHint::NoHint` + /// - `fill`: `' '` + /// - `align`: `None` + /// - `sign`: `None` + /// - `alternate`: `false` + /// - `zero_pad`: `false` + /// - `debug_as_hex`: `None` + /// - `width`: `None` + /// - `precision`: `None` + pub fn new() -> Self { + Self { + display_hint: DisplayHint::NoHint, + fill: ' ', + align: None, + sign: None, + alternate: false, + zero_pad: false, + debug_as_hex: None, + width: None, + precision: None, + } + } + + /// Create format spec with provided parameters. + #[allow(clippy::too_many_arguments)] + pub fn from_params( + display_hint: DisplayHint, + fill: char, + align: Option, + sign: Option, + alternate: bool, + zero_pad: bool, + debug_as_hex: Option, + width: Option, + precision: Option, + ) -> Self { + Self { + display_hint, + fill, + align, + sign, + alternate, + zero_pad, + debug_as_hex, + width, + precision, + } + } + + /// Set display hint. + pub fn display_hint(&mut self, display_hint: DisplayHint) -> &mut Self { + self.display_hint = display_hint; + self + } + + /// Set fill character. + pub fn fill(&mut self, fill: char) -> &mut Self { + self.fill = fill; + self + } + + /// Set alignment. + pub fn align(&mut self, align: Option) -> &mut Self { + self.align = align; + self + } + + /// Set sign. + pub fn sign(&mut self, sign: Option) -> &mut Self { + self.sign = sign; + self + } + + /// Set alternate formatting mode. + pub fn alternate(&mut self, alternate: bool) -> &mut Self { + self.alternate = alternate; + self + } + + /// Set zero padding mode. + pub fn zero_pad(&mut self, zero_pad: bool) -> &mut Self { + self.zero_pad = zero_pad; + self + } + + /// Set debug as hex mode. + pub fn debug_as_hex(&mut self, debug_as_hex: Option) -> &mut Self { + self.debug_as_hex = debug_as_hex; + self + } + + /// Set width. + pub fn width(&mut self, width: Option) -> &mut Self { + self.width = width; + self + } + + /// Set precision. + pub fn precision(&mut self, precision: Option) -> &mut Self { + self.precision = precision; + self + } + + /// Get display hint. + pub fn get_display_hint(&self) -> DisplayHint { + self.display_hint + } + + /// Get fill character. + pub fn get_fill(&self) -> char { + self.fill + } + + /// Get alignment. + pub fn get_align(&self) -> Option { + self.align + } + + /// Get sign. + pub fn get_sign(&self) -> Option { + self.sign + } + + /// Get alternate mode. + pub fn get_alternate(&self) -> bool { + self.alternate + } + + /// Get zero padding mode. + pub fn get_zero_pad(&self) -> bool { + self.zero_pad + } + + /// Get debug as hex mode. + pub fn get_debug_as_hex(&self) -> Option { + self.debug_as_hex + } + + /// Get width. + pub fn get_width(&self) -> Option { + self.width + } + + /// Get precision. + pub fn get_precision(&self) -> Option { + self.precision + } +} + +impl Default for FormatSpec { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::{Alignment, DebugAsHex, DisplayHint, FormatSpec, Sign}; + + #[test] + fn test_new() { + let format_spec = FormatSpec::new(); + + assert!(format_spec.get_display_hint() == DisplayHint::NoHint); + assert_eq!(format_spec.get_fill(), ' '); + assert!(format_spec.get_align().is_none()); + assert!(format_spec.get_sign().is_none()); + assert!(!format_spec.get_alternate()); + assert!(!format_spec.get_zero_pad()); + assert!(format_spec.get_debug_as_hex().is_none()); + assert!(format_spec.get_width().is_none()); + assert!(format_spec.get_precision().is_none()); + } + + #[test] + fn test_default() { + let spec_default = FormatSpec::default(); + let spec_new = FormatSpec::new(); + + assert!(spec_default.get_display_hint() == spec_new.get_display_hint()); + assert!(spec_default.get_fill() == spec_new.get_fill()); + assert!(spec_default.get_align() == spec_new.get_align()); + assert!(spec_default.get_sign() == spec_new.get_sign()); + assert!(spec_default.get_alternate() == spec_new.get_alternate()); + assert!(spec_default.get_zero_pad() == spec_new.get_zero_pad()); + assert!(spec_default.get_debug_as_hex() == spec_new.get_debug_as_hex()); + assert!(spec_default.get_width() == spec_new.get_width()); + assert!(spec_default.get_precision() == spec_new.get_precision()); + } + + #[test] + fn test_from_params() { + let display_hint = DisplayHint::Binary; + let fill = 'Z'; + let align = Some(Alignment::Right); + let sign = Some(Sign::Plus); + let alternate = true; + let zero_pad = true; + let debug_as_hex = Some(DebugAsHex::Upper); + let width = Some(1234); + let precision = Some(5); + + let format_spec = FormatSpec::from_params(display_hint, fill, align, sign, alternate, zero_pad, debug_as_hex, width, precision); + + assert!(format_spec.get_display_hint() == display_hint); + assert!(format_spec.get_fill() == fill); + assert!(format_spec.get_align() == align); + assert!(format_spec.get_sign() == sign); + assert!(format_spec.get_alternate() == alternate); + assert!(format_spec.get_zero_pad() == zero_pad); + assert!(format_spec.get_debug_as_hex() == debug_as_hex); + assert!(format_spec.get_width() == width); + assert!(format_spec.get_precision() == precision); + } + + #[test] + fn test_display_hint() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_display_hint() == DisplayHint::NoHint); + format_spec.display_hint(DisplayHint::LowerExp); + assert!(format_spec.get_display_hint() == DisplayHint::LowerExp); + } + + #[test] + fn test_fill() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_fill() == ' '); + format_spec.fill('c'); + assert!(format_spec.get_fill() == 'c'); + } + + #[test] + fn test_align() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_align().is_none()); + format_spec.align(Some(Alignment::Center)); + assert!(format_spec.get_align() == Some(Alignment::Center)); + } + + #[test] + fn test_sign() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_sign().is_none()); + format_spec.sign(Some(Sign::Minus)); + assert!(format_spec.get_sign() == Some(Sign::Minus)); + } + + #[test] + fn test_alternate() { + let mut format_spec = FormatSpec::new(); + assert!(!format_spec.get_alternate()); + format_spec.alternate(true); + assert!(format_spec.get_alternate()); + } + + #[test] + fn test_zero_pad() { + let mut format_spec = FormatSpec::new(); + assert!(!format_spec.get_zero_pad()); + format_spec.zero_pad(true); + assert!(format_spec.get_zero_pad()); + } + + #[test] + fn test_debug_as_hex() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_debug_as_hex().is_none()); + format_spec.debug_as_hex(Some(DebugAsHex::Lower)); + assert!(format_spec.get_debug_as_hex() == Some(DebugAsHex::Lower)); + } + + #[test] + fn test_width() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_width().is_none()); + format_spec.width(Some(12345)); + assert!(format_spec.get_width() == Some(12345)); + } + + #[test] + fn test_precision() { + let mut format_spec = FormatSpec::new(); + assert!(format_spec.get_precision().is_none()); + format_spec.precision(Some(54321)); + assert!(format_spec.get_precision() == Some(54321)); + } +} diff --git a/src/log/mw_log_fmt/lib.rs b/src/log/mw_log_fmt/lib.rs new file mode 100644 index 0000000..5ea4966 --- /dev/null +++ b/src/log/mw_log_fmt/lib.rs @@ -0,0 +1,32 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Implementation of formatting library. +//! Allows creation of message frames that are not exclusively text based. +//! +//! Replacement for [`core::fmt`]. + +#![cfg_attr(all(not(feature = "std"), not(test)), no_std)] + +mod fmt; +mod fmt_impl; +#[cfg(feature = "std")] +mod fmt_impl_std; +mod fmt_spec; +mod macros; + +pub use fmt::*; +pub use fmt_spec::*; + +#[cfg(test)] +mod test_utils; diff --git a/src/log/mw_log_fmt/macros.rs b/src/log/mw_log_fmt/macros.rs new file mode 100644 index 0000000..2d26947 --- /dev/null +++ b/src/log/mw_log_fmt/macros.rs @@ -0,0 +1,36 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// Writes data using provided writer. +/// +/// This macro accepts a writer, a format string, and a list of arguments. +/// Arguments will be formatted according to the specified format string and the result will be passed to the writer. +#[macro_export] +macro_rules! score_write { + ($dst:expr, $($arg:tt)*) => { + $crate::write($dst, mw_log::__private_api::format_args!($($arg)*)) + }; +} + +/// Writes data using provided writer, with a newline appended. +/// +/// For more information, see [`write!`]. +#[macro_export] +macro_rules! score_writeln { + ($dst:expr $(,)?) => { + $crate::score_write!($dst, "\n") + }; + ($dst:expr, $($arg:tt)*) => { + $crate::write($dst, mw_log::__private_api::format_args_nl!($($arg)*)) + }; +} diff --git a/src/log/mw_log_fmt/test_utils.rs b/src/log/mw_log_fmt/test_utils.rs new file mode 100644 index 0000000..ee37629 --- /dev/null +++ b/src/log/mw_log_fmt/test_utils.rs @@ -0,0 +1,102 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +//! Common testing utilities. + +use crate::{Error, FormatSpec, Result, ScoreDebug, ScoreDisplay, ScoreWrite}; +use core::fmt::{Error as CoreFmtError, Write}; + +impl From for Error { + fn from(_value: CoreFmtError) -> Self { + Error + } +} + +pub(crate) struct StringWriter { + buf: String, +} + +impl StringWriter { + pub fn new() -> Self { + Self { buf: String::new() } + } + + pub fn get(&self) -> &str { + self.buf.as_str() + } +} + +impl ScoreWrite for StringWriter { + fn write_bool(&mut self, v: &bool, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_f32(&mut self, v: &f32, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_f64(&mut self, v: &f64, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_i8(&mut self, v: &i8, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_i16(&mut self, v: &i16, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_i32(&mut self, v: &i32, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_i64(&mut self, v: &i64, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_u8(&mut self, v: &u8, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_u16(&mut self, v: &u16, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_u32(&mut self, v: &u32, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_u64(&mut self, v: &u64, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } + + fn write_str(&mut self, v: &str, _spec: &FormatSpec) -> Result { + Ok(write!(self.buf, "{}", v)?) + } +} + +/// Common test comparing [`ScoreDisplay`] with [`core::fmt::Display`]. +pub(crate) fn common_test_display(v: T) { + let mut w = StringWriter::new(); + let _ = ScoreDisplay::fmt(&v, &mut w, &FormatSpec::new()); + assert_eq!(w.get(), format!("{v}")); +} + +/// Common test comparing [`ScoreDebug`] with [`core::fmt::Display`]. +/// This is useful for e.g., checking string primitives. +pub(crate) fn common_test_debug(v: T) { + let mut w = StringWriter::new(); + let _ = ScoreDebug::fmt(&v, &mut w, &FormatSpec::new()); + assert_eq!(w.get(), format!("{v:?}")); +} diff --git a/src/log/src/lib.rs b/src/log/src/lib.rs deleted file mode 100644 index 7d78535..0000000 --- a/src/log/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -// NOTE: this library is a placeholder until actual library is merged. -// Cargo workspace requires any member to be present, otherwise CI/CD won't be able to pass. - -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -}