1
// generate_custom.rs with get_example_model function
2

            
3
// dependencies
4
use crate::parse::model_from_json;
5
use conjure_core::ast::Model;
6
use std::env;
7
use std::error::Error;
8
use std::fs::{copy, read_to_string, File};
9
use std::io::Write;
10
use std::path::PathBuf;
11
use walkdir::WalkDir;
12

            
13
use serde_json::Value;
14

            
15
/// Searches recursively in `../tests/integration` folder for an `.essence` file matching the given filename,
16
/// then uses conjure to process it into astjson, and returns the parsed model.
17
///
18
/// # Arguments
19
///
20
/// * `filename` - A string slice that holds filename without extension
21
///
22
/// # Returns
23
///
24
/// Function returns a `Result<Value, Box<dyn Error>>`, where `Value` is the parsed model
25
12
pub fn get_example_model(filename: &str) -> Result<Model, Box<dyn Error>> {
26
12
    // define relative path -> integration tests dir
27
12
    let base_dir = "tests/integration";
28
12
    let mut essence_path = PathBuf::new();
29

            
30
    // walk through directory tree recursively starting at base
31
1032
    for entry in WalkDir::new(base_dir).into_iter().filter_map(|e| e.ok()) {
32
1032
        let path = entry.path();
33
1032
        if path.is_file()
34
612
            && path.extension().map_or(false, |e| e == "essence")
35
228
            && path.file_stem() == Some(std::ffi::OsStr::new(filename))
36
        {
37
            essence_path = path.to_path_buf();
38
            break;
39
1032
        }
40
    }
41

            
42
12
    println!("PATH TO FILE: {}", essence_path.display());
43
12

            
44
12
    // return error if file not found
45
12
    if essence_path.as_os_str().is_empty() {
46
12
        return Err(Box::new(std::io::Error::new(
47
12
            std::io::ErrorKind::NotFound,
48
12
            "ERROR: File not found in any subdirectory",
49
12
        )));
50
    }
51

            
52
    // let path = PathBuf::from(format!("../tests/integration/basic/comprehension{}.essence", filename));
53
    let mut cmd = std::process::Command::new("conjure");
54
    let output = cmd
55
        .arg("pretty")
56
        .arg("--output-format=astjson")
57
        .arg(essence_path)
58
        .output()?;
59

            
60
    // convert Conjure's stdout from bytes to string
61
    let astjson = String::from_utf8(output.stdout)?;
62

            
63
    println!("ASTJSON: {}", astjson);
64

            
65
    // parse AST JSON from desired Model format
66
    let generated_mdl = model_from_json(&astjson)?;
67

            
68
    Ok(generated_mdl)
69

            
70
    // // search matching `.essence` files withing test_dir (similar logic to integration_test() and build.rs)
71
    // for entry in WalkDir::new(&test_dir)
72
    //     .follow_links(true)
73
    //     .into_iter()
74
    //     .filter_map(Result::ok)
75
    // {
76
    //     // check if current entry matches filename with `.essence` extension
77
    //     let path = entry.path();
78
    //     // sanity check to check for extension
79
    //     if path.is_file()
80
    //         && path.file_stem() == Some(filename.as_ref())
81
    //         && path.extension().unwrap_or_default() == "essence"
82
    //     {
83
    //         // construct conjure command
84
    //         let output = std::process::Command::new("conjure")
85
    //             .arg("pretty")
86
    //             .arg("--output-format=astjson")
87
    //             .arg(path)
88
    //             .output()?;
89

            
90
    //         // convert Conjure's stdout from bytes to string
91
    //         let astjson = String::from_utf8(output.stdout)?;
92

            
93
    //         // parse AST JSON from desired Model format
94
    //         let generated_mdl = model_from_json(&astjson)?;
95

            
96
    //         // convert and sort model
97
    //         let generated_json = sort_json_object(&serde_json::to_value(generated_mdl.clone())?);
98

            
99
    //         // serialize sorted JSON to pretty string
100
    //         let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
101

            
102
    //         // write serialized JSON to file
103
    //         File::create(path.with_extension("generated.serialised.json"))?
104
    //             .write_all(generated_json_str.as_bytes())?;
105

            
106
    //         // if ACCEPT environment var is `true`
107
    //         if env::var("ACCEPT").map_or(false, |v| v == "true") {
108
    //             copy(
109
    //                 path.with_extension("generated.serialised.json"),
110
    //                 path.with_extension("expected.serialised.json"),
111
    //             )?;
112
    //         }
113

            
114
    //         // read expected JSON model
115
    //         let expected_str = read_to_string(path.with_extension("expected.serialised.json"))?;
116

            
117
    //         // parse expected JSON string into Model format
118
    //         let expected_mdl: Value = serde_json::from_str(&expected_str)?;
119

            
120
    //         assert_eq!(generated_json, expected_mdl);
121

            
122
    //         // return expected model as final result
123
    //         return Ok(generated_mdl);
124
    //     }
125
    // }
126

            
127
    // // if no matching `.essence` file was found, return error
128
    // Err("ERROR: No matching `.essence` file was found".into())
129
12
}
130

            
131
/// Recursively sorts the keys of all JSON objects within the provided JSON value.
132
///
133
/// serde_json will output JSON objects in an arbitrary key order.
134
/// this is normally fine, except in our use case we wouldn't want to update the expected output again and again.
135
/// so a consistent (sorted) ordering of the keys is desirable.
136
fn sort_json_object(value: &Value) -> Value {
137
    match value {
138
        Value::Object(obj) => {
139
            let mut ordered: Vec<(String, Value)> = obj
140
                .iter()
141
                .map(|(k, v)| {
142
                    if k == "variables" {
143
                        (k.clone(), sort_json_variables(v))
144
                    } else {
145
                        (k.clone(), sort_json_object(v))
146
                    }
147
                })
148
                // .map(|(k, v)| (k.clone(), sort_json_object(v)))
149
                .collect();
150
            ordered.sort_by(|a, b| a.0.cmp(&b.0));
151

            
152
            Value::Object(ordered.into_iter().collect())
153
        }
154
        Value::Array(arr) => Value::Array(arr.iter().map(sort_json_object).collect()),
155
        _ => value.clone(),
156
    }
157
}
158

            
159
/// Sort the "variables" field by name.
160
/// We have to do this separately becasue that field is not a JSON object, instead it's an array of tuples.
161
fn sort_json_variables(value: &Value) -> Value {
162
    match value {
163
        Value::Array(vars) => {
164
            let mut vars_sorted = vars.clone();
165
            vars_sorted.sort_by(|a, b| {
166
                let a_obj = &a.as_array().unwrap()[0];
167
                let a_name: crate::ast::Name = serde_json::from_value(a_obj.clone()).unwrap();
168

            
169
                let b_obj = &b.as_array().unwrap()[0];
170
                let b_name: crate::ast::Name = serde_json::from_value(b_obj.clone()).unwrap();
171

            
172
                a_name.cmp(&b_name)
173
            });
174
            Value::Array(vars_sorted)
175
        }
176
        _ => value.clone(),
177
    }
178
}