summaryrefslogtreecommitdiff
path: root/src/codegen.rs
blob: c1da2f8896e758c99632dbb74a8da06e7c1aa63e (plain)
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
use crate::{Document, SubplotError, TemplateSpec};
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::{Read, Write};
use std::path::{Path, PathBuf};

use base64::encode;

use serde::Serialize;
use tera::{Context, Tera, Value};

use anyhow::Result;

/// Return the requested template specification.
pub fn template_spec(templates: &Path, doc: &Document) -> Result<TemplateSpec> {
    let template = match doc.meta().template_name() {
        Some(x) => &x,
        None => "python",
    };

    let mut filename = templates.to_path_buf();
    filename.push(Path::new(template));
    filename.push(Path::new("template.yaml"));
    Ok(TemplateSpec::from_file(&filename)?)
}

/// Generate a test program from a document, using a template spec.
pub fn generate_test_program(
    doc: &mut Document,
    spec: &TemplateSpec,
    filename: &Path,
) -> Result<()> {
    let context = context(doc)?;
    let tera = tera(&spec)?;
    let code = tera.render("template", &context).expect("render");
    write(filename, &code)?;
    Ok(())
}

fn context(doc: &mut Document) -> Result<Context> {
    let mut context = Context::new();
    context.insert("scenarios", &doc.matched_scenarios()?);
    context.insert("files", doc.files());

    let funcs_filenames = doc.meta().functions_filenames();
    let mut funcs = vec![];
    for filename in funcs_filenames {
        let content =
            cat(filename).map_err(|e| SubplotError::FunctionsFileNotFound(filename.into(), e))?;
        funcs.push(Func::new(filename, content));
    }
    context.insert("functions", &funcs);

    Ok(context)
}

fn tera(tmplspec: &TemplateSpec) -> Result<Tera> {
    // Tera insists on a glob, but we want to load a specific template
    // only, so we use a glob that doesn't match anything.
    let mut tera = Tera::new(&"*notexist").expect("new");
    tera.register_filter("base64", base64);
    tera.add_template_file(tmplspec.template_filename(), Some("template"))?;
    Ok(tera)
}

fn cat<P: AsRef<Path>>(filename: P) -> Result<String> {
    let mut f = File::open(filename)?;
    let mut buf = String::new();
    f.read_to_string(&mut buf)?;
    Ok(buf)
}

fn write(filename: &Path, content: &str) -> Result<()> {
    let mut f: File = File::create(filename)?;
    f.write_all(&content.as_bytes())?;
    Ok(())
}

fn base64(v: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
    match v {
        Value::String(s) => Ok(Value::String(encode(s))),
        _ => Err(tera::Error::msg(
            "can only base64 encode strings".to_string(),
        )),
    }
}

#[derive(Debug, Serialize)]
pub struct Func {
    pub source: PathBuf,
    pub code: String,
}

impl Func {
    pub fn new(source: &Path, code: String) -> Func {
        Func {
            source: source.to_path_buf(),
            code,
        }
    }
}