@devaloop/devalang 0.0.1-alpha.1 → 0.0.1-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.devalang +9 -0
  2. package/Cargo.toml +15 -6
  3. package/README.md +79 -81
  4. package/docs/CHANGELOG.md +213 -0
  5. package/docs/ROADMAP.md +11 -8
  6. package/docs/TODO.md +32 -29
  7. package/examples/bank.deva +9 -0
  8. package/examples/condition.deva +20 -0
  9. package/examples/duration.deva +9 -0
  10. package/examples/group.deva +12 -0
  11. package/examples/index.deva +12 -5
  12. package/examples/loop.deva +16 -0
  13. package/examples/samples/hat-808.wav +0 -0
  14. package/examples/synth.deva +14 -0
  15. package/examples/variables.deva +9 -0
  16. package/out-tsc/bin/devalang.exe +0 -0
  17. package/out-tsc/scripts/version/fetch.js +1 -5
  18. package/package.json +5 -4
  19. package/project-version.json +3 -3
  20. package/rust/cli/bank.rs +455 -0
  21. package/rust/cli/build.rs +114 -28
  22. package/rust/cli/check.rs +96 -103
  23. package/rust/cli/driver.rs +280 -0
  24. package/rust/cli/init.rs +79 -0
  25. package/rust/cli/install.rs +17 -0
  26. package/rust/cli/mod.rs +8 -1
  27. package/rust/cli/play.rs +193 -0
  28. package/rust/cli/template.rs +57 -0
  29. package/rust/cli/update.rs +4 -0
  30. package/rust/common/cdn.rs +11 -0
  31. package/rust/common/mod.rs +1 -0
  32. package/rust/config/driver.rs +76 -0
  33. package/rust/config/loader.rs +110 -0
  34. package/rust/config/mod.rs +2 -0
  35. package/rust/core/audio/engine.rs +242 -0
  36. package/rust/core/audio/evaluator.rs +31 -0
  37. package/rust/core/audio/interpreter/arrow_call.rs +142 -0
  38. package/rust/core/audio/interpreter/call.rs +70 -0
  39. package/rust/core/audio/interpreter/condition.rs +69 -0
  40. package/rust/core/audio/interpreter/driver.rs +236 -0
  41. package/rust/core/audio/interpreter/let_.rs +19 -0
  42. package/rust/core/audio/interpreter/load.rs +18 -0
  43. package/rust/core/audio/interpreter/loop_.rs +67 -0
  44. package/rust/core/audio/interpreter/mod.rs +12 -0
  45. package/rust/core/audio/interpreter/sleep.rs +36 -0
  46. package/rust/core/audio/interpreter/spawn.rs +84 -0
  47. package/rust/core/audio/interpreter/tempo.rs +16 -0
  48. package/rust/core/audio/interpreter/trigger.rs +102 -0
  49. package/rust/core/audio/loader/mod.rs +1 -0
  50. package/rust/core/audio/loader/trigger.rs +64 -0
  51. package/rust/core/audio/mod.rs +6 -0
  52. package/rust/core/audio/player.rs +54 -0
  53. package/rust/core/audio/renderer.rs +54 -0
  54. package/rust/core/builder/mod.rs +70 -27
  55. package/rust/core/debugger/lexer.rs +27 -0
  56. package/rust/core/debugger/mod.rs +13 -49
  57. package/rust/core/debugger/preprocessor.rs +27 -0
  58. package/rust/core/debugger/store.rs +25 -0
  59. package/rust/core/error/mod.rs +60 -0
  60. package/rust/core/lexer/handler/arrow.rs +31 -0
  61. package/rust/core/lexer/handler/at.rs +21 -0
  62. package/rust/core/lexer/handler/brace.rs +41 -0
  63. package/rust/core/lexer/handler/colon.rs +21 -0
  64. package/rust/core/lexer/handler/comment.rs +30 -0
  65. package/rust/core/lexer/handler/dot.rs +21 -0
  66. package/rust/core/lexer/handler/driver.rs +241 -0
  67. package/rust/core/lexer/handler/identifier.rs +41 -0
  68. package/rust/core/lexer/handler/indent.rs +52 -0
  69. package/rust/core/lexer/handler/mod.rs +15 -0
  70. package/rust/core/lexer/handler/newline.rs +23 -0
  71. package/rust/core/lexer/handler/number.rs +31 -0
  72. package/rust/core/lexer/handler/operator.rs +44 -0
  73. package/rust/core/lexer/handler/slash.rs +21 -0
  74. package/rust/core/lexer/handler/string.rs +63 -0
  75. package/rust/core/lexer/mod.rs +37 -319
  76. package/rust/core/lexer/token.rs +87 -0
  77. package/rust/core/mod.rs +6 -2
  78. package/rust/core/parser/driver.rs +339 -0
  79. package/rust/core/parser/handler/arrow_call.rs +151 -0
  80. package/rust/core/parser/handler/at.rs +162 -0
  81. package/rust/core/parser/handler/bank.rs +41 -0
  82. package/rust/core/parser/handler/condition.rs +74 -0
  83. package/rust/core/parser/handler/dot.rs +178 -0
  84. package/rust/core/parser/handler/identifier/call.rs +41 -0
  85. package/rust/core/parser/handler/identifier/group.rs +75 -0
  86. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  87. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  88. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  89. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  90. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  91. package/rust/core/parser/handler/loop_.rs +72 -0
  92. package/rust/core/parser/handler/mod.rs +8 -0
  93. package/rust/core/parser/handler/tempo.rs +47 -0
  94. package/rust/core/parser/mod.rs +3 -200
  95. package/rust/core/parser/statement.rs +96 -0
  96. package/rust/core/preprocessor/loader.rs +308 -0
  97. package/rust/core/preprocessor/mod.rs +2 -24
  98. package/rust/core/preprocessor/module.rs +42 -56
  99. package/rust/core/preprocessor/processor.rs +76 -0
  100. package/rust/core/preprocessor/resolver/bank.rs +41 -51
  101. package/rust/core/preprocessor/resolver/call.rs +123 -0
  102. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  103. package/rust/core/preprocessor/resolver/driver.rs +232 -0
  104. package/rust/core/preprocessor/resolver/group.rs +61 -0
  105. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  106. package/rust/core/preprocessor/resolver/loop_.rs +76 -67
  107. package/rust/core/preprocessor/resolver/mod.rs +12 -111
  108. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  109. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  110. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  111. package/rust/core/preprocessor/resolver/trigger.rs +90 -154
  112. package/rust/core/preprocessor/resolver/value.rs +78 -0
  113. package/rust/core/shared/bank.rs +21 -0
  114. package/rust/core/shared/duration.rs +9 -0
  115. package/rust/core/shared/mod.rs +3 -0
  116. package/rust/core/shared/value.rs +29 -0
  117. package/rust/core/store/export.rs +28 -0
  118. package/rust/core/store/global.rs +39 -0
  119. package/rust/core/store/import.rs +28 -0
  120. package/rust/core/store/mod.rs +4 -0
  121. package/rust/core/store/variable.rs +28 -0
  122. package/rust/core/utils/mod.rs +2 -0
  123. package/rust/core/utils/path.rs +31 -0
  124. package/rust/core/utils/validation.rs +37 -0
  125. package/rust/installer/bank.rs +55 -0
  126. package/rust/installer/mod.rs +1 -0
  127. package/rust/lib.rs +162 -1
  128. package/rust/main.rs +104 -31
  129. package/rust/utils/file.rs +35 -0
  130. package/rust/utils/installer.rs +56 -0
  131. package/rust/utils/logger.rs +108 -34
  132. package/rust/utils/mod.rs +5 -3
  133. package/rust/utils/{loader.rs → spinner.rs} +2 -0
  134. package/rust/utils/watcher.rs +33 -0
  135. package/templates/minimal/.devalang +5 -0
  136. package/templates/minimal/README.md +202 -0
  137. package/templates/minimal/src/index.deva +2 -0
  138. package/templates/welcome/.devalang +5 -0
  139. package/templates/welcome/README.md +202 -0
  140. package/templates/welcome/samples/kick-808.wav +0 -0
  141. package/templates/welcome/src/index.deva +13 -0
  142. package/templates/welcome/src/variables.deva +5 -0
  143. package/typescript/scripts/version/fetch.ts +1 -6
  144. package/docs/COMMANDS.md +0 -31
  145. package/docs/SYNTAX.md +0 -148
  146. package/examples/exported.deva +0 -7
  147. package/rust/audio/mod.rs +0 -1
  148. package/rust/cli/new.rs +0 -1
  149. package/rust/core/parser/at.rs +0 -142
  150. package/rust/core/parser/bank.rs +0 -42
  151. package/rust/core/parser/dot.rs +0 -107
  152. package/rust/core/parser/identifer.rs +0 -91
  153. package/rust/core/parser/loop_.rs +0 -62
  154. package/rust/core/parser/tempo.rs +0 -42
  155. package/rust/core/parser/variable.rs +0 -129
  156. package/rust/core/preprocessor/dependencies.rs +0 -54
  157. package/rust/core/preprocessor/resolver/at.rs +0 -24
  158. package/rust/core/types/cli.rs +0 -160
  159. package/rust/core/types/mod.rs +0 -7
  160. package/rust/core/types/module.rs +0 -41
  161. package/rust/core/types/parser.rs +0 -73
  162. package/rust/core/types/statement.rs +0 -105
  163. package/rust/core/types/store.rs +0 -116
  164. package/rust/core/types/token.rs +0 -83
  165. package/rust/core/types/variable.rs +0 -32
  166. package/rust/runner/executer.rs +0 -44
  167. package/rust/runner/mod.rs +0 -1
  168. package/rust/utils/path.rs +0 -46
