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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// @Author: Lashermes Ronan <ronan>
// @Date:   11-07-2017
// @Email:  ronan.lashermes@inria.fr
// @Last modified by:   ronan
// @Last modified time: 25-07-2017
// @License: MIT

use std::path::{Path, PathBuf};
use std::fs;
use std::io::{Read, Write};
use std::env;
use std::collections::HashMap;

use app_dirs::*;
use errors::*;

use packer::packer_functions::{ActionContext, ActionType};

const USER_DATA: &str = "USER_DATA";
const USER_CONFIG: &str = "USER_CONFIG";
const USER_CACHE: &str = "USER_CACHE";
const SHARED_DATA: &str = "SHARED_DATA";
const SHARED_CONFIG: &str = "SHARED_CONFIG";

const RELEASE_BINARY: &str = "RELEASE_BINARY";
const BINARY: &str = "BINARY";
const DEBUG_BINARY: &str = "DEBUG_BINARY";
const APP_NAME: &str = "APP_NAME";

const HOME: &str = "HOME";

const DOLLAR: &str = "$";


//Ok, the var application is a bit complex.
//the goal is to replace some specific strings $KEYWORD with the replacement value computed just in time
//e.g. $HOME -> /home/user with user name at install
//to do that efficiently, we store closures that compute the replacement value into a hashmap
//with the variable (without $) name as key
//then we just have to get the closure at runtime, apply it with the current context and voilà!

type VarTransform = Fn(&ActionContext) -> (Result<String>) + Sync;

lazy_static! {
    static ref PACK_VARS: HashMap<String, Box<VarTransform>> = {
        ///Add a new closure into a hashmap
        fn add_closure<T>(hm: &mut HashMap<String, Box<VarTransform>>, key: String, closure: T)
            where T:Fn(&ActionContext) -> (Result<String>) + Sync + 'static {
            hm.insert(key, Box::new(closure));
        }
        let mut m = HashMap::new();
        //home
        add_closure(&mut m, HOME.to_string(), |_: &ActionContext| {
            let path_res: Result<PathBuf> = env::home_dir().ok_or("No home dir found.".into());
            match path_res {
                Ok(path) => Ok(format!("{}", path.display())),
                Err(e) => Err(e)
            }
        });
        //binaries
        add_closure(&mut m, BINARY.to_string(), |ctx: &ActionContext| {
            let mut pathbuf = ctx.get_root_path().to_path_buf();
            pathbuf.push("target");
            pathbuf.push("release");
            pathbuf.push(&ctx.get_app_info().name);

            match pathbuf.to_str() {
                Some(s) => Ok(s.to_string()),
                None => Err("No binary path found.".into())
            }
        });
        add_closure(&mut m, APP_NAME.to_string(), |ctx: &ActionContext| {
            Ok(ctx.get_app_info().name.to_string())
        });
        add_closure(&mut m, RELEASE_BINARY.to_string(), |ctx: &ActionContext| {
            let mut pathbuf = ctx.get_root_path().to_path_buf();
            pathbuf.push("target");
            pathbuf.push("release");
            pathbuf.push(&ctx.get_app_info().name);

            match pathbuf.to_str() {
                Some(s) => Ok(s.to_string()),
                None => Err("No binary path found.".into())
            }
        });
        add_closure(&mut m, DEBUG_BINARY.to_string(), |ctx: &ActionContext| {
            let mut pathbuf = ctx.get_root_path().to_path_buf();
            pathbuf.push("target");
            pathbuf.push("debug");
            pathbuf.push(&ctx.get_app_info().name);

            match pathbuf.to_str() {
                Some(s) => Ok(s.to_string()),
                None => Err("No binary path found.".into())
            }
        });
        m
    };

    static ref INSTALL_VARS: HashMap<String, Box<VarTransform>> = {
        ///Add a new closure into a hashmap
        fn add_closure<T>(hm: &mut HashMap<String, Box<VarTransform>>, key: String, closure: T)
            where T:Fn(&ActionContext) -> (Result<String>) + Sync + 'static {
            hm.insert(key, Box::new(closure));
        }

        let mut m = HashMap::new();
        //home
        add_closure(&mut m, HOME.to_string(), |_: &ActionContext| {
            let path_res: Result<PathBuf> = env::home_dir().ok_or("No home dir found.".into());
            match path_res {
                Ok(path) => Ok(format!("{}", path.display())),
                Err(e) => Err(e)
            }
        });

        //APP_NAME
        add_closure(&mut m, APP_NAME.to_string(), |ctx: &ActionContext| {
            Ok(ctx.get_app_info().name.to_string())
        });

        //App_dirs
        add_closure(&mut m, USER_DATA.to_string(), |ctx: &ActionContext| {
            let dir = get_app_root(AppDataType::UserData, ctx.get_app_info())
                        .map_err(|_|format!("No user data dir found for {}.", ctx.get_app_info().name))?;
            dir.to_str().and_then(|s|Some(s.to_string())).ok_or("No user data folder found.".into())
        });
        add_closure(&mut m, USER_CONFIG.to_string(), |ctx: &ActionContext| {
            let dir = get_app_root(AppDataType::UserConfig, ctx.get_app_info())
                        .map_err(|_|format!("No user config dir found for {}.", ctx.get_app_info().name))?;
            dir.to_str().and_then(|s|Some(s.to_string())).ok_or("No user config folder found.".into())
        });
        add_closure(&mut m, USER_CACHE.to_string(), |ctx: &ActionContext| {
            let dir = get_app_root(AppDataType::UserCache, ctx.get_app_info())
                        .map_err(|_|format!("No user cache dir found for {}.", ctx.get_app_info().name))?;
            dir.to_str().and_then(|s|Some(s.to_string())).ok_or("No user cache folder found.".into())
        });
        add_closure(&mut m, SHARED_DATA.to_string(), |ctx: &ActionContext| {
            let dir = get_app_root(AppDataType::SharedData, ctx.get_app_info())
                        .map_err(|_|format!("No shared data dir found."))?;
            dir.to_str().and_then(|s|Some(s.to_string())).ok_or("No shared data folder found.".into())
        });
        add_closure(&mut m, SHARED_CONFIG.to_string(), |ctx: &ActionContext| {
            let dir = get_app_root(AppDataType::SharedConfig, ctx.get_app_info())
                        .map_err(|_|format!("No shared config found."))?;
            dir.to_str().and_then(|s|Some(s.to_string())).ok_or("No shared config folder found.".into())
        });
        m
    };
}

