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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
//! A library which easily allows you to set ENV variables in your process through a file.
//! 
//! The library is extremly simple, but also customizeable and it allows you to set your own value delimiter and comment style.
//! You can use external config files such as .dotenv too. All you have to do is specify which delimiter and comment style need to be used.


//! # Example
//! 
//! ### Cargo.toml
//! ```toml
//! [dependenices]
//! env_plus = "0.1.2"
//! ```
//! ### .env_plus
//! ```
//! // This is a comment!
//! SECRET=YOUR_SECRET
//! ```

//! ### main<nolink>.rs
//! ```rust
//! use env_plus::EnvLoader;
//! 
//! fn main() {
//!     EnvLoader::new()
//!     .activate();
//!
//!     let secret = std::env::var("SECRET").unwrap();
//!     assert_eq!(secret, String::from("YOUR_SECRET"));
//! }
//! ```
//! 
//! For more advanced usage, please look at the documentation for each method 
//! on the EnvLoader struct. There're plenty of examples of how to use this
//! crate.<br />


use std::fs;
mod tests;


/// The entry point of the library
/// 
/// The EnvLoader uses few default values which are listed below
/// * A file - '.env_plus' which is relative to the current directory. You can specify your own file with fn change_file
/// * A comment style - '//' which makes the program ignore everything after it. By putting it on the beginning of the line, the whole line is marked as a comment
/// * Delimiter - '=' once placed in each line, everything on the left is marked as a key and on the right as value (only the first = is used). Change with fn change_delimiter
/// * Overwrite - bool (default false) which specifies if the already exisiting ENVs will be replaced or not if you have a var with the same name. Change with fn overwrite_envs
#[derive(Clone)]
pub struct EnvLoader {
    file: String,
    comment: String,
    value_delimiter: String,
    overwrite: bool
}


impl EnvLoader {

    /// Create a new EnvLoader instance.
    /// 
    /// # Examples
    /// 
    /// ```
    /// // .env_plus
    /// 
    /// // Double slash is used for commenting, equal sign is used for assiging a value by default.
    /// SECRET=YOUR_SECRET
    /// ```
    /// 
    /// ``` 
    /// // main.rs
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     EnvLoader::new()
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    /// 
    pub fn new() -> EnvLoader {
        EnvLoader {
            file: String::from("./.env_plus"),
            comment: String::from("//"),
            value_delimiter: String::from("="),
            overwrite: false,
        }
    }

    /// Changes the file which will be used to parse ENVs from. Any file can be used as long as you set its 
    /// comment style and delimiter. 
    /// 
    /// # Examples
    /// 
    /// ```
    /// // my_special_file.extension
    /// 
    /// SECRET=YOUR_SECRET
    /// ```
    /// 
    /// ```
    /// // main.rs
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     EnvLoader::new()
    ///     .change_file(String::from("./my_special_file.extension"))
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    /// 
    ///     // YOUR_SECRET will be in your special file which we loaded above. 
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    pub fn change_file(mut self, path: String) -> Self {
        self.file = path;

        self
    }

    /// Sets a new value to be marked as a comment in the file and not 
    /// be loaded.
    /// 
    /// # Examples
    /// 
    /// ```
    /// // .env_plus 
    /// 
    /// --This is a now a comment
    /// SECRET=YOUR_SECRET
    /// ```
    /// 
    /// ```
    /// // main.rs
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     EnvLoader::new()
    ///     .change_comment(String::from("--"))
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    pub fn change_comment(mut self, comment: String) -> Self {
        self.comment = comment;

        self
    }

    /// Change the delimiter that will be used to parse the file lines.
    /// The default delimiter is =
    /// 
    /// # Examples
    /// 
    /// ```
    /// // .env_plus
    /// 
    /// SECRET===YOUR_SECRET
    /// ```
    /// 
    /// ```
    /// // main.rs 
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     EnvLoader::new()
    ///     .change_delimiter(String::from("==="))
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    pub fn change_delimiter(mut self, delimiter: String) ->  Self {
        self.value_delimiter = delimiter;

        self
    }

    /// If true is passed, all current ENV vars that have the same names as the ones in 
    /// the file will be overwritten, otherwise they won't.
    /// 
    /// # Examples
    /// 
    /// ```
    /// // .env_plus
    /// 
    /// SECRET=YOUR_SECRET
    /// ```
    /// 
    /// ```
    /// // main.rs
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     std::env::set_var("SECRET", "MY_SECRET");
    /// 
    ///     EnvLoader::new()
    ///     .overwrite_envs(true)
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    pub fn overwrite_envs(mut self, overwrite: bool) -> Self {
        self.overwrite = overwrite;

        self
    }


    /// Activate the module and load your ENV file.
    /// 
    /// # Examples 
    /// 
    /// ```
    /// // special.env 
    /// 
    /// @ I really love my comment design.
    /// SECRET||YOUR_SECRET
    /// ```
    /// 
    /// ```
    /// // main.rs 
    /// use env_plus::EnvLoader;
    /// 
    /// fn main() {
    ///     EnvLoader::new()
    ///     .change_delimiter(String::from("||"))
    ///     .change_comment(String::from("@"))
    ///     .change_file(String::from("./special.env"))
    ///     .activate();
    /// 
    ///     let secret = std::env::var("SECRET").unwrap();
    ///     assert_eq!(secret, String::from("YOUR_SECRET"));
    /// }
    /// ```
    pub fn activate(self) {
        let files_loaded = load_file(self);

        if !files_loaded {
            eprintln!("An error has occured")
        }
    }
}


fn load_file(envs: EnvLoader) -> bool {
    let file = fs::read_to_string(envs.file); // Add a correct error handling

    if file.is_err() {
        println!("{:?}", file.err());
        false

    } else {
        let unwraped_file = file.unwrap();
        let file_lines = unwraped_file.lines();

        for (ind, line) in file_lines.enumerate() {
            load_line(line, &envs.comment, &envs.value_delimiter, &envs.overwrite, ind);
        };

        true
    }

}


fn load_line(line: &str, comment: &String, delimiter: &String, overwrite: &bool, ind: usize) {
    if line.trim().starts_with(comment) || line.trim() == "" { return };

    let split_line: Vec<&str> = line.split(comment).collect();
    let main_line = split_line[0];

    let key_value: Vec<&str> = main_line.splitn(2, delimiter).collect();
    if key_value.len() < 2 { panic!("Line {} with content '{}' does not appear to be formatted properly.", ind + 1, line) };

    let key = key_value[0];
    let value = key_value[1];


    let env_exists = std::env::var(key);

    if env_exists.is_err() {
        std::env::set_var(key, value)
    } else {
        if !overwrite { return }

        std::env::set_var(key, value)
    }
}