august-build 0.7.1

Task-based build system with a custom syntax and focus on paralellism for all your artifact construction needs.
Documentation
use std::io;

use crate::colours::OwoColorizeStderrSupported;
use ariadne::{Color, Label, Report, ReportKind, Source};

use august_build::{
    parser::Spanned,
    runtime::{Notifier, NotifierEvent, RuntimeError},
    Command,
};

#[derive(Debug)]
pub struct SilentNotifier;

impl Notifier for SilentNotifier {
    fn on_event(&self, _event: NotifierEvent<'_>) {}
}

#[derive(Debug)]
pub struct LogNotifier {
    file_name: String,
    code: String,
    verbose: bool,
}

impl LogNotifier {
    pub fn new(file_name: impl Into<String>, code: impl Into<String>) -> Self {
        Self {
            file_name: file_name.into(),
            code: code.into(),
            verbose: false,
        }
    }

    #[inline]
    pub fn verbose(mut self) -> Self {
        self.verbose = true;
        self
    }

    fn cmd_call(&self, cmd: &Command) {
        use august_build::EnvCommand::{PathPush, PathRemove, RemoveVar, SetVar};
        use august_build::FsCommand::{Copy, Create, CreateDir, Move, Remove};
        use Command::{Env, Exec, Fs};

        if !self.verbose {
            return;
        }

        let text = match cmd {
            Exec(args) => Some(
                args.iter()
                    .map(Spanned::inner)
                    .fold(format!("{} ", "[exec]".bright_blue()), |agg, s| {
                        agg + s + " "
                    }),
            ),
            Env(SetVar(k, v)) => Some(format!(
                "{} Setting {k} to {v}",
                "[env::set_var]".bright_blue()
            )),
            Env(RemoveVar(k)) => Some(format!(
                "{} Clearing the variable {k}",
                "[env::remove_var]".bright_blue()
            )),
            Env(PathPush(p)) => Some(format!(
                "{} Adding {p} to PATH",
                "[env::path_push]".bright_blue()
            )),
            Env(PathRemove(p)) => Some(format!(
                "{} Removing {p} from PATH",
                "[env::path_remove]".bright_blue()
            )),
            Fs(Create(p)) => Some(format!(
                "{} Creating empty file {p}",
                "[fs::create]".bright_blue()
            )),
            Fs(CreateDir(p)) => Some(format!(
                "{} Creating directory {p}",
                "[fs::create_dir]".bright_blue()
            )),
            Fs(Remove(p)) => Some(format!("{} Removing {p}", "[fs::remove]".bright_blue())),
            Fs(Copy(src, dst)) => Some(format!(
                "{} Copying {src} to {dst}",
                "[fs::copy]".bright_blue()
            )),
            Fs(Move(src, dst)) => Some(format!(
                "{} Moving {src} to {dst}",
                "[fs::move]".bright_blue()
            )),
            _ => None,
        };

        if let Some(inner) = text {
            eprintln!("{} {inner}", "[cmd]".blue());
        }
    }

    fn err(&self, errors: &[RuntimeError]) {
        use august_build::runtime::FsError::{
            CopyError, CreateDirError, CreateFileError, FileAccessError, RemoveError,
        };
        use RuntimeError::{
            CommandUnsupported, DependencyError, ExecutionFailure, FailedDependency, FsError,
            JoinPathsError,
        };

        let fs_single = |p: &Spanned<String>, io: &io::Error, message: &str| {
            Report::build(
                ReportKind::Custom("[err]", Color::Red),
                (self.file_name.clone(), p.span()),
            )
            .with_message(message)
            .with_note(io.to_string())
            .with_label(Label::new((self.file_name.clone(), p.span())).with_color(Color::Red))
            .finish()
            .eprint((self.file_name.clone(), Source::from(self.code.clone())))
            .ok();
        };

        for err in errors {
            match err {
                DependencyError(span) => {
                    eprintln!(
                        "{} Failed to complete {} due to other errors",
                        "[err]".red(),
                        span.red()
                    );
                }
                FailedDependency(parent, child) => {
                    eprintln!(
                        "{} Unable to complete unit {} due to {} failing",
                        "[err]".red(),
                        parent.cyan(),
                        child.red()
                    );
                }
                ExecutionFailure(args, io) => {
                    if !args.is_empty() {
                        let arg0 = args.first().unwrap().span();
                        let argn = args.last().unwrap().span();

                        Report::build(ReportKind::Error, (self.file_name.clone(), arg0.clone()))
                            .with_label(
                                Label::new((self.file_name.clone(), arg0.start..argn.end))
                                    .with_color(Color::Red),
                            )
                            .with_message("Failed to execute process")
                            .with_note(io.to_string())
                            .finish()
                            .eprint((self.file_name.clone(), Source::from(self.code.clone())))
                            .ok();
                    }
                }
                FsError(CreateFileError(p, io)) => {
                    fs_single(p, io, "Failed to create file");
                }
                FsError(CreateDirError(p, io)) => {
                    fs_single(p, io, "Failed to create directory");
                }
                FsError(RemoveError(p, io)) => {
                    fs_single(p, io, "Failed to remove file/directory");
                }
                FsError(FileAccessError(p, io)) => {
                    fs_single(p, io, "Unable to read the file contents");
                }
                FsError(CopyError(src, dst, io)) => {
                    Report::build(
                        ReportKind::Custom("[err]", Color::Red),
                        (self.file_name.clone(), src.span()),
                    )
                    .with_message(format!("Unable copy {} to {}", src.cyan(), dst.cyan()))
                    .with_note(io.to_string())
                    .with_label(
                        Label::new((self.file_name.clone(), src.span())).with_color(Color::Red),
                    )
                    .with_label(
                        Label::new((self.file_name.clone(), dst.span())).with_color(Color::Red),
                    )
                    .finish()
                    .eprint((self.file_name.clone(), Source::from(self.code.clone())))
                    .ok();
                }
                JoinPathsError(e) => {
                    eprintln!("{} Error occured when join to PATH: {e}", "[err]".red());
                }
                CommandUnsupported(cmd) => {
                    eprintln!(
                        "{} Command {cmd:?} is unsupported on the current runtime",
                        "[err]".red()
                    );
                }
            }
        }
    }
}

impl Notifier for LogNotifier {
    fn on_event(&self, event: NotifierEvent<'_>) {
        match event {
            NotifierEvent::Call(cmd) => self.cmd_call(cmd),
            NotifierEvent::Start(name) => {
                eprintln!("{} Begining work on unit {}", "[run]".green(), name.green());
            }
            NotifierEvent::Complete(name) => {
                eprintln!("{} Completed unit {}", "[run]".green(), name.green());
            }
            NotifierEvent::Error(err) => self.err(err),
            NotifierEvent::Dependency { parent, name } => {
                eprintln!(
                    "{} Spawning unit {} to resolve dependency of {}",
                    "[dep]".yellow(),
                    name.yellow(),
                    parent.yellow()
                );
            }
            NotifierEvent::BlockOn { parent, name } => {
                eprintln!(
                    "{} Blocking {} until {} reaches completion",
                    "[dep]".yellow(),
                    parent.yellow(),
                    name.yellow()
                );
            }
        }
    }
}