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
140
141
142
143
144
145
146
// Copyright 2015-2017 Intecture Developers. See the COPYRIGHT file at the
// top-level directory of this distribution and at
// https://intecture.io/COPYRIGHT.
//
// Licensed under the Mozilla Public License 2.0 <LICENSE or
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
// modified, or distributed except according to those terms.

//! Template primitive.

pub mod ffi;

use error::Result;
use error::Error;
use mustache;
use rustc_serialize::Encodable;
use std::convert::Into;
use std::fs;
use std::io::{Seek, SeekFrom};
use std::path::Path;
use tempfile::tempfile;

/// Primitive for rendering templates into uploadable files.
///
///# Examples
///
/// ```no_run
/// # use inapi::{MapBuilder, Template};
/// let template = Template::new("/path/to/template").unwrap();
/// let data = MapBuilder::new().insert_str("key", "value").build();
/// let rendered_file = template.render_data(&data).unwrap();
/// ```
#[cfg_attr(feature = "remote-run", doc = "")]
#[cfg_attr(feature = "remote-run", doc = "To upload the rendered file to your host, you can pass it")]
#[cfg_attr(feature = "remote-run", doc = "straight into the `File` primitive:")]
#[cfg_attr(feature = "remote-run", doc = "")]
#[cfg_attr(feature = "remote-run", doc = "```no_run")]
#[cfg_attr(feature = "remote-run", doc = "# use inapi::{File, Host, MapBuilder, Template};")]
#[cfg_attr(feature = "remote-run", doc = "# let template = Template::new(\"/path/to/template\").unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let data = MapBuilder::new().insert_str(\"key\", \"value\").build();")]
#[cfg_attr(feature = "remote-run", doc = "# let rendered_file = template.render_data(&data).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "")]
#[cfg_attr(feature = "remote-run", doc = "let file = File::new(&mut host, \"/path/to/remote/file\").unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "file.upload_file(&mut host, rendered_file, None).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "```")]
pub struct Template {
    inner: mustache::Template,
}

impl Template {
    /// Create a new `File` struct.
    pub fn new<P: AsRef<Path>>(path: P) -> Result<Template> {
        if !path.as_ref().exists() {
            return Err(Error::Generic("Template path does not exist".into()));
        }

        Ok(Template {
            inner: try!(mustache::compile_path(path.as_ref())),
        })
    }

    /// Render template to file using generic `Encodable` data.
    pub fn render<T: Encodable>(&self, data: &T) -> Result<fs::File> {
        let mut fh = try!(tempfile());
        try!(self.inner.render(&mut fh, data));

        // Reset cursor to beginning of file for reading
        try!(fh.seek(SeekFrom::Start(0)));
        Ok(fh)
    }

    /// Render template to file using a `Data` instance.
    pub fn render_data(&self, data: &mustache::Data) -> Result<fs::File> {
        let mut fh = try!(tempfile());
        self.inner.render_data(&mut fh, data)?;

        // Reset cursor to beginning of file for reading
        try!(fh.seek(SeekFrom::Start(0)));
        Ok(fh)
    }
}

#[cfg(test)]
mod tests {
    use mustache::MapBuilder;
    use std::fs;
    use std::io::{Read, Write};
    use super::*;
    use tempdir::TempDir;

    #[test]
    fn test_new() {
        let tempdir = TempDir::new("template_test_render").unwrap();
        let template_path = format!("{}/template.mustache", tempdir.path().to_str().unwrap());

        assert!(Template::new(&template_path).is_err());

        fs::File::create(&template_path).unwrap();
        Template::new(&template_path).unwrap();
        assert!(Template::new(&template_path).is_ok());
    }

    #[test]
    fn test_render() {
        let tempdir = TempDir::new("template_test_render").unwrap();
        let template_path = format!("{}/template.mustache", tempdir.path().to_str().unwrap());

        let template_str = "Hello, {{name}}!".to_string();
        let data = TestData {
            name: "Jasper Beardly"
        };

        let mut fh = fs::File::create(&template_path).unwrap();
        fh.write_all(template_str.as_bytes()).unwrap();

        let template = Template::new(&template_path).unwrap();
        let mut fh = template.render(&data).unwrap();
        let mut content = String::new();
        fh.read_to_string(&mut content).unwrap();
        assert_eq!(content, "Hello, Jasper Beardly!");
    }

    #[test]
    fn test_render_data_to_file() {
        let tempdir = TempDir::new("template_test_render").unwrap();
        let template_path = format!("{}/template.mustache", tempdir.path().to_str().unwrap());

        let template_str = "Hello, {{name}}!".to_string();
        let data = MapBuilder::new().insert_str("name", "Jasper Beardly").build();

        let mut fh = fs::File::create(&template_path).unwrap();
        fh.write_all(template_str.as_bytes()).unwrap();

        let template = Template::new(&template_path).unwrap();
        let mut fh = template.render_data(&data).unwrap();
        let mut content = String::new();
        fh.read_to_string(&mut content).unwrap();
        assert_eq!(content, "Hello, Jasper Beardly!");
    }

    #[derive(RustcEncodable)]
    struct TestData {
        name: &'static str,
    }
}