pub fn store_data_in_file<T: AsRef<Path>>(data: &[u8], path: T) -> Result<()> {
    let mut file = fs::File::create(path).map_err(|e|e.to_string())?;
    file.write_all(data).map_err(|e|e.to_string())?;
    Ok(())
}

pub fn read_data_from_file<T: AsRef<Path>>(path: T) -> Result<Vec<u8>> {
    let mut file = fs::File::open(path).map_err(|e|e.to_string())?;
    let mut file_data: Vec<u8> = Vec::new();
    file.read_to_end(&mut file_data).map_err(|e|e.to_string())?;
    Ok(file_data)
}


pub fn apply_variables(input: &str, ctx: &ActionContext) -> Result<String> {
    let mut result = String::new();
    let mut working = input.to_string();

    let vars_hm: &HashMap<String, Box<VarTransform>> = match ctx.get_action_type() {
        ActionType::Pack => &PACK_VARS,
        _ => &INSTALL_VARS
    };

    while let Some(i) = working.find(DOLLAR) {
        let mut rest = working.split_off(i); //working = [0:i[
        rest = rest.split_off(DOLLAR.len());
        result += &working;

        for var_name in vars_hm.keys() {
            if rest.starts_with(var_name) {
                let f = vars_hm.get(var_name).ok_or(format!("Var {} not found.", var_name))?;
                result += &f(ctx)?;
                rest = rest.split_off(var_name.len());
                break;
            }
        }

        working = rest;
    }
    result += &working;

    debug!("{} transformed to {}", input, result);
    Ok(result)
}

#[test]
fn test_vars() {
    let root_path = fs::canonicalize("./").unwrap();
    let app_info = OwnedAppInfo { name: String::from("test"), author: String::from("tester") };
    let ctx = ActionContext::new(ActionType::Pack, &app_info, &root_path, true);

    let test_vectors = vec!["$BINARY", "$HOME/.cargo/bin/$FILENAME"];

    for test_vector in test_vectors.iter() {
        // println!("Transformed: {}", apply_variables(test_vector, &ctx).unwrap());
        assert!(apply_variables(test_vector, &ctx).is_ok());
    }

}