1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use std::fmt::Display;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};

mod util;

use chrono::Utc;
use colorful::core::color_string::CString;
use colorful::Colorful;
use lazy_static::lazy_static;

lazy_static! {
	static ref CONF: Arc<Mutex<ConfigBuilder>> = Arc::new(Mutex::new(ConfigBuilder::default()));
}

/// Configure task_log using this interactive configuration struct.
///
/// This is done by calling methods an instance of the struct to configure it and then calling `.apply()` to apply that configuration. Behind the scenes this updates the static mutex holding the configuration.
///
/// ## Example:
/// ```
/// use task_log::ConfigBuilder;
///
/// ConfigBuilder::new()
/// 	.color(false)
/// 	.duration(false)
/// 	.apply()
/// 	.expect("Failed to configure logger");
/// ```
pub struct ConfigBuilder {
	/// If the prefix (e.g. "DONE" or "RUNNING") should be in color.
	pub color: bool,
	/// If the duration that the task took should be included in the prefix (e.g. "DONE") at the end of the task.
	pub duration: bool,
}

impl ConfigBuilder {
	/// Create a new ConfigBuilder off of the default struct values.
	pub fn new() -> Self { Self::default() }

	/// Set the color value
	pub fn color(mut self, enabled: bool) -> Self {
		self.color = enabled;
		self
	}

	/// Set the duration value
	pub fn duration(mut self, enabled: bool) -> Self {
		self.duration = enabled;
		self
	}

	/// Apply the configuration
	pub fn apply<'a>(self) -> Result<(), PoisonError<MutexGuard<'a, Self>>> {
		let mut changer = CONF.lock()?;
		*changer = self;
		Ok(())
	}
}

impl Default for ConfigBuilder {
	fn default() -> Self {
		ConfigBuilder {
			color: true,
			duration: true,
		}
	}
}

/// Automatic logging for function execution.
///
/// This function does the following:
/// 1. Output that the task is now running.
/// 2. Run the task.
/// 3. Clear running output and output that the task is now done.
///
/// Whatever the runner returns is returned by this function so you can still use `Result` and `?` if you're using a closure.
///
/// Logging this way actually makes for some very clean code. You can clearly see what is happening in a specific section of your code. It is almost like normal comments but with logging added on!
///
/// # Examples
///
/// To see more examples that you can even run locally please check out the [examples directory](https://github.com/gleich/task_log/tree/main/examples).
///
/// ## Basic Example
///
/// ```
/// use task_log::task;
///
/// let sum = task("Adding 1 and 2", || -> u32 { 1 + 2 });
/// println!("Sum of 1 and 2 is {}", sum);
/// ```
///
/// ## Error Example
///
/// ```
/// use std::fs;
/// use std::io::Result;
///
/// use task_log::task;
///
/// task("Creating and removing file", || -> Result<()> {
/// 	let filename = "hello.txt";
/// 	fs::write(filename, "foo bar")?;
/// 	fs::remove_file(filename)?;
/// 	Ok(())
/// })
/// .expect("Failed to create and remove the file");
/// ```
pub fn task<M, F, R>(msg: M, mut runner: F) -> R
where
	F: FnMut() -> R,
	M: Display,
{
	let arc_ref = Arc::clone(&CONF);
	let config = arc_ref.lock().unwrap();

	// START
	let start_time = Utc::now();
	let running_msg = if config.duration {
		"  RUNNING       "
	} else {
		"  RUNNING  "
	};
	println!(
		"{}| {}",
		if config.color {
			running_msg.yellow()
		} else {
			CString::new(running_msg)
		},
		msg
	);

	let result = runner();

	// DONE
	println!("\x1b[A\x1b[A");
	let done_msg = if config.duration {
		format!(
			"  DONE in {}  ",
			util::format_duration(Utc::now() - start_time)
		)
	} else {
		String::from("  DONE    ")
	};
	println!(
		"{} | {}",
		if config.color {
			done_msg.green()
		} else {
			CString::new(done_msg)
		},
		msg
	);
	result
}

#[cfg(test)]
mod tests {
	use crate::task;

	#[test]
	fn basic_run() {
		let name = "basic run";
		assert_eq!(3, task(name, || -> u32 { 1 + 2 }));
		assert_eq!(true, task(name, || -> bool { true }));
	}
}