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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! The `conductor new` command, and any other file generators.

use rustc_serialize::json::{Json, ToJson};
use std::collections::BTreeMap;
#[cfg(test)]
use std::env;
#[cfg(test)]
use std::fs;
use std::io;
use std::path::{PathBuf, Path};

use project::Project;
use template::Template;
use util::Error;

/// A list of standard overrides to generate.
const OVERRIDES: &'static [&'static str] = &["development", "production", "test"];

/// Interface to various file-generation commands.
pub trait CommandGenerate {
    /// Create a new conductor project skeleton, returning the path of the
    /// generated project.
    ///
    /// ```text
    /// <name>
    /// └── pods
    ///   ├── common.env
    ///   ├── frontend.yml
    ///   └── overrides
    ///       ├── development
    ///       │   └── common.env
    ///       ├── production
    ///       │   ├── common.env
    ///       └── test
    ///           └── common.env
    /// ```
    fn generate_new(parent_dir: &Path, name: &str) -> Result<PathBuf, Error>;

    /// Print our all available generators (excluding the `generate_new`
    /// generator).
    fn generate_list(&self) -> Result<(), Error>;

    /// Run the specified generator.
    fn generate(&self, name: &str) -> Result<(), Error>;
}

impl CommandGenerate for Project {
    fn generate_new(parent_dir: &Path, name: &str) -> Result<PathBuf, Error> {
        let proj_dir = parent_dir.join(name);

        // Generate our top-level files.
        let mut proj_tmpl = try!(Template::new("new"));
        let proj_info = ProjectInfo { name: name };
        try!(proj_tmpl.generate(&proj_dir, &proj_info, &mut io::stdout()));

        // Generate files for each override.
        let mut ovr_tmpl = try!(Template::new("new/pods/_overrides/_default"));
        let overrides_dir = proj_dir.join("pods").join("overrides");
        for ovr in OVERRIDES {
            let ovr_info = OverrideInfo {
                project: &proj_info,
                name: ovr,
            };
            let dir = overrides_dir.join(ovr);
            try!(ovr_tmpl.generate(&dir, &ovr_info, &mut io::stdout()));
        }

        Ok(proj_dir)
    }

    fn generate_list(&self) -> Result<(), Error> {
        for generator in self.plugins().generators() {
            println!("{:19} {}",
                     generator.name(),
                     generator.generator_description());
        }
        Ok(())
    }

    fn generate(&self, name: &str) -> Result<(), Error> {
        self.plugins().generate(self, name, &mut io::stdout())
    }
}

#[test]
fn generate_new_creates_a_project() {
    let cwd = env::current_dir().unwrap();
    Project::generate_new(&cwd, "test_project").unwrap();
    let proj_dir = env::current_dir().unwrap().join("test_project");

    assert!(proj_dir.exists());
    assert!(proj_dir.join("pods/common.env").exists());
    assert!(proj_dir.join("pods/frontend.yml").exists());
    assert!(proj_dir.join("pods/db.yml").exists());
    assert!(proj_dir.join("pods/overrides/development/common.env").exists());
    assert!(proj_dir.join("pods/overrides/production/common.env").exists());
    assert!(proj_dir.join("pods/overrides/test/common.env").exists());

    fs::remove_dir_all(&proj_dir.as_path()).unwrap();
}


/// Information about the project we're generating.  This will be passed to
/// our templates.
#[derive(Debug)]
struct ProjectInfo<'a> {
    /// The name of this project.
    name: &'a str,
}

// Convert to JSON for use in a Handlebars template.  We could get
// Handlebars and serde to convert to JSON automatically, but it's less
// work to define it by hand.
impl<'a> ToJson for ProjectInfo<'a> {
    fn to_json(&self) -> Json {
        let mut info: BTreeMap<String, Json> = BTreeMap::new();
        info.insert("name".to_string(), self.name.to_json());
        info.to_json()
    }
}

/// Information about the override we're generating.  This will be passed
/// to our templates.
#[derive(Debug)]
struct OverrideInfo<'a> {
    /// The project in which this override appears.
    project: &'a ProjectInfo<'a>,
    /// The name of this override.
    name: &'a str,
}

impl<'a> ToJson for OverrideInfo<'a> {
    fn to_json(&self) -> Json {
        let mut info: BTreeMap<String, Json> = BTreeMap::new();
        info.insert("project".to_string(), self.project.to_json());
        info.insert("name".to_string(), self.name.to_json());
        info.to_json()
    }
}