use crate::error::ItraceError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub general: GeneralConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default, rename = "level")]
pub level_styles: HashMap<String, LevelStyle>,
#[serde(default)]
pub datetime: DateTimeConfig,
#[serde(default)]
pub columns: ColumnsConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GeneralConfig {
#[serde(default = "default_true")]
pub colors: bool,
pub output: Option<PathBuf>,
}
impl Default for GeneralConfig {
fn default() -> Self {
Self {
colors: true,
output: None,
}
}
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoggingConfig {
#[serde(default = "default_level")]
pub default: String,
#[serde(default)]
pub targets: HashMap<String, String>,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
default: default_level(),
targets: HashMap::new(),
}
}
}
fn default_level() -> String {
"info".to_string()
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LevelStyle {
pub bg: Option<Color>,
pub fg: Option<Color>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Color {
Ansi { ansi: u8 },
Rgb { rgb: [u8; 3] },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateTimeConfig {
#[serde(default = "default_datetime_format")]
pub format: String,
}
impl Default for DateTimeConfig {
fn default() -> Self {
Self {
format: default_datetime_format(),
}
}
}
fn default_datetime_format() -> String {
"%H:%M:%S%.6f".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnsConfig {
#[serde(default)]
pub order: Vec<String>,
#[serde(default)]
pub default: ColumnDef,
#[serde(default, flatten)]
pub definitions: HashMap<String, ColumnDef>,
}
impl Default for ColumnsConfig {
fn default() -> Self {
Self {
order: Vec::new(),
default: ColumnDef::default(),
definitions: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnDef {
#[serde(default = "default_width")]
pub width: usize,
#[serde(default)]
pub align: Alignment,
pub bg: Option<Color>,
pub fg: Option<Color>,
}
impl Default for ColumnDef {
fn default() -> Self {
Self {
width: default_width(),
align: Alignment::default(),
bg: None,
fg: None,
}
}
}
fn default_width() -> usize {
10
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Alignment {
#[default]
Left,
Right,
Center,
}
impl Config {
pub fn load(tracer_name: &str) -> Result<Self, ItraceError> {
let path = Self::resolve_path(tracer_name)?;
if !path.exists() {
return Err(ItraceError::TracerNotFound { path });
}
let content = std::fs::read_to_string(&path).map_err(|e| ItraceError::Io {
path: path.clone(),
source: e,
})?;
let config: Config = toml::from_str(&content).map_err(|e| ItraceError::Toml {
path: path.clone(),
source: e,
})?;
Ok(config)
}
pub fn resolve_path(tracer_name: &str) -> Result<PathBuf, ItraceError> {
let base = dirs::config_dir().ok_or(ItraceError::NoConfigDir)?;
Ok(base.join("itrace").join(format!("{tracer_name}.toml")))
}
pub fn to_env_filter_string(&self) -> String {
let mut parts: Vec<String> = self
.logging
.targets
.iter()
.map(|(target, level)| format!("{target}={level}"))
.collect();
parts.insert(0, self.logging.default.clone());
parts.join(",")
}
}