@@ -0,0 +1,79 @@
1
+ use std::{ fs, path::Path };
2
+ use include_dir::{ include_dir, Dir };
3
+ use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
4
+
5
+ #[cfg(feature = "cli")]
6
+ use inquire::{ Select, Confirm };
7
+
8
+ static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
9
+
10
+ #[cfg(feature = "cli")]
11
+ pub fn handle_init_command(name: Option<String>, template: Option<String>) {
12
+ let current_dir = std::env::current_dir().unwrap();
13
+ let project_name = name
14
+ .clone()
15
+ .unwrap_or_else(|| {
16
+ current_dir.file_name().unwrap_or_default().to_string_lossy().to_string()
17
+ });
18
+
19
+ // Select a template if not provided
20
+ let selected_template = template.unwrap_or_else(|| {
21
+ Select::new("Select a template for your project:", get_available_templates())
22
+ .prompt()
23
+ .unwrap_or_else(|_| {
24
+ eprintln!("No template selected. Exiting...");
25
+ std::process::exit(1);
26
+ })
27
+ });
28
+
29
+ if selected_template.is_empty() {
30
+ eprintln!("Template cannot be empty.");
31
+ std::process::exit(1);
32
+ }
33
+
34
+ if name.is_none() {
35
+ // Case of initialization in the current directory
36
+ if fs::read_dir(&current_dir).unwrap().next().is_some() {
37
+ let confirm = Confirm::new(
38
+ "The current directory is not empty. Do you want to continue?"
39
+ )
40
+ .with_default(false)
41
+ .prompt()
42
+ .unwrap_or(false);
43
+
44
+ if !confirm {
45
+ eprintln!("Operation cancelled by the user.");
46
+ std::process::exit(0);
47
+ }
48
+ }
49
+
50
+ scaffold_project_current_dir(current_dir.as_path(), selected_template);
51
+ println!(
52
+ "✅ Initialized '{}' project in current directory: {}",
53
+ project_name,
54
+ current_dir.display()
55
+ );
56
+ } else {
57
+ // Case of initialization in a new directory
58
+ let target_path = current_dir.join(&project_name);
59
+
60
+ if target_path.exists() {
61
+ eprintln!("❌ A folder named '{}' already exists.", project_name);
62
+ std::process::exit(1);
63
+ }
64
+
65
+ fs::create_dir_all(&target_path).expect("Error creating project directory");
66
+
67
+ scaffold_project_current_dir(&target_path, selected_template);
68
+ println!("✅ Initialized '{}' project in: {}", project_name, target_path.display());
69
+ }
70
+ }
71
+
72
+ fn scaffold_project_current_dir(path: &Path, template: String) {
73
+ let template_dir = TEMPLATES_DIR.get_dir(&template).unwrap_or_else(|| {
74
+ eprintln!("❌ The template '{}' doesn't exist.", template);
75
+ std::process::exit(1);
76
+ });
77
+
78
+ copy_dir_recursive(template_dir, path, &template_dir.path());
79
+ }
@@ -0,0 +1,17 @@
1
+ use crate::installer::bank::install_bank;
2
+
3
+ #[cfg(feature = "cli")]
4
+ pub async fn handle_install_bank_command(name: String) -> Result<(), String> {
5
+ let deva_dir = std::path::Path::new("./.deva/");
6
+
7
+ println!("⬇️ Installing bank '{}'...", name);
8
+ println!("📂 Target directory: {}", deva_dir.display());
9
+
10
+ if let Err(e) = install_bank(name.as_str(), deva_dir).await {
11
+ eprintln!("❌ Error installing bank '{}': {}", name, e);
12
+ } else {
13
+ println!("✅ Bank '{}' installed successfully!", name);
14
+ }
15
+
16
+ Ok(())
17
+ }
package/rust/cli/mod.rs CHANGED
@@ -1,3 +1,10 @@
1
+ pub mod driver;
2
+
1
3
  pub mod check;
