1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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(())
}