do_nothing 0.1.0

A gradual automation scripting framework
Documentation
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
use std::io;
use std::io::Write;

#[derive(PartialEq, Debug)]
pub struct Procedure {
    title: String,
    description: String,
    steps: Vec<Step>,
}

impl Procedure {
    pub fn new(title: &str, description: &str) -> Self {
        Procedure {
            title: title.to_string(),
            description: description.to_string(),
            steps: vec![],
        }
    }

    pub fn add_manual_step(mut self, title: &str, instructions: &str) -> Self {
        let new_step = Step::new_manual(title, instructions);
        self.steps.push(new_step);
        self
    }

    pub fn add_automated_step<F>(mut self, title: &str, automation: F) -> Self
    where
        F: Fn(HashMap<String, String>) -> HashMap<String, String> + 'static,
    {
        let new_step = Step::new_automated(title, Box::new(automation));
        self.steps.push(new_step);
        self
    }

    pub fn execute(self) {
        println!("# {}\n", self.title);
        println!("{}\n", self.description);
        print!("Press [Enter] to begin ");
        std::io::stdout().flush().expect("failed");
        let mut _input: String = String::new();
        io::stdin().read_line(&mut _input).expect("failed!");
        println!();
        let state = HashMap::new();
        self.steps.iter().fold(state, handle_step);
        println!("done!");
    }
}

fn handle_step(state: HashMap<String, String>, step: &Step) -> HashMap<String, String> {
    match step {
        Step::Automated(automated_step) => {
            println!("## {}\n", automated_step.title);
            (automated_step.automation)(state)
        }
        Step::Manual(manual_step) => {
            println!("## {}\n", manual_step.title);
            println!("{}\n", manual_step.instructions);
            print!("Press [Enter] to continue ");
            std::io::stdout().flush().expect("failed");
            let mut _input: String = String::new();
            io::stdin().read_line(&mut _input).expect("failed!");
            println!();
            println!("hello");
            state
        }
    }
}

#[derive(PartialEq, Debug)]
enum Step {
    Manual(ManualStep),
    Automated(AutomatedStep),
}

impl Step {
    pub fn new_manual(title: &str, instructions: &str) -> Step {
        Step::Manual(ManualStep {
            title: title.to_string(),
            instructions: instructions.to_string(),
        })
    }
    pub fn new_automated(title: &str, automation: Automation) -> Step {
        Step::Automated(AutomatedStep {
            title: title.to_string(),
            automation,
        })
    }
}

#[derive(PartialEq, Debug)]
struct ManualStep {
    title: String,
    instructions: String,
}

pub type Automation = Box<dyn Fn(HashMap<String, String>) -> HashMap<String, String>>;

struct AutomatedStep {
    title: String,
    automation: Automation,
}

impl PartialEq for AutomatedStep {
    fn eq(&self, other: &Self) -> bool {
        self.title == other.title
    }
}

impl Debug for AutomatedStep {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AutomatedStep")
            .field("title", &self.title)
            .finish()
    }
}

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

    #[test]
    fn it_works() {
        let p = Procedure::new("hello", "world");
        assert_eq!(
            Procedure {
                title: "hello".to_string(),
                description: "world".to_string(),
                steps: vec![]
            },
            p
        );
    }

    #[test]
    fn procedure_with_manual_step() {
        let p = Procedure::new("hello", "world").add_manual_step("step", "instructions");
        assert_eq!(
            Procedure {
                title: "hello".to_string(),
                description: "world".to_string(),
                steps: vec![Step::Manual(ManualStep {
                    title: "step".to_string(),
                    instructions: "instructions".to_string()
                })],
            },
            p
        );
    }
}