blue-lang-cli 0.0.223

The blue command line: run, fmt, ast, erase, check.
use std::path::Path;

use blue_lang_runtime::door::{Door, Ran};
use blue_lang_runtime::pipeline::Executor;

pub(crate) const ENV: &str = "BLUE_DOOR";

#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
pub(crate) enum DoorFlag {
    Auto,
    Walker,
    Vm,
}

impl DoorFlag {
    fn parse(value: &str) -> Option<Self> {
        match value.trim().to_ascii_lowercase().as_str() {
            "" | "auto" => Some(DoorFlag::Auto),
            "walker" | "off" => Some(DoorFlag::Walker),
            "vm" => Some(DoorFlag::Vm),
            _ => None,
        }
    }
}

pub(crate) fn executor(
    vm: bool,
    flag: Option<DoorFlag>,
    env: Option<&str>,
) -> Result<Executor, String> {
    let chosen = match (vm, flag, env) {
        (true, Some(DoorFlag::Walker), _) => {
            return Err("`--vm` and `--door walker` name two doors; pass one".into())
        }
        (true, _, _) => DoorFlag::Vm,
        (false, Some(f), _) => f,
        (false, None, Some(v)) => DoorFlag::parse(v).ok_or_else(|| {
            format!("{ENV}=\"{v}\" names no door; it is auto, walker (or off) or vm")
        })?,
        (false, None, None) => DoorFlag::Auto,
    };
    Ok(match chosen {
        DoorFlag::Auto => Executor::Auto,
        DoorFlag::Walker => Executor::Walker,
        DoorFlag::Vm => Executor::Vm,
    })
}

pub(crate) struct Held {
    pub memo: bool,
    pub steps: bool,
    pub taped: bool,
    pub watched: bool,
}

impl Held {
    fn reason(&self) -> Option<&'static str> {
        if self.memo {
            Some("the memo is on, and it answers calls on the walker only")
        } else if self.steps {
            Some("--receipt-steps counts the walker's steps")
        } else if self.taped {
            Some("a recorded or replayed run is taped on the walker")
        } else if self.watched {
            Some("a watched run observes the walker's calls")
        } else {
            None
        }
    }
}

pub(crate) fn settle(
    executor: Executor,
    held: &Held,
    account: &blue_lang_runtime::Account,
) -> Executor {
    match (executor, held.reason()) {
        (Executor::Auto, Some(why)) => {
            if let Ok(mut slot) = account.door.lock() {
                *slot = Some(Door::chosen(Ran::Walker, why));
            }
            Executor::Walker
        }
        (e, _) => e,
    }
}

pub(crate) fn write(file: &Path, door: &Door) {
    let text = blue_lang_proc::receipt::canonical(&door.to_json());
    let written = file
        .parent()
        .filter(|d| !d.as_os_str().is_empty())
        .map_or(Ok(()), std::fs::create_dir_all)
        .and_then(|()| std::fs::write(file, text));
    if let Err(e) = written {
        eprintln!(
            "blue: the run's door could not be written to {}: {e}",
            file.display()
        );
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_flag_beats_the_environment_and_auto_is_the_default() {
        assert_eq!(executor(false, None, None), Ok(Executor::Auto));
        assert_eq!(executor(false, None, Some("walker")), Ok(Executor::Walker));
        assert_eq!(executor(false, None, Some("off")), Ok(Executor::Walker));
        assert_eq!(
            executor(false, Some(DoorFlag::Vm), Some("walker")),
            Ok(Executor::Vm)
        );
        assert_eq!(executor(true, None, Some("walker")), Ok(Executor::Vm));
        assert!(executor(true, Some(DoorFlag::Walker), None).is_err());
        assert!(executor(false, None, Some("fast")).is_err());
    }

    #[test]
    fn a_held_run_stays_on_the_walker_and_names_why() {
        let account = blue_lang_runtime::Account::default();
        let held = Held {
            memo: true,
            steps: false,
            taped: false,
            watched: false,
        };
        assert_eq!(settle(Executor::Auto, &held, &account), Executor::Walker);
        let door = account.door.lock().unwrap().clone().unwrap();
        assert_eq!(door.ran, Ran::Walker);
        assert!(door.why.contains("memo"));
        let free = Held {
            memo: false,
            steps: false,
            taped: false,
            watched: false,
        };
        assert_eq!(
            settle(
                Executor::Auto,
                &free,
                &blue_lang_runtime::Account::default()
            ),
            Executor::Auto
        );
        assert_eq!(
            settle(Executor::Vm, &held, &blue_lang_runtime::Account::default()),
            Executor::Vm
        );
    }
}