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