use std::fmt::Arguments;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
use crate::config::Config;
use crate::level::Level;
#[derive(Debug)]
pub struct Spanner<T>
where
T: std::io::Write,
{
writer: Mutex<T>,
depth: AtomicUsize,
config: Config,
}
impl<T> Spanner<T>
where
T: std::io::Write,
{
pub fn from_writer(writer: T) -> Self {
Self {
writer: Mutex::new(writer),
depth: AtomicUsize::new(0),
config: Config::default(),
}
}
pub fn enter_span(&self, name: &str) -> Span<T> {
self.enter_with_level(Level::Info, name)
}
pub fn enter_with_level(&self, level: Level, name: &str) -> Span<T> {
Span::enter(self, level, name)
}
pub fn enter_args(&self, level: Level, args: Arguments) -> Span<T> {
Span::enter_args(self, level, args)
}
#[must_use]
pub fn with_config(self, cfg: Config) -> Self {
Self {
config: cfg,
..self
}
}
}
pub type VecSpanner = Spanner<Vec<u8>>;
impl VecSpanner {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_vec(vec: Vec<u8>) -> Self {
Self {
writer: Mutex::new(vec),
depth: AtomicUsize::new(0),
config: Config::default(),
}
}
}
impl Default for VecSpanner {
fn default() -> Self {
Self {
writer: Mutex::new(Vec::new()),
depth: AtomicUsize::new(0),
config: Config::default(),
}
}
}
pub type FileSpanner = Spanner<std::fs::File>;
impl FileSpanner {
#[must_use]
pub fn new(file: std::fs::File) -> Self {
Self {
writer: Mutex::new(file),
depth: AtomicUsize::new(0),
config: Config::default(),
}
}
}
pub type StdoutSpanner = Spanner<std::io::Stdout>;
impl StdoutSpanner {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl Default for StdoutSpanner {
fn default() -> Self {
Self {
writer: Mutex::new(std::io::stdout()),
depth: AtomicUsize::new(0),
config: Config::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct Span<'a, T>
where
T: std::io::Write,
{
parent: &'a Spanner<T>,
drop_message: String,
level: Level,
}
impl<'a, T> Span<'a, T>
where
T: std::io::Write,
{
fn enter(parent: &'a Spanner<T>, level: Level, name: &str) -> Self {
Self::enter_args(parent, level, format_args!("{name}"))
}
fn enter_args(parent: &'a Spanner<T>, level: Level, args: Arguments) -> Self {
let mut drop_message = String::new();
if parent.config.level <= level {
let prev_depth = parent.depth.fetch_add(1, Ordering::Relaxed);
let (enter_message, drop_msg) =
Self::generate_messages(args, prev_depth, &parent.config);
drop_message = drop_msg;
if let Ok(mut writer) = parent.writer.lock() {
let _ = writer.write(enter_message.as_ref());
}
}
Self {
parent,
drop_message,
level,
}
}
fn generate_messages(name: Arguments, depth: usize, cfg: &Config) -> (String, String) {
let spaces: String = (0..depth).enumerate().fold(
String::with_capacity(depth * cfg.tabwidth),
|mut acc, (i, _)| {
let is_displayed = match cfg.skip {
0 => false,
_ => i % cfg.skip == 0,
};
if is_displayed {
acc.push((cfg.depthmap)(i));
} else {
acc.push(' ');
}
for _ in 0..cfg.tabwidth.saturating_sub(1) {
acc.push(' ');
}
acc
},
);
let is_displayed = match cfg.skip {
0 => false,
_ => depth % cfg.skip == 0,
};
let enter_message = format!(
"{}{}{}\n",
spaces,
if is_displayed { '┌' } else { ' ' },
name
);
let drop_message = format!(
"{}{}{}\n",
spaces,
if is_displayed { '└' } else { ' ' },
name
);
(enter_message, drop_message)
}
}
impl<T> Drop for Span<'_, T>
where
T: std::io::Write,
{
fn drop(&mut self) {
if self.parent.config.level < self.level {
return;
}
let _ = self.parent.depth.fetch_sub(1, Ordering::Relaxed);
if let Ok(mut writer) = self.parent.writer.lock() {
let _ = writer.write(self.drop_message.as_ref());
}
}
}
#[macro_export]
macro_rules! spf {
($spa:expr, $level:path => $arg:literal) => {{
let span = $spa.enter_with_level($level, $arg);
span
}};
($spa:expr => $arg:literal) => {{
let span = $spa.enter_span($arg);
span
}};
($spa:expr, $level:path, $($arg:tt)*) => {{
let span = $spa.enter_args($level, format_args!($($arg)*));
span
}};
($spa:expr, $($arg:tt)*) => {{
let span = $spa.enter_args($crate::level::Level::Info, format_args!($($arg)*));
span
}};
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
struct Helper<T>
where
T: std::io::Write,
{
spanner: Spanner<T>,
}
impl<T> Helper<T>
where
T: std::io::Write,
{
fn helper(&self, current_depth: usize, target_depth: usize) {
let _span = self
.spanner
.enter_span(format!("Span({})", current_depth).as_ref());
if current_depth >= target_depth {
return;
}
return self.helper(current_depth + 1, target_depth);
}
fn helper_macro(&self, current_depth: usize, target_depth: usize) {
let _span = spf!(self.spanner, "Span({current_depth})");
if current_depth >= target_depth {
return;
}
return self.helper_macro(current_depth + 1, target_depth);
}
fn helper_macro_2(&self, current_depth: usize, target_depth: usize) {
let _span = spf!(self.spanner, Level::Info, "Span({current_depth})");
if current_depth >= target_depth {
return;
}
return self.helper_macro_2(current_depth + 1, target_depth);
}
fn helper_macro_3(&self, current_depth: usize, target_depth: usize) {
let _span = spf!(self.spanner => "Span({current_depth})");
if current_depth >= target_depth {
return;
}
return self.helper_macro_3(current_depth + 1, target_depth);
}
fn helper_macro_4(&self, current_depth: usize, target_depth: usize) {
let _span = spf!(self.spanner, "Span({})", current_depth);
if current_depth >= target_depth {
return;
}
return self.helper_macro_2(current_depth + 1, target_depth);
}
}
#[test]
fn it_works() {
let helper = Helper {
spanner: VecSpanner::new(),
};
let expected = r#"┌Span(0)
| Span(1)
| ┌Span(2)
| ┆ Span(3)
| ┆ ┌Span(4)
| ┆ | Span(5)
| ┆ | Span(5)
| ┆ └Span(4)
| ┆ Span(3)
| └Span(2)
| Span(1)
└Span(0)
"#;
helper.helper(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec);
let helper = Helper {
spanner: VecSpanner::new(),
};
helper.helper_macro(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec)
}
#[test]
fn test_config() {
let helper = Helper {
spanner: VecSpanner::new().with_config(Config::new().with_skip(3)),
};
let expected = r#"┌Span(0)
| Span(1)
| Span(2)
| ┌Span(3)
| ┊ Span(4)
| ┊ Span(5)
| ┊ Span(5)
| ┊ Span(4)
| └Span(3)
| Span(2)
| Span(1)
└Span(0)
"#;
helper.helper(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec)
}
#[test]
fn test_level() {
let mut writer = Cursor::new(Vec::new());
let spanner = Spanner::from_writer(&mut writer)
.with_config(Config::new().with_skip(3).with_level(Level::Info));
{
let _span = spanner.enter_with_level(Level::Debug, "foo");
}
assert_eq!(writer.get_ref(), &[]);
}
#[test]
fn test_skip() {
let helper = Helper {
spanner: VecSpanner::new().with_config(Config::new().with_skip(0)),
};
let expected = r#" Span(0)
Span(1)
Span(2)
Span(3)
Span(4)
Span(5)
Span(5)
Span(4)
Span(3)
Span(2)
Span(1)
Span(0)
"#;
helper.helper(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec)
}
#[test]
fn it_works_macros() {
let expected = r#"┌Span(0)
| Span(1)
| ┌Span(2)
| ┆ Span(3)
| ┆ ┌Span(4)
| ┆ | Span(5)
| ┆ | Span(5)
| ┆ └Span(4)
| ┆ Span(3)
| └Span(2)
| Span(1)
└Span(0)
"#;
let expected_3 = r#"┌Span({current_depth})
| Span({current_depth})
| ┌Span({current_depth})
| ┆ Span({current_depth})
| ┆ ┌Span({current_depth})
| ┆ | Span({current_depth})
| ┆ | Span({current_depth})
| ┆ └Span({current_depth})
| ┆ Span({current_depth})
| └Span({current_depth})
| Span({current_depth})
└Span({current_depth})
"#;
let helper = Helper {
spanner: VecSpanner::new(),
};
helper.helper_macro(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec);
let helper = Helper {
spanner: VecSpanner::new(),
};
helper.helper_macro_2(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec);
let helper = Helper {
spanner: VecSpanner::new(),
};
helper.helper_macro_3(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected_3.bytes().collect::<Vec<_>>(), vec);
let helper = Helper {
spanner: VecSpanner::new(),
};
helper.helper_macro_4(0, 5);
let vec = helper.spanner.writer.into_inner().unwrap();
assert_eq!(expected.bytes().collect::<Vec<_>>(), vec);
}
}