systemprompt-logging 0.2.0

Core logging module for systemprompt.io OS
Documentation
use std::io::Write;

use crate::services::cli::display::{CollectionDisplay, Display, DisplayUtils, StatusDisplay};
use crate::services::cli::theme::{EmphasisType, ItemStatus, MessageLevel, Theme};
#[derive(Debug)]
pub struct ValidationSummary {
    pub valid: Vec<(String, String)>,
    pub installed: Vec<String>,
    pub updated: Vec<String>,
    pub schemas_applied: Vec<String>,
    pub seeds_applied: Vec<String>,
    pub disabled: Vec<String>,
}

impl ValidationSummary {
    pub const fn new() -> Self {
        Self {
            valid: Vec::new(),
            installed: Vec::new(),
            updated: Vec::new(),
            schemas_applied: Vec::new(),
            seeds_applied: Vec::new(),
            disabled: Vec::new(),
        }
    }

    pub fn add_valid(&mut self, name: String, version: String) {
        self.valid.push((name, version));
    }

    pub fn add_installed(&mut self, name: String) {
        self.installed.push(name);
    }

    pub fn add_updated(&mut self, name: String) {
        self.updated.push(name);
    }

    pub fn add_schema_applied(&mut self, name: String) {
        self.schemas_applied.push(name);
    }

    pub fn add_seed_applied(&mut self, name: String) {
        self.seeds_applied.push(name);
    }

    pub fn add_disabled(&mut self, name: String) {
        self.disabled.push(name);
    }

    pub fn total_active(&self) -> usize {
        self.valid.len() + self.installed.len() + self.updated.len()
    }

    pub fn has_changes(&self) -> bool {
        !self.installed.is_empty()
            || !self.updated.is_empty()
            || !self.schemas_applied.is_empty()
            || !self.seeds_applied.is_empty()
    }
}

impl Display for ValidationSummary {
    fn display(&self) {
        DisplayUtils::section_header("Module Validation Summary");

        if !self.valid.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .valid
                .iter()
                .map(|(name, version)| {
                    let detail = format!("v{version}");
                    StatusDisplay::new(ItemStatus::Valid, name).with_detail(detail)
                })
                .collect();

            let collection = CollectionDisplay::new("Valid modules", displays);
            collection.display();
        }

        if !self.installed.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .installed
                .iter()
                .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
                .collect();

            let collection = CollectionDisplay::new("Newly installed", displays);
            collection.display();
        }

        if !self.updated.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .updated
                .iter()
                .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
                .collect();

            let collection = CollectionDisplay::new("Updated modules", displays);
            collection.display();
        }

        if !self.schemas_applied.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .schemas_applied
                .iter()
                .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
                .collect();

            let collection = CollectionDisplay::new("Schemas applied", displays);
            collection.display();
        }

        if !self.seeds_applied.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .seeds_applied
                .iter()
                .map(|name| StatusDisplay::new(ItemStatus::Applied, name))
                .collect();

            let collection = CollectionDisplay::new("Seeds applied", displays);
            collection.display();
        }

        if !self.disabled.is_empty() {
            let displays: Vec<StatusDisplay> = self
                .disabled
                .iter()
                .map(|name| StatusDisplay::new(ItemStatus::Disabled, name).with_detail("disabled"))
                .collect();

            let collection = CollectionDisplay::new("Disabled modules", displays);
            collection.display();
        }

        let total_active = self.total_active();
        if total_active > 0 {
            let mut stdout = std::io::stdout();
            let _ = writeln!(
                stdout,
                "\n{} {} active modules ready",
                Theme::icon(MessageLevel::Success),
                Theme::color(&total_active.to_string(), EmphasisType::Bold)
            );
        }
    }
}

impl Default for ValidationSummary {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone)]
pub struct OperationResult {
    pub operation: String,
    pub success: bool,
    pub message: Option<String>,
    pub details: Vec<String>,
}

impl OperationResult {
    pub fn success(operation: impl Into<String>) -> Self {
        Self {
            operation: operation.into(),
            success: true,
            message: None,
            details: Vec::new(),
        }
    }

    pub fn failure(operation: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            operation: operation.into(),
            success: false,
            message: Some(message.into()),
            details: Vec::new(),
        }
    }

    #[must_use]
    pub fn with_message(mut self, message: impl Into<String>) -> Self {
        self.message = Some(message.into());
        self
    }

    #[must_use]
    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
        self.details.push(detail.into());
        self
    }

    #[must_use]
    pub fn with_details(mut self, details: Vec<String>) -> Self {
        self.details = details;
        self
    }
}

impl Display for OperationResult {
    fn display(&self) {
        let level = if self.success {
            MessageLevel::Success
        } else {
            MessageLevel::Error
        };

        let base_message = self.message.as_ref().map_or_else(
            || self.operation.clone(),
            |msg| format!("{}: {msg}", self.operation),
        );

        DisplayUtils::message(level, &base_message);

        for detail in &self.details {
            let colored = Theme::color(detail, EmphasisType::Dim);
            let mut stdout = std::io::stdout();
            let _ = writeln!(stdout, "  \u{2022} {colored}");
        }
    }
}

#[derive(Debug)]
pub struct ProgressSummary {
    pub total: usize,
    pub completed: usize,
    pub failed: usize,
    pub operation_name: String,
}

impl ProgressSummary {
    pub fn new(operation_name: impl Into<String>, total: usize) -> Self {
        Self {
            total,
            completed: 0,
            failed: 0,
            operation_name: operation_name.into(),
        }
    }

    pub fn add_success(&mut self) {
        self.completed += 1;
    }

    pub fn add_failure(&mut self) {
        self.failed += 1;
    }

    pub const fn is_complete(&self) -> bool {
        self.completed + self.failed >= self.total
    }

    pub fn success_rate(&self) -> f64 {
        if self.total == 0 {
            1.0
        } else {
            let completed = u32::try_from(self.completed).unwrap_or(u32::MAX);
            let total = u32::try_from(self.total).unwrap_or(u32::MAX);
            f64::from(completed) / f64::from(total)
        }
    }
}

impl Display for ProgressSummary {
    fn display(&self) {
        let status = if self.failed == 0 {
            MessageLevel::Success
        } else if self.completed > 0 {
            MessageLevel::Warning
        } else {
            MessageLevel::Error
        };

        let message = if self.failed > 0 {
            format!(
                "{}: {}/{} completed, {} failed",
                self.operation_name, self.completed, self.total, self.failed
            )
        } else {
            format!(
                "{}: {}/{} completed",
                self.operation_name, self.completed, self.total
            )
        };

        DisplayUtils::message(status, &message);
    }
}