sicpgen 0.1.1

A simple CLI tool for generating exercise templates for SICP
Documentation
use itertools::Itertools;
#[macro_use] extern crate lazy_static;
use tera::{Context, Tera};
use std::fmt;
use std::path::PathBuf;
use std::fs::create_dir_all;
use failure::Fail;

lazy_static! {
    pub static ref TEMPLATES: Tera = {
        match Tera::new("src/templates/**/*.tpl") {
            Ok(t) => t,
            Err(e) => {
                println!("Parsing error(s): {}", e);
                std::process::exit(1);
            },
        }
    };
}

#[derive(Debug, Fail)]
pub enum GenerationError {
    RootDoesNotExistError(PathBuf),
    ExerciseExists(PathBuf)
}

impl fmt::Display for GenerationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let msg = match self {
            GenerationError::RootDoesNotExistError(v) => format!("Path provided ({}) doesn\'t exist", v.to_str().unwrap()),
            GenerationError::ExerciseExists(v) => format!("Exercise {} already exists", v.display()),
        };

        write!(f, "{}", msg)
    }
}

#[derive(Debug, Fail)]
pub struct TestingError {
}

impl fmt::Display for TestingError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Failed to run test suite")
    }
}

#[derive(Debug)]
pub struct Exercise {
    chapter: String,
    exercise: String,
    path: PathBuf,
}

impl fmt::Display for Exercise {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "chapter {} exercise {}", self.chapter, self.exercise)
    }
}

impl Exercise {
    fn new(root: &PathBuf, number: &str) -> Exercise {
        let (chapter, exercise_number) = number.split('-').collect_tuple().unwrap();
        let path = root.join(format!("chapter-{}", chapter)).join(format!("exercise-{}", exercise_number));
        Exercise { chapter: String::from(chapter), exercise: String::from(exercise_number), path }
    }

    fn generate(&self) -> Result<(), GenerationError> {
        if self.path.exists() {
            return Err(GenerationError::ExerciseExists((*self.path).to_path_buf()));
        }

        if let Some(v) = self.path.to_str() {
            create_dir_all(v).unwrap();
        }

        self.generate_readme().unwrap();
        self.generate_test().unwrap();

        Ok(())
    }

    fn generate_readme(&self) -> std::io::Result<()> {
        let mut context = Context::new();
        context.insert("chapter", &self.chapter);
        context.insert("exercise", &self.exercise);
        let content = TEMPLATES.render("readme.md.tpl", &context).unwrap();
        let filename = self.path.join("README.md");
        std::fs::write(filename, content)
    }

    fn generate_test(&self) -> std::io::Result<()> {
        let context = Context::new();
        let content = TEMPLATES.render("test.rkt.tpl", &context).unwrap();
        let filename = self.path.join("test.rkt");
        std::fs::write(filename, content)
    }
}

pub fn generate(root: &PathBuf, number: &str) -> Result<(), GenerationError> {
    if !root.exists() {
        return Err(GenerationError::RootDoesNotExistError(root.to_owned()));
    }

    Exercise::new(root, number).generate()
}

pub fn test(_root: &PathBuf, _number: &str) -> Result<(), TestingError> {
    Ok(())
}

#[test]
fn test_generate_exercise() -> Result<(), std::io::Error> {
    use tempfile::tempdir;
    let root = tempdir()?.into_path();

    generate(&root, "1-1").unwrap();

    let exercise_root = root.join("chapter-1").join("exercise-1");
    assert_eq!(exercise_root.exists(), true);
    assert_eq!(exercise_root.join("README.md").exists(), true);
    assert_eq!(exercise_root.join("test.rkt").exists(), true);

    Ok(())
}