nauman 1.1.2

A CI inspired approach for local job automation
use std::io;
use std::io::Write;
use std::path::PathBuf;
use crate::{execution::ExecutionContext, config::{LogHandlers, Shell}, logging::{MultiOutputStream, LoggingSpec, pprint}, common::Env, flow::Command, flow};
use anyhow::{Result};
use colored::{Colorize};
use prettytable::{Cell, row, Row, Table};
use crate::common::LogLevel;
use crate::execution::ExecutionResult;
use crate::pprint::{flex_banner, truncate_string};

pub trait LogAction {
    fn min_level(&self) -> LogLevel;

    fn write(&self, level: LogLevel, output: &mut impl std::io::Write) -> std::io::Result<()>;
}

pub struct ActionCommandStart<'a> {
    pub command: &'a Command
}

impl<'a> LogAction for ActionCommandStart<'a> {
    fn min_level(&self) -> LogLevel {
        LogLevel::Info
    }

    fn write(&self, _level: LogLevel, output: &mut impl std::io::Write) -> std::io::Result<()> {
        writeln!(output, "{}", if self.command.is_hook {
            pprint::flex_banner(format!("Hook: {}", &self.command.name)).yellow()
        } else {
            pprint::flex_banner( format!("Task: {}", &self.command.name)).green()
        })
    }
}

pub struct ActionShell<'a> {
    pub handler: &'a Shell,
    pub cwd: &'a PathBuf,
    pub env: &'a Env,
    pub shell_program: &'a str,
    pub shell_args: &'a [String],
}

impl<'a> LogAction for ActionShell<'a> {
    fn min_level(&self) -> LogLevel {
        LogLevel::Info
    }

    fn write(&self, level: LogLevel, output: &mut impl std::io::Write) -> std::io::Result<()> {
        if level >= LogLevel::Info {
            writeln!(output, "{}", pprint::command(&self.handler.run))?;
        }
        Ok(())
    }
}

pub struct ActionCommandEnd<'a> {
    pub command: &'a Command,
    pub result: &'a ExecutionResult,
}

impl<'a> LogAction for ActionCommandEnd<'a> {
    fn min_level(&self) -> LogLevel {
        LogLevel::Error
    }

    fn write(&self, level: LogLevel, output: &mut impl Write) -> std::io::Result<()> {
        if !self.result.is_success() && !self.result.is_aborted() && level >= LogLevel::Error {
            writeln!(output, "{}", pprint::task_error(
                &self.command.name, self.result.exit_code, self.result.duration.as_ref()
            ))?;
        }
        if self.result.is_aborted() && level >= LogLevel::Debug {
            if !self.command.is_hook {
                writeln!(output, "{}", pprint::task_aborted(
                    &self.command.name, self.command.policy
                ))?;
            }
        }
        if self.result.is_success() && !self.result.is_aborted() && level >= LogLevel::Debug {
            writeln!(output, "{}", pprint::task_success(
                &self.command.name, self.result.duration.as_ref()
            ))?;
        }
        Ok(())
    }
}

pub struct ActionSummary<'a> {
    pub flow: &'a flow::Flow,
    pub results: &'a Vec<(String, ExecutionResult)>,
}

impl<'a> LogAction for ActionSummary<'a> {
    fn min_level(&self) -> LogLevel {
        LogLevel::Info
    }

    fn write(&self, _level: LogLevel, _output: &mut impl Write) -> std::io::Result<()> {
        let mut table = Table::new();
        table.add_row(row![
            "Task",
            "Action",
            "Time (in s)",
        ]);

        for (command_id, result) in self.results.iter() {
            let command = self.flow.command(command_id).expect("Command not found");
            let step = if result.is_success() {
                if command.is_hook { "🪝".to_string() } else { command.task_no.map(|i| i.to_string()).unwrap_or_default() }
            } else if result.is_aborted() { "⛔️".to_string() } else { "💥".to_string() };
            let name = &truncate_string(&command.name, 60);
            let duration = result.duration.map(|d| d.as_secs().to_string())
                .unwrap_or_else(|| "-".to_string());

            table.add_row(Row::new(vec![
                Cell::new(&step),
                Cell::new(name).style_spec(if !result.is_success() { "Fr" } else { "" }),
                Cell::new(&duration),
            ]));
        }

        println!("{}", flex_banner(format!("Summary: {}", self.flow.name)).yellow());
        table.printstd();
        Ok(())
    }
}

pub struct Logger {
    config: LogHandlers,
    level: LogLevel,
    pub output: MultiOutputStream,
}

impl Logger {
    pub fn new(config: LogHandlers, level: LogLevel) -> Logger {
        Logger {
            config,
            level,
            output: MultiOutputStream::new(),
        }
    }

    pub fn switch(
        &mut self,
        context: &ExecutionContext,
    ) -> Result<()> {
        let spec = LoggingSpec::from_config(&self.config, context)?;
        self.output = MultiOutputStream::from_spec(spec);
        Ok(())
    }

    pub fn mut_output(&mut self) -> &mut MultiOutputStream {
        &mut self.output
    }

    pub fn flush(&mut self) -> io::Result<()> {
        self.output.flush()
    }

    pub fn log_action(&mut self, action: impl LogAction) -> Result<()> {
        if self.level >= action.min_level() {
            action.write(self.level, self.mut_output())?;
        }
        Ok(())
    }
}