2
- pub mod new;
3
4
  pub mod build;
5
+ pub mod init;
6
+ pub mod template;
7
+ pub mod play;
8
+ pub mod install;
9
+ pub mod bank;
10
+ pub mod update;
@@ -0,0 +1,193 @@
1
+ use crate::{
2
+ core::{
3
+ builder::Builder,
4
+ debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
5
+ preprocessor::loader::ModuleLoader,
6
+ store::global::GlobalStore,
7
+ utils::path::{ find_entry_file, normalize_path },
8
+ },
9
+ config::driver::Config,
10
+ utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
11
+ };
12
+
13
+ use std::{ path::Path, sync::mpsc::channel, thread, time::Duration };
14
+ use std::fs;
15
+ use std::collections::HashMap;
16
+
17
+ #[cfg(feature = "cli")]
18
+ pub fn handle_play_command(
19
+ config: Option<Config>,
20
+ entry: Option<String>,
21
+ output: Option<String>,
22
+ watch: bool,
23
+ repeat: bool
24
+ ) {
25
+ use crate::core::audio::player::AudioPlayer;
26
+
27
+ let logger = Logger::new();
28
+
29
+ let entry_path = entry
30
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.entry.clone()))
31
+ .unwrap_or_else(|| "".to_string());
32
+
33
+ let output_path = output
34
+ .or_else(|| config.as_ref().and_then(|c| c.defaults.output.clone()))
35
+ .unwrap_or_else(|| "".to_string());
36
+
37
+ let fetched_repeat = if repeat {
38
+ true
39
+ } else {
40
+ config
41
+ .as_ref()
42
+ .and_then(|c| c.defaults.repeat)
43
+ .unwrap_or(false)
44
+ };
45
+
46
+ if entry_path.is_empty() || output_path.is_empty() {
47
+ logger.log_message(LogLevel::Error, "Entry or output path not specified.");
48
+ std::process::exit(1);
49
+ }
50
+
51
+ let entry_file = find_entry_file(&entry_path).unwrap_or_else(|| {
52
+ logger.log_message(LogLevel::Error, "index.deva not found");
53
+ std::process::exit(1);
54
+ });
55
+
56
+ let audio_file = format!("{}/audio/index.wav", normalize_path(&output_path));
57
+ let mut audio_player = AudioPlayer::new();
58
+
59
+ if watch && fetched_repeat {
60
+ logger.log_message(
61
+ LogLevel::Error,
62
+ "Watch and repeat cannot be used together. Use repeat instead."
63
+ );
64
+ std::process::exit(1);
65
+ }
66
+
67
+ if watch {
68
+ let (tx, rx) = channel::<()>();
69
+
70
+ // Thread 1 : Watcher sending changes
71
+ let entry_clone = entry_path.clone();
72
+ thread::spawn(move || {
73
+ let _ = watch_directory(entry_clone, move || {
74
+ let _ = tx.send(()); // signal a change
75
+ });
76
+ });
77
+
78
+ // Main thread: build + play in a loop
79
+ begin_play(&config, &entry_file, &output_path);
80
+ audio_player.play_file_once(&audio_file);
81
+
82
+ logger.log_message(LogLevel::Watcher, "Watching for changes... Press Ctrl+C to exit.");
83
+
84
+ while let Ok(_) = rx.recv() {
85
+ logger.log_message(LogLevel::Watcher, "Change detected, rebuilding...");
86
+
87
+ begin_play(&config, &entry_file, &output_path);
88
+
89
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
90
+
91
+ audio_player.play_file_once(&audio_file);
92
+ }
93
+ } else if fetched_repeat {
94
+ // Initial build to start from a clean slate
95
+ begin_play(&config, &entry_file, &output_path);
96
+
97
+ logger.log_message(LogLevel::Info, "🎵 Playback started (repeat mode)...");
98
+
99
+ let mut last_snapshot = snapshot_files(&entry_path);
100
+ let mut audio_player = AudioPlayer::new();
101
+ audio_player.play_file_once(&audio_file);
102
+
103
+ loop {
104
+ let current_snapshot = snapshot_files(&entry_path);
105
+ let has_changed = files_changed(&last_snapshot, &current_snapshot);
106
+
107
+ if has_changed {
108
+ logger.log_message(LogLevel::Info, "Change detected, rebuilding in background...");
109
+ let entry_file = entry_file.clone();
110
+ let output_path = output_path.clone();
111
+ let config_clone = config.clone();
112
+
113
+ // Rebuild in a separate thread
114
+ std::thread::spawn(move || {
115
+ begin_play(&config_clone, &entry_file, &output_path);
116
+ });
117
+
118
+ last_snapshot = current_snapshot;
119
+ }
120
+
121
+ // Wait for the audio to finish without blocking the current playback
122
+ audio_player.wait_until_end();
123
+
124
+ // Then replay the audio (rebuilt or not)
125
+ audio_player.play_file_once(&audio_file);
126
+ }
127
+ } else {
128
+ // Single execution
129
+ begin_play(&config, &entry_file, &output_path);
130
+
131
+ logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
132
+
133
+ audio_player.play_file_once(&audio_file);
134
+ audio_player.wait_until_end();
135
+ }
136
+ }
137
+
138
+ fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
139
+ let spinner = with_spinner("Building...", || {
140
+ thread::sleep(Duration::from_millis(800));
141
+ });
142
+
143
+ let normalized_entry = normalize_path(entry_file);
144
+ let normalized_output_dir = normalize_path(&output);
145
+
146
+ let duration = std::time::Instant::now();
147
+ let mut global_store = GlobalStore::new();
148
+ let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
149
+ let (modules_tokens, modules_statements) = loader.load_all_modules(&mut global_store);
150
+
151
+ // SECTION Write logs
152
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
153
+ write_preprocessor_log_file(
154
+ &normalized_output_dir,
155
+ "resolved_statements.log",
156
+ modules_statements.clone()
157
+ );
158
+
159
+ // SECTION Building AST and Audio
160
+ let builder = Builder::new();
161
+ builder.build_ast(&modules_statements, &output);
162
+ builder.build_audio(&modules_statements, &output, &mut global_store);
163
+
164
+ // SECTION Logging
165
+ let logger = Logger::new();
166
+ let success_message = format!(
167
+ "Build completed successfully in {:.2?}. Output files written to: '{}'",
168
+ duration.elapsed(),
169
+ normalized_output_dir
170
+ );
171
+
172
+ logger.log_message(LogLevel::Success, &success_message);
173
+ }
174
+
175
+ fn snapshot_files<P: AsRef<Path>>(dir: P) -> HashMap<String, u64> {
176
+ let mut map = HashMap::new();
177
+ if let Ok(entries) = fs::read_dir(dir) {
178
+ for entry in entries.flatten() {
179
+ if let Ok(meta) = entry.metadata() {
180
+ if let Ok(mtime) = meta.modified() {
181
+ if let Ok(duration) = mtime.duration_since(std::time::UNIX_EPOCH) {
182
+ map.insert(entry.path().display().to_string(), duration.as_secs());
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ map
189
+ }
190
+
191
+ fn files_changed(old: &HashMap<String, u64>, new: &HashMap<String, u64>) -> bool {
192
+ old != new
193
+ }
@@ -0,0 +1,57 @@
1
+ use include_dir::{ include_dir, Dir, DirEntry };
2
+ use crate::utils::file::format_file_size;
3
+
4
+ static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
5
+
6
+ #[cfg(feature = "cli")]
7
+ pub fn handle_template_list_command() {
8
+ let available_templates = get_available_templates();
9
+
10
+ println!("📦 Available templates ({}) :\n", available_templates.len());
11
+
12
+ for dir in available_templates {
13
+ println!("• {}", dir);
14
+ }
15
+
16
+ println!("\nUsage : devalang init --name <project-name> --template <template-name>");
17
+ }
18
+
19
+ #[cfg(feature = "cli")]
20
+ pub fn handle_template_info_command(name: String) {
21
+ let template_dir = TEMPLATES_DIR.get_dir(name.clone()).unwrap_or_else(|| {
22
+ println!("❌ The template '{}' is not found.", name);
23
+
24
+ std::process::exit(1);
25
+ });
26
+
27
+ let mut file_count = 0;
28
+ let mut dir_count = 0;
29
+ let mut total_size: u64 = 0;
30
+
31
+ fn walk(dir: &Dir, file_count: &mut u32, dir_count: &mut u32, total_size: &mut u64) {
32
+ for entry in dir.entries() {
33
+ match entry {
34
+ DirEntry::File(file) => {
35
+ *file_count += 1;
36
+ *total_size += file.contents().len() as u64;
37
+ }
38
+ DirEntry::Dir(subdir) => {
39
+ *dir_count += 1;
40
+ walk(subdir, file_count, dir_count, total_size);
41
+ }
42
+ }
43
+ }
44
+ }
45
+
46
+ walk(template_dir, &mut file_count, &mut dir_count, &mut total_size);
47
+
48
+ println!("📦 Template : {}", name);
49
+ println!("📂 Content : {file_count} file(s), {dir_count} folder(s)");
50
+ println!("💾 Size : {}", format_file_size(total_size));
51
+ }
52
+
53
+ pub fn get_available_templates() -> Vec<String> {
54
+ TEMPLATES_DIR.dirs()
55
+ .map(|dir| dir.path().file_name().unwrap().to_string_lossy().to_string())
56
+ .collect()
57
+ }
@@ -0,0 +1,4 @@
1
+ pub async fn handle_update_command(only: Option<String>) -> Result<(), Box<dyn std::error::Error>> {
2
+ println!("Updates are not yet implemented. This is a placeholder function.");
3
+ Ok(())
4
+ }
@@ -0,0 +1,11 @@
1
+ pub fn get_cdn_url() -> String {
2
+ let cdn_url = std::env
3
+ ::var("CDN_URL")
4
+ .unwrap_or_else(|_| "https://cdn.devalang.com".to_string());
5
+
6
+ if !cdn_url.ends_with('/') {
7
+ format!("{}/", cdn_url)
8
+ } else {
9
+ cdn_url
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ pub mod cdn;
@@ -0,0 +1,76 @@
1
+ use serde::{ Deserialize, Serialize };
2
+
3
+ #[derive(Debug, Deserialize, Clone, Serialize)]
4
+ pub struct Config {
5
+ pub defaults: ConfigDefaults,
6
+ pub banks: Option<Vec<BankEntry>>,
7
+ }
8
+
9
+ #[derive(Debug, Deserialize, Clone, Serialize)]
10
+ pub struct ConfigDefaults {
11
+ pub entry: Option<String>,
12
+ pub output: Option<String>,
13
+ pub watch: Option<bool>,
14
+ pub repeat: Option<bool>,
15
+ }
16
+
17
+ #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
18
+ pub struct BankEntry {
19
+ pub path: String,
20
+ pub version: Option<String>,
21
+ pub author: Option<String>,
22
+ }
23
+
24
+ impl Config {
25
+ pub fn new() -> Self {
26
+ Config {
27
+ defaults: ConfigDefaults {
28
+ entry: None,
29
+ output: None,
30
+ watch: None,
31
+ repeat: None,
32
+ },
33
+ banks: Some(Vec::new()),
34
+ }
35
+ }
36
+
37
+ pub fn with_defaults(
38
+ entry: Option<String>,
39
+ output: Option<String>,
40
+ watch: Option<bool>,
41
+ repeat: Option<bool>
42
+ ) -> Self {
43
+ Config {
44
+ defaults: ConfigDefaults {
45
+ entry,
46
+ output,
47
+ watch,
48
+ repeat,
49
+ },
50
+ banks: Some(Vec::new()),
51
+ }
52
+ }
53
+
54
+ pub fn from_string(config_string: &str) -> Result<(Self, String), String> {
55
+ let config: Config = toml
56
+ ::from_str(config_string)
57
+ .map_err(|e| format!("Failed to parse config string: {}", e))?;
58
+ let config_path = ".devalang".to_string();
59
+
60
+ Ok((config, config_path))
61
+ }
62
+
63
+ pub fn write(&self, new_config: &Config) -> Result<(), String> {
64
+ let config_path = ".devalang";
65
+
66
+ let content = toml
67
+ ::to_string(new_config)
68
+ .map_err(|e| format!("Failed to serialize config: {}", e))?;
69
+
70
+ std::fs
71
+ ::write(config_path, content)
72
+ .map_err(|e| format!("Failed to write config to file '{}': {}", config_path, e))?;
73
+
74
+ Ok(())
75
+ }
76
+ }
@@ -0,0 +1,110 @@
1
+ use std::{ fs, path::Path };
2
+ use std::collections::HashMap;
3
+ use crate::config::driver::{ BankEntry, Config };
4
+
5
+ pub fn load_config(path: Option<&Path>) -> Option<Config> {
6
+ let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
7
+
8
+ if config_path.exists() {
9
+ let content = fs::read_to_string(config_path).ok()?;
10
+ toml::from_str(&content).ok()
11
+ } else {
12
+ None
13
+ }
14
+ }
15
+
16
+ pub fn update_bank_version_in_config(config: &mut Config, dependency: &str, new_version: &str) {
17
+ // Si le vecteur banks n'existe pas, on ne fait rien
18
+ if config.banks.is_none() {
19
+ println!("No banks configured.");
20
+ return;
21
+ }
22
+
23
+ let banks = config.banks.as_mut().unwrap();
24
+
25
+ if let Some(bank) = banks.iter_mut().find(|b| b.path.contains(dependency)) {
26
+ bank.version = Some(new_version.to_string());
27
+
28
+ if let Err(e) = config.write(config) {
29
+ eprintln!("❌ Failed to write config: {}", e);
30
+ } else {
31
+ println!("✅ Bank '{}' updated to version '{}'", dependency, new_version);
32
+ }
33
+ } else {
34
+ println!("Bank '{}' not found in config", dependency);
35
+ }
36
+ }
37
+
38
+ pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
39
+ if config.banks.is_none() {
40
+ println!("No banks configured.");
41
+ return;
42
+ }
43
+
44
+ let banks = config.banks.as_mut().unwrap();
45
+
46
+ if let Some(index) = banks.iter().position(|b| b.path.contains(dependency)) {
47
+ banks.remove(index);
48
+
49
+ if let Err(e) = config.write(config) {
50
+ eprintln!("❌ Failed to write config: {}", e);
51
+ } else {
52
+ println!("✅ Bank '{}' removed from config", dependency);
53
+ }
54
+ } else {
55
+ println!("Bank '{}' not found in config", dependency);
56
+ }
57
+ }
58
+
59
+ pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
60
+ if config.banks.is_none() {
61
+ config.banks = Some(Vec::new());
62
+ }
63
+
64
+ let banks = config.banks.as_mut().unwrap();
65
+
66
+ let exists = banks.iter().any(|b| b.path == dependency);
67
+ if exists {
68
+ println!("Bank '{}' already in config", dependency);
69
+ return;
70
+ }
71
+
72
+ let metadata_path = Path::new(real_path).join("bank.toml");
73
+
74
+ if !metadata_path.exists() {
75
+ eprintln!("❌ Bank metadata file '{}' does not exist", metadata_path.display());
76
+ return;
77
+ }
78
+
79
+ let metadata_content = fs
80
+ ::read_to_string(&metadata_path)
81
+ .expect("Failed to read bank metadata file");
82
+
83
+ let metadata: HashMap<String, String> = toml
84
+ ::from_str(&metadata_content)
85
+ .expect("Failed to parse bank metadata file");
86
+
87
+ let bank_to_insert = BankEntry {
88
+ path: dependency.to_string(),
89
+ version: Some(
90
+ metadata
91
+ .get("version")
92
+ .cloned()
93
+ .unwrap_or_else(|| "0.0.1".to_string())
94
+ ),
95
+ author: Some(
96
+ metadata
97
+ .get("author")
98
+ .cloned()
99
+ .unwrap_or_else(|| "unknown".to_string())
100
+ ),
101
+ };
102
+
103
+ banks.push(bank_to_insert);
104
+
105
+ if let Err(e) = config.write(config) {
106
+ eprintln!("❌ Failed to write config: {}", e);
107
+ } else {
108
+ println!("✅ Bank '{}' added to config", dependency);
109
+ }
110
+ }
@@ -0,0 +1,2 @@
1
+ pub mod loader;
2
+ pub mod driver;