use log::LevelFilter;
use termcolor::{Color, ColorSpec};
pub use time::format_description::FormatItem;
pub use time::macros::format_description;
pub use time::UtcOffset;
const LEVEL_NUMBER: usize = 6;
#[derive(Debug, Clone, Copy)]
pub enum TimeFormat {
Rfc2822,
Rfc3339,
Custom(&'static [FormatItem<'static>]),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Token {
Text(&'static str),
Time,
Level,
ColorStart,
ColorEnd,
Thread,
Module,
FileName,
File,
Line,
Message,
}
#[derive(Debug, Clone)]
pub struct Config {
pub(crate) level: LevelFilter,
pub(crate) time_offset: UtcOffset,
pub(crate) time_format: [TimeFormat; LEVEL_NUMBER],
pub(crate) format_text: [&'static str; LEVEL_NUMBER],
pub(crate) tokens: [Vec<Token>; LEVEL_NUMBER],
pub(crate) colored_text_color: [Option<Color>; LEVEL_NUMBER],
pub(crate) background_color: [Option<Color>; LEVEL_NUMBER],
pub(crate) compiled_colors: [ColorSpec; LEVEL_NUMBER],
pub(crate) enabled_colors: bool,
}
const DEFAULT_FORMAT_TEXT: &str = "[_time] [_color_start][[_level]][_color_end] [_module]: [_msg]";
const CONF_FULL_FORMAT_TEST: &str = "[_time] [_color_start][[_level]][_color_end] [[_module]] [_file_name]:[_line] - [_msg]";
impl Config {
pub(crate) fn calculate_data(&mut self) {
self.calculate_tokens();
self.calculate_colors();
}
fn calculate_colors(&mut self) {
for (idx, color_spec) in self.compiled_colors.iter_mut().enumerate() {
*color_spec = ColorSpec::new().set_bg(self.background_color[idx]).set_fg(self.colored_text_color[idx]).clone();
}
}
fn calculate_tokens(&mut self) {
let allowed_tokens = [
("[_time]", Token::Time),
("[_level]", Token::Level),
("[_color_start]", Token::ColorStart),
("[_color_end]", Token::ColorEnd),
("[_thread]", Token::Thread),
("[_module]", Token::Module),
("[_file]", Token::File),
("[_file_name]", Token::FileName),
("[_line]", Token::Line),
("[_msg]", Token::Message),
];
for (idx, format_text) in self.format_text.into_iter().enumerate() {
let mut collected_tokens = Vec::new();
let mut current_index = 0;
loop {
let mut minimum_index = usize::MAX;
let mut choose_token_text = None;
for (token_txt, token) in &allowed_tokens {
if let Some(find_idx) = format_text[current_index..].find(token_txt) {
if minimum_index > find_idx + current_index {
minimum_index = find_idx + current_index;
choose_token_text = Some((token_txt, token));
}
}
}
if let Some((token_txt, token)) = choose_token_text {
if minimum_index > current_index {
let text = &format_text[current_index..minimum_index];
collected_tokens.push(Token::Text(text));
}
collected_tokens.push(token.clone());
current_index = minimum_index + token_txt.len() - 1;
} else {
if current_index != format_text.len() {
let text = &format_text[current_index..];
collected_tokens.push(Token::Text(text));
}
break; }
current_index += 1;
}
self.tokens[idx] = collected_tokens;
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigBuilder(Config);
impl ConfigBuilder {
#[must_use]
pub fn new() -> ConfigBuilder {
ConfigBuilder(Config::default())
}
#[must_use]
pub fn new_preset_config_full() -> ConfigBuilder {
let mut builder = ConfigBuilder::default();
builder.set_time_format(
TimeFormat::Custom(format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3] [offset_hour sign:mandatory]"
)),
None,
);
builder.set_format_text(CONF_FULL_FORMAT_TEST, None);
builder
}
pub fn set_format_text(&mut self, format_text: &'static str, level: Option<LevelFilter>) -> &mut ConfigBuilder {
if let Some(level) = level {
self.0.format_text[level as usize] = format_text;
} else {
self.0.format_text = [format_text; LEVEL_NUMBER];
}
self
}
pub fn set_background_color(&mut self, background_color: Option<Color>, level: Option<LevelFilter>) -> &mut ConfigBuilder {
if let Some(level) = level {
self.0.background_color[level as usize] = background_color;
} else {
self.0.background_color = [background_color; LEVEL_NUMBER];
}
self
}
pub fn set_colored_text_color(&mut self, colored_text_color: Option<Color>, level: Option<LevelFilter>) -> &mut ConfigBuilder {
if let Some(level) = level {
self.0.colored_text_color[level as usize] = colored_text_color;
} else {
self.0.colored_text_color = [colored_text_color; LEVEL_NUMBER];
}
self
}
pub fn set_enabled_colours(&mut self, enabled_colours: bool) -> &mut ConfigBuilder {
self.0.enabled_colors = enabled_colours;
self
}
pub fn set_level(&mut self, level: LevelFilter) -> &mut ConfigBuilder {
self.0.level = level;
self
}
pub fn set_time_format(&mut self, time_format: TimeFormat, level: Option<LevelFilter>) -> &mut ConfigBuilder {
if let Some(level) = level {
self.0.time_format[level as usize] = time_format;
} else {
self.0.time_format = [time_format; LEVEL_NUMBER];
}
self
}
pub fn set_time_offset(&mut self, offset: UtcOffset) -> &mut ConfigBuilder {
self.0.time_offset = offset;
self
}
#[cfg(feature = "time-local-offset")]
pub fn set_time_offset_to_local(&mut self) -> Result<&mut ConfigBuilder, &mut ConfigBuilder> {
match UtcOffset::current_local_offset() {
Ok(offset) => {
self.0.time_offset = offset;
Ok(self)
}
Err(_) => Err(self),
}
}
#[cfg(feature = "chrono-local-offset")]
pub fn set_chrono_local_time_offset(&mut self) -> Result<&mut ConfigBuilder, &mut ConfigBuilder> {
match UtcOffset::from_whole_seconds(chrono::offset::Local::now().offset().local_minus_utc()) {
Ok(offset) => {
self.0.time_offset = offset;
Ok(self)
}
Err(_) => Err(self),
}
}
pub fn build(&mut self) -> Config {
self.0.clone()
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
ConfigBuilder::new()
}
}
impl Default for Config {
fn default() -> Config {
Config {
level: LevelFilter::Info,
time_format: [TimeFormat::Custom(format_description!("[hour]:[minute]:[second]")); LEVEL_NUMBER],
time_offset: UtcOffset::UTC,
tokens: [vec![], vec![], vec![], vec![], vec![], vec![]],
colored_text_color: [
None,
Some(Color::Red), Some(Color::Yellow), Some(Color::Blue), Some(Color::Cyan), Some(Color::White), ],
background_color: [None, None, None, None, None, None],
enabled_colors: true,
format_text: [DEFAULT_FORMAT_TEXT; LEVEL_NUMBER],
compiled_colors: [ColorSpec::new(), ColorSpec::new(), ColorSpec::new(), ColorSpec::new(), ColorSpec::new(), ColorSpec::new()],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let text = "[_time] [_level] [_thread] [_module] [_file][_file_name] [_line] [_color_start][_msg][_color_end] [RAR]";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(
config.tokens[0],
vec![
Token::Time,
Token::Text(" "),
Token::Level,
Token::Text(" "),
Token::Thread,
Token::Text(" "),
Token::Module,
Token::Text(" "),
Token::File,
Token::FileName,
Token::Text(" "),
Token::Line,
Token::Text(" "),
Token::ColorStart,
Token::Message,
Token::ColorEnd,
Token::Text(" [RAR]"),
]
);
let text = "]]][[";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(config.tokens[0], vec![Token::Text("]]][[")]);
let text = " [_time]";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(config.tokens[0], vec![Token::Text(" "), Token::Time]);
let text = "[_time]";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(config.tokens[0], vec![Token::Time]);
let text = "[_time] ";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(config.tokens[0], vec![Token::Time, Token::Text(" ")]);
let text = "";
let mut config = ConfigBuilder::new().set_format_text(text, None).build();
config.calculate_data();
assert_eq!(config.tokens[0], vec![]);
}
}