ryna/
config.rs

1use std::collections::{ HashMap, HashSet };
2use std::fs;
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, RwLock};
5
6use colored::Colorize;
7use glob::glob;
8use malachite::Integer;
9use md5::compute;
10use regex::{Regex, Captures};
11use serde::{Serialize, Deserialize};
12use serde_yaml::{from_str, to_string};
13use directories::ProjectDirs;
14
15use crate::compilation::RynaError;
16use crate::context::{standard_ctx, RynaContext};
17use crate::docs::{generate_all_class_docs, generate_all_function_overload_docs, generate_all_interface_docs, generate_all_operation_docs, generate_all_syntax_docs};
18use crate::functions::define_macro_emit_fn;
19use crate::graph::DirectedGraph;
20use crate::macros::define_module_path_macro;
21use crate::{ryna_error, parser::*};
22use crate::regex_ext::replace_all_fallible;
23use crate::serialization::{CompiledRynaModule, ReducedRynaModule};
24use crate::object::Object;
25use crate::types::INT;
26use crate::operations::{DIV_BINOP_ID, NEQ_BINOP_ID, SUB_BINOP_ID};
27
28const ENV_VAR_REGEX: &str = r"\$\{\s*([a-zA-Z0-9_]+)\s*\}";
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
31pub struct ModuleInfo {
32    #[serde(skip_serializing_if = "String::is_empty")]
33    #[serde(default)]
34    pub path: String,
35    
36    pub version: String,
37
38    #[serde(skip)]
39    pub is_local: bool,
40
41    #[serde(skip)]
42    pub dependencies: HashSet<(String, String)>
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize)]
46pub struct RynaConfig {
47    pub module_name: String,
48
49    #[serde(default = "default_version")]
50    pub version: String,
51
52    #[serde(skip_serializing_if = "String::is_empty")]
53    #[serde(default)]
54    pub hash: String,
55
56    #[serde(skip_serializing_if = "Vec::is_empty")]
57    #[serde(default)]
58    pub module_paths: Vec<String>,
59
60    #[serde(skip_serializing_if = "String::is_empty")]
61    #[serde(default)]
62    pub build: String,
63
64    pub modules: HashMap<String, ModuleInfo>
65}
66
67fn default_version() -> String {
68    "0.1.0".into()
69}
70
71#[derive(Clone, Debug, Serialize, Deserialize)]
72pub struct RynaGlobalConfig {
73    #[serde(skip)]
74    file_path: String,
75
76    pub modules_path: String
77}
78
79pub type Imports = HashMap<ImportType, HashSet<String>>;
80pub type ImportMap = HashMap<String, Imports>;
81pub type InnerDepGraph = DirectedGraph<(ImportType, usize), ()>;
82pub type VersionModCache = HashMap<(String, String), ModuleInfo>;
83
84type FileCache = HashMap<String, (RynaConfig, HashMap<String, HashMap<ImportType, HashSet<String>>>, String, bool)>;
85
86pub struct RynaModule {
87    pub name: String,
88    pub hash: String,
89    pub ctx: RynaContext,
90    pub code: Vec<RynaExpr>,
91    pub source: Vec<(String, usize)>, 
92    pub imports: ImportMap,
93    pub inner_dependencies: InnerDepGraph
94}
95
96impl RynaModule {
97    pub fn new(
98        name: String,
99        hash: String,
100        ctx: RynaContext,
101        code: Vec<RynaExpr>,
102        source: Vec<(String, usize)>, 
103        imports: ImportMap,
104        inner_dependencies: InnerDepGraph
105    ) -> RynaModule {
106        RynaModule { name, hash, ctx, code, source, imports, inner_dependencies }
107    }
108}
109
110pub fn get_intermediate_cache_path(module_name: &String, module_path: &String) -> PathBuf {
111    let module_path = Path::new(&*module_path);
112    let parts = module_name.split("/").skip(1).collect::<Vec<_>>();
113
114    let mut cache_path = module_path.to_owned();
115
116    cache_path = if cache_path.is_file() {
117        cache_path.parent().unwrap().join("ryna_cache/intermediate/local")
118
119    } else {
120        cache_path.join("ryna_cache/intermediate")
121    };
122
123    if parts.len() > 1 {
124        for p in &parts[..parts.len() - 1] {
125            cache_path = cache_path.join(p);
126        }
127    }
128
129    cache_path = cache_path.join(
130        if parts.is_empty() { 
131            "main.rynaci".into() 
132
133        } else { 
134            format!("{}.rynaci", module_path.file_stem().unwrap().to_str().unwrap()) 
135        }
136    );
137
138    cache_path
139}
140
141impl RynaConfig {
142    pub fn get_imports_topological_order(&self, all_modules: &VersionModCache) -> Result<Vec<(String, String)>, RynaError> {
143        fn topological_order(node: &(String, String), res: &mut Vec<(String, String)>, temp: &mut HashSet<(String, String)>, perm: &mut HashSet<(String, String)>, all_modules: &VersionModCache) -> Result<(), RynaError> {
144            if perm.contains(node) {
145                return Ok(());
146            }
147
148            if temp.contains(node) {
149                return Err(RynaError::module_error("Dependency tree is cyclic".into()));
150            }
151
152            temp.insert(node.clone());
153
154            match all_modules.get(node) {
155                Some(m) => {
156                    for (n, v) in &m.dependencies {
157                        topological_order(&(n.clone(), v.clone()), res, temp, perm, all_modules)?;
158                    }
159                },
160
161                None => {
162                    return Err(RynaError::module_error(format!("Module {} {} was not found", node.0.green(), format!("v{}", node.1).cyan())));
163                },
164            }
165
166            temp.remove(node);
167            perm.insert(node.clone());
168            res.push(node.clone());
169
170            Ok(())
171        }
172
173        let mut res = vec!();
174        let mut temp = HashSet::new();
175        let mut perm = HashSet::new();
176
177        topological_order(&(self.module_name.clone(), self.version.clone()), &mut res, &mut temp, &mut perm, all_modules)?;
178
179        Ok(res)
180    }
181
182    fn get_cached_intermediate_module(&self, path: &String, force_recompile: bool) -> Option<RynaModule> {
183        if force_recompile {
184            return None;
185        }
186
187        let cache_path = get_intermediate_cache_path(&self.module_name, path);
188
189        if cache_path.is_file() {
190            let reduced_module = ReducedRynaModule::from_file(&cache_path);
191
192            if reduced_module.hash == self.hash {
193                return Some(reduced_module.recover_module());
194            }
195        }
196
197        None
198    }
199}
200
201fn parse_ryna_module_with_config(path: &String, already_compiled: &mut HashMap<(String, String), RynaModule>, all_modules: &VersionModCache, file_cache: &FileCache, optimize: bool, force_recompile: bool) -> Result<RynaModule, RynaError> {
202    let (config_yml, imports, main, is_macro) = file_cache.get(path).unwrap();
203
204    // Try to read intermediate cache
205    if let Some(module) = config_yml.get_cached_intermediate_module(path, *is_macro || force_recompile) {
206        return Ok(module);
207
208    } else {
209        let mut ctx = standard_ctx();
210        
211        ctx.optimize = optimize;
212        ctx.module_path = path.clone();
213        ctx.module_name = config_yml.module_name.clone().into();
214
215        define_module_path_macro(&mut ctx);
216
217        if *is_macro {
218            define_macro_emit_fn(&mut ctx, "emit".into());
219        }
220
221        let topological_order = config_yml.get_imports_topological_order(all_modules)?;
222
223        // Forbid multiple versions of same module
224        
225        let mut module_versions = HashMap::<&String, Vec<_>>::new();
226
227        for (name, ver) in &topological_order {
228            module_versions.entry(name).or_default().push(ver);
229        }
230
231        for (name, vers) in module_versions {
232            if vers.len() > 1 {
233                let all_but_last_vers = &vers[..vers.len() - 1];
234
235                return Err(RynaError::module_error(
236                    format!(
237                        "Multiple versions needed for module {} ({} and {})", 
238                        name,
239                        all_but_last_vers.iter().map(|i| format!("{}", i.cyan())).collect::<Vec<_>>().join(", "),
240                        vers.last().unwrap().cyan()
241                    )
242                ));
243            }
244        }
245
246        // Compile modules
247
248        for dep in &topological_order {
249            if *dep.0 != config_yml.module_name && !already_compiled.contains_key(dep) {
250                let module = all_modules.get(dep).unwrap();
251                let compiled_module = parse_ryna_module_with_config(&module.path, already_compiled, all_modules, file_cache, optimize, force_recompile)?;
252
253                already_compiled.entry(dep.clone()).or_insert(compiled_module);
254            }
255        }
256
257        // Select modules to send to compilation routine
258        
259        let module_dependencies = topological_order.iter()
260                                                   .filter(|i| i.0 != config_yml.module_name)
261                                                   .map(|i| (i.0.clone(), i.1.clone()))
262                                                   .map(|i| (i.0.clone(), already_compiled.get(&i).unwrap()))
263                                                   .collect();
264
265        let (module, source) = ctx.parse_with_dependencies(&config_yml.module_name, main, &module_dependencies)?;
266        let graph = ctx.get_inner_dep_graph(&module)?;
267        
268        let res = RynaModule::new(config_yml.module_name.clone(), config_yml.hash.clone(), ctx, module, source, imports.clone(), graph);
269
270        save_intermediate_cache(&res);
271
272        Ok(res)
273    }
274}
275
276pub fn save_intermediate_cache(module: &RynaModule) {
277    let cache_path = get_intermediate_cache_path(&module.ctx.module_name, &module.ctx.module_path);
278
279    std::fs::create_dir_all(cache_path.parent().unwrap()).expect("Unable to create cache folders");
280
281    let reduced_module = module.get_reduced_module();
282    reduced_module.write_to_file(&cache_path);
283}
284
285pub fn get_all_modules_cascade_aux(module_path: &Path, macro_code: Option<String>, seen_paths: &mut HashSet<String>, modules: &mut VersionModCache, file_cache: &mut FileCache) -> Result<(), RynaError> {
286    let main_path = module_path.join(Path::new("main.ryna"));
287
288    if macro_code.is_none() && !main_path.exists() {
289        return Err(RynaError::module_error(format!("Main file ({}) does not exist", main_path.to_str().unwrap())));
290    }
291
292    let config_path = module_path.join(Path::new("ryna_config.yml"));
293
294    if !config_path.exists() {
295        return Err(RynaError::module_error(format!("Config file ({}) does not exist", config_path.to_str().unwrap())));
296    }
297
298    let config = fs::read_to_string(&config_path).expect("Error while reading config file");
299
300    let main = match &macro_code {
301        Some(m_code) => m_code.clone(),
302        None => fs::read_to_string(&main_path).expect("Error while reading main file"),
303    };
304
305    let mut config_yml: RynaConfig = from_str(&config).expect("Unable to parse configuration file");
306    let imports = ryna_module_imports_parser(Span::new(&main), Arc::new(config_yml.module_name.clone())).unwrap().1;
307
308    let mut local_files = glob(format!("{}/**/*.ryna", module_path.to_str().unwrap()).as_str())
309        .expect("Error while reading module path")
310        .map(Result::unwrap)
311        .collect::<Vec<_>>();
312
313    local_files.sort();
314
315    if macro_code.is_none() {
316        let combined_hashes = local_files.iter()
317            .map(std::fs::read_to_string)
318            .map(Result::unwrap)
319            .map(compute)
320            .map(|i| format!("{:x}", i))
321            .collect::<Vec<_>>()
322            .join("");
323
324        let new_hash = if combined_hashes.len() == 32 {
325            combined_hashes
326
327        } else {
328            format!("{:x}", compute(combined_hashes))
329        };
330
331        if config_yml.hash != new_hash {
332            config_yml.hash = new_hash;
333            fs::write(config_path, to_string(&config_yml).unwrap()).expect("Unable to update configuration file");
334        }
335    }
336
337    let norm_mod_path = normalize_path(module_path)?;
338
339    for path in local_files {
340        let full_import_path = normalize_path(&path)?;
341        let import_name = full_import_path[norm_mod_path.len()..full_import_path.len() - 5].replace('\\', "/");
342
343        if import_name != "/main" {
344            let parent_module_name = config_yml.module_name.clone();
345
346            config_yml.modules.entry(format!("{}{}", parent_module_name, import_name)).or_insert(ModuleInfo {
347                path: full_import_path.clone(),
348                version: config_yml.version.clone(),
349                is_local: true,
350                dependencies: HashSet::new(),
351            });
352        }
353    }
354
355    let all_deps = config_yml.modules.iter().map(|i| (i.0.clone(), i.1.version.clone())).collect::<HashMap<_, _>>();
356    let mut local_imports = HashMap::new();
357
358    for (module_name, info) in config_yml.modules.iter_mut() {
359        if info.is_local {
360            let local_main = fs::read_to_string(&info.path).expect("Error while reading main file");
361            let local_file_imports = ryna_module_imports_parser(Span::new(&local_main), Arc::new(config_yml.module_name.clone())).unwrap().1;
362
363            local_imports.entry(module_name.clone()).or_insert((local_file_imports.clone(), local_main));   
364
365            for module in local_file_imports.keys() {
366                if !all_deps.contains_key(module) {
367                    return Err(RynaError::module_error(format!("Module with name {} was not found", module.green())));
368                }
369            }
370
371            info.dependencies = local_file_imports.keys()
372                                                  .map(|i| (i.clone(), all_deps.get(i).unwrap().clone()))
373                                                  .collect();
374            
375            modules.entry((module_name.clone(), config_yml.version.clone())).or_insert(info.clone());
376        }
377    }
378
379    for (module_name, info) in config_yml.modules.iter() {
380        if info.is_local {
381            let mut local_yml = config_yml.clone();
382            local_yml.module_name = module_name.clone();
383
384            let (local_imports, local_main) = local_imports.get(module_name).unwrap().clone();
385
386            file_cache.insert(normalize_path(Path::new(&info.path))?, (local_yml, local_imports, local_main, false));
387        }
388    }
389
390    file_cache.insert(normalize_path(module_path)?, (config_yml.clone(), imports.clone(), main.clone(), macro_code.is_some()));
391
392    for module in imports.keys() {
393        if !config_yml.modules.contains_key(module) {
394            return Err(RynaError::module_error(format!("Module with name {} was not found", module.green())));
395        }
396    }
397
398    modules.entry((config_yml.module_name, config_yml.version.clone())).or_insert(ModuleInfo { 
399        path: normalize_path(module_path)?, 
400        version: config_yml.version, 
401        is_local: false,
402        dependencies: config_yml.modules.into_iter().map(|i| (i.0, i.1.version)).filter(|(i, _)| imports.contains_key(i)).collect()
403    });
404
405    for path in config_yml.module_paths {
406        let n_path = normalize_path(Path::new(&path))?;
407
408        if !seen_paths.contains(&n_path) {
409            seen_paths.insert(n_path.clone());
410
411            for file in glob(format!("{}/**/ryna_config.yml", n_path).as_str()).expect("Error while reading module path") {
412                match file {
413                    Ok(f) if f.is_file() => {
414                        get_all_modules_cascade_aux(f.parent().unwrap(), None, seen_paths, modules, file_cache)?;
415                    },
416    
417                    _ => {
418                        return Err(RynaError::module_error("Unable to extract file from module path".into()));
419                    }
420                }
421            }
422        }
423    }
424
425    Ok(())
426}
427
428pub fn get_all_modules_cascade(module_path: &Path, macro_code: Option<String>) -> Result<(VersionModCache, FileCache), RynaError> {
429    let mut res = HashMap::new();
430    let mut file_cache = HashMap::new();
431
432    get_all_modules_cascade_aux(module_path, macro_code, &mut HashSet::new(), &mut res, &mut file_cache)?;
433
434    Ok((res, file_cache))
435}
436
437fn generate_test_file(module: &mut RynaModule) -> Result<(), RynaError> {
438    // Add definitions
439    let mut new_code = module.code.iter()
440                                  .cloned()
441                                  .filter(RynaExpr::is_definition)
442                                  .collect::<Vec<_>>();
443
444    macro_rules! fn_call {
445        ($id: expr) => {
446            RynaExpr::FunctionCall(
447                Location::none(), $id, vec!(), vec!()
448            )
449        };
450    }
451
452    macro_rules! fn_call_1 {
453        ($id: expr, $arg: expr) => {
454            RynaExpr::FunctionCall(
455                Location::none(), $id, vec!(), vec!($arg)
456            )
457        };
458    }
459
460    macro_rules! literal {
461        ($obj: expr) => {
462            RynaExpr::Literal(Location::none(), Object::new($obj))
463        };
464    }
465
466    macro_rules! if_else {
467        ($cond: expr, $ib: expr, $eb: expr) => {
468            RynaExpr::If(
469                Location::none(),
470                Box::new($cond),
471                $ib,
472                vec!(),
473                Some($eb),
474            )
475        };
476    }
477
478    macro_rules! var_def {
479        ($name: expr, $obj: expr) => {
480            RynaExpr::VariableDefinition(Location::none(), $name, INT, Box::new($obj))
481        };
482    }
483
484    macro_rules! var {
485        ($obj: expr) => {
486            RynaExpr::NameReference(Location::none(), $obj)
487        };
488    }
489
490    macro_rules! binop {
491        ($id: expr, $a: expr, $b: expr) => {
492            RynaExpr::BinaryOperation(Location::none(), $id, vec!(), Box::new($a), Box::new($b))
493        };
494    }
495
496    macro_rules! subtract {
497        ($a: expr, $b: expr) => {
498            binop!(SUB_BINOP_ID, $a, $b)
499        };
500    }
501
502    macro_rules! divide {
503        ($a: expr, $b: expr) => {
504            binop!(DIV_BINOP_ID, $a, $b)
505        };
506    }
507
508    // Add test function calls and boilerplate
509    let print_id = module.ctx.get_function_id("print".into()).unwrap();
510    let time_id = module.ctx.get_function_id("time".into()).unwrap();
511    let inc_id = module.ctx.get_function_id("inc".into()).unwrap();
512    let panic_id = module.ctx.get_function_id("panic".into()).unwrap();
513
514    let mut var_idx = 0;
515
516    let mut test_functions = vec!();
517
518    for f in &module.ctx.functions {
519        for ov in &f.overloads {
520            if ov.location.module == module.ctx.module_name && ov.annotations.iter().any(|i| i.name == "test") {
521                test_functions.push((&f.name, f.id));
522                break; // Only one overload per function
523            }
524        }
525    }
526
527    const TEST_LOG_RPAD: usize = 7;
528    let max_test_name_len = test_functions.iter().map(|(n, _)| n.len()).max().unwrap_or_default() + TEST_LOG_RPAD;
529
530    new_code.push(fn_call_1!(print_id, literal!(format!("\n*** Executing {} tests ***\n\n", test_functions.len()))));
531
532    let succ_tests_var = "successful_tests".to_string();
533
534    new_code.push(var_def!(succ_tests_var.clone(), literal!(Integer::from(0))));
535
536    for (name, id) in &test_functions {
537        let time_var = format!("start_{}", var_idx);
538
539        new_code.push(var_def!(time_var.clone(), fn_call!(time_id)));
540
541        new_code.push(fn_call_1!(print_id, literal!(format!(
542            "Testing {}{} ", 
543            name.cyan(),
544            ".".repeat(max_test_name_len - name.len())
545        ))));
546
547        new_code.push(if_else!(
548            fn_call!(*id),
549            vec!(
550                fn_call_1!(print_id, literal!(format!("{}", "Ok!".green()))),
551                fn_call_1!(inc_id, var!(succ_tests_var.clone()))
552            ),
553            vec!(fn_call_1!(print_id, literal!(format!("{}", "Failed".red()))))
554        ));
555
556        new_code.push(fn_call_1!(print_id, literal!(format!(" ["))));
557
558        new_code.push(fn_call_1!(print_id, divide!(subtract!(fn_call!(time_id), var!(time_var)), literal!(1000000.0))));
559
560        new_code.push(fn_call_1!(print_id, literal!(format!(" ms]\n"))));
561
562        var_idx += 1; // Increase variable counter
563    }
564    
565    new_code.push(fn_call_1!(print_id, literal!(format!("{}", "\nResults: "))));
566    new_code.push(fn_call_1!(print_id, var!(succ_tests_var.clone())));
567    new_code.push(fn_call_1!(print_id, literal!(format!("/{} tests succeeded\n", test_functions.len()))));
568
569    new_code.push(if_else!(
570        binop!(NEQ_BINOP_ID, var!(succ_tests_var.clone()), literal!(Integer::from(test_functions.len()))),
571        vec!(
572            fn_call_1!(panic_id, literal!(format!("Some tests failed"))),
573        ),
574        vec!()
575    ));
576
577    module.code = new_code;
578
579    Ok(())
580}
581
582pub fn precompile_ryna_module_with_config(path: &String, all_modules: VersionModCache, file_cache: FileCache, optimize: bool, test: bool, force_recompile: bool) -> Result<(RynaContext, Vec<RynaExpr>), RynaError> {
583    let mut module = parse_ryna_module_with_config(&normalize_path(Path::new(path))?, &mut HashMap::new(), &all_modules, &file_cache, optimize, force_recompile)?;
584
585    if test {
586        generate_test_file(&mut module)?;
587    }
588
589    module.ctx.precompile_module(&mut module.code)?;
590
591    Ok((module.ctx, module.code))
592}
593
594pub fn generate_docs(path: &String) -> Result<(), RynaError> {
595    let project_path = &normalize_path(Path::new(path))?;
596
597    let (_, all_mods, files) = compute_project_hash(path, None, false, false)?;
598    let mut module = parse_ryna_module_with_config(project_path, &mut HashMap::new(), &all_mods, &files, false, false)?;
599    module.ctx.precompile_module(&mut module.code)?;
600
601    generate_all_function_overload_docs(&project_path, &module);
602    generate_all_operation_docs(&project_path, &module);
603    generate_all_class_docs(&project_path, &module);
604    generate_all_syntax_docs(&project_path, &module);
605    generate_all_interface_docs(&project_path, &module);
606
607    Ok(())
608}
609
610pub fn compute_project_hash(path: &String, macro_code: Option<String>, optimize: bool, test: bool) -> Result<(String, VersionModCache, FileCache), RynaError> {
611    let module_path = Path::new(path);
612    let (all_modules, file_cache) = get_all_modules_cascade(module_path, macro_code)?;
613
614    let config_yml = &file_cache.get(&normalize_path(module_path)?).unwrap().0;
615
616    let mut final_hash = config_yml.hash.clone();
617
618    let mut sorted_modules = config_yml.modules.values().collect::<Vec<_>>();
619    sorted_modules.sort_by_key(|i| &i.path); // This should be unique and allow the same order every time
620
621    // Add the hashes of all submodules
622    for info in sorted_modules {
623        final_hash = format!("{}{}", final_hash, file_cache.get(&normalize_path(Path::new(&info.path))?).unwrap().0.hash);
624    }
625
626    // Add ryna version
627    final_hash.push_str(env!("CARGO_PKG_VERSION"));
628
629    // Add ryna optimization flag
630    final_hash.push_str(&optimize.to_string());
631
632    // Add ryna test flag
633    final_hash.push_str(&test.to_string());
634
635    Ok((format!("{:x}", compute(&final_hash)), all_modules, file_cache))
636}
637
638pub fn read_compiled_cache(path: &String) -> Option<CompiledRynaModule> {
639    let module_path = Path::new(path);
640    let cache_path = module_path.join(Path::new("ryna_cache"));
641
642    if !cache_path.exists() {
643        return None;
644    }
645
646    let code_path = cache_path.join(Path::new("main.rynac"));
647
648    if !code_path.exists() {
649        return None;
650    }
651
652    let code = CompiledRynaModule::from_file(&code_path);
653
654    Some(code)
655}
656
657pub fn save_compiled_cache(path: &String, module: &CompiledRynaModule) -> Result<(), RynaError> {
658    let module_path = Path::new(path);
659    let cache_path = module_path.join(Path::new("ryna_cache"));
660
661    if !cache_path.exists() {
662        fs::create_dir(&cache_path).expect("Unable to create cache directory");
663    }
664
665    let code_path = cache_path.join(Path::new("main.rynac"));
666    module.write_to_file(&code_path);
667
668    Ok(())
669}
670
671pub fn normalize_path(path: &Path) -> Result<String, RynaError> {
672    let path_slashes = path.to_str().unwrap().replace('\\', "/");
673    let sub_path = parse_env_vars_and_normalize(&path_slashes)?;
674    
675    return match Path::new(&sub_path).canonicalize() {
676        Ok(p) => Ok(p.to_str().unwrap().replace('\\', "/")),
677        Err(_) => Err(RynaError::module_error(format!(
678            "Unable to normalize path: {} (does it exist?)",
679            path_slashes.green()
680        ))),
681    };
682}
683
684pub fn parse_env_vars_and_normalize(path: &str) -> Result<String, RynaError> {
685    let res = path.to_owned();
686    let env_var_regex = Regex::new(ENV_VAR_REGEX).unwrap();
687
688    let replacement = |caps: &Captures| {
689        let cap = caps.get(1).unwrap().as_str();
690        
691        if let Some(var) = CONFIG.read().unwrap().get(cap) {
692            Ok(var.into())
693 
694        } else {
695            Err(RynaError::module_error(format!("Unable to find config variable {}", cap)))
696        }
697    };
698    
699    return replace_all_fallible(&env_var_regex, res.as_str(), replacement);
700}
701
702impl RynaGlobalConfig {
703    pub fn load() -> RynaGlobalConfig {
704        if let Some(proj_dirs) = ProjectDirs::from("", "",  "ryna-language") {
705            let config_path = proj_dirs.config_dir();
706            let config_file_path = config_path.join("config.yml");
707    
708            if !config_file_path.exists() {
709                std::fs::create_dir_all(config_path).unwrap();
710                std::fs::write(&config_file_path, "modules_path: \"\"").unwrap();
711            }
712
713            let config_file = std::fs::read_to_string(&config_file_path).unwrap();
714            let mut config: RynaGlobalConfig = serde_yaml::from_str(&config_file).unwrap();
715
716            config.file_path = config_file_path.to_str().unwrap().to_string();
717
718            return config;
719        }
720    
721        ryna_error!("Unable to read config file");
722    }
723
724    pub fn save(&self) -> Result<(), String> {
725        let yml = serde_yaml::to_string(self).unwrap();
726
727        std::fs::write(&self.file_path, yml)
728            .map_err(|_| "Unable to save configuration file".to_string())
729    }
730
731    pub fn get(&self, name: &str) -> Option<&str> {
732        match name {
733            "MODULES_PATH" => Some(&self.modules_path),
734            _ => None
735        }
736    }
737}
738
739lazy_static! {
740    pub static ref CONFIG: RwLock<RynaGlobalConfig> = RwLock::new(RynaGlobalConfig::load());
741}