@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
package/rust/lib.rs CHANGED
@@ -1 +1,162 @@
1
- // TODO WebAssembly support
1
+ pub mod core;
2
+ pub mod utils;
3
+ pub mod config;
4
+ pub mod common;
5
+
6
+ use serde::{ Deserialize, Serialize };
7
+ use wasm_bindgen::prelude::*;
8
+ use serde_wasm_bindgen::to_value;
9
+
10
+ use crate::core::{
11
+ audio::{ engine::AudioEngine, interpreter::driver::{ run_audio_program } },
12
+ parser::statement::{ Statement, StatementKind },
13
+ preprocessor::loader::ModuleLoader,
14
+ shared::value::Value,
15
+ store::global::GlobalStore,
16
+ utils::path::normalize_path,
17
+ };
18
+
19
+ #[derive(Serialize, Deserialize)]
20
+ struct ParseResult {
21
+ ok: bool,
22
+ ast: String,
23
+ errors: Vec<ErrorResult>,
24
+ }
25
+
26
+ #[derive(Serialize, Deserialize)]
27
+ struct ErrorResult {
28
+ message: String,
29
+ line: usize,
30
+ column: usize,
31
+ }
32
+
33
+ #[wasm_bindgen]
34
+ pub fn parse(entry_path: &str, source: &str) -> Result<JsValue, JsValue> {
35
+ let statements = parse_internal_from_string(entry_path, source);
36
+
37
+ match statements {
38
+ Ok(value) => {
39
+ let ast_string = value;
40
+ to_value(&ast_string).map_err(|e|
41
+ JsValue::from_str(&format!("Error converting AST to JS value: {}", e))
42
+ )
43
+ }
44
+ Err(e) => { Err(JsValue::from_str(&format!("Error: {}", e))) }
45
+ }
46
+ }
47
+
48
+ #[wasm_bindgen]
49
+ pub fn render_audio(user_code: &str) -> Result<js_sys::Float32Array, JsValue> {
50
+ let entry_path = normalize_path("playground.deva");
51
+ let output_path = normalize_path("./temp");
52
+
53
+ let mut global_store = GlobalStore::new();
54
+
55
+ let loader = ModuleLoader::from_raw_source(
56
+ &entry_path,
57
+ &output_path,
58
+ user_code,
59
+ &mut global_store
60
+ );
61
+
62
+ loader
63
+ .load_wasm_module(&mut global_store)
64
+ .map_err(|e| JsValue::from_str(&format!("Module loading error: {}", e)))?;
65
+
66
+ let all_statements_map = loader.extract_statements_map(&global_store);
67
+
68
+ let main_statements = all_statements_map
69
+ .get(&entry_path)
70
+ .ok_or(JsValue::from_str("❌ No statements found for entry module"))?
71
+ .clone();
72
+
73
+ let audio_engine = AudioEngine::new("wasm_output".to_string());
74
+
75
+ let (final_engine, _, _) = run_audio_program(
76
+ &main_statements,
77
+ audio_engine,
78
+ "playground".to_string(),
79
+ "wasm_output".to_string()
80
+ );
81
+
82
+ let samples = final_engine.get_normalized_buffer();
83
+
84
+ if samples.is_empty() {
85
+ return Err(JsValue::from_str("❌ Audio buffer is empty"));
86
+ }
87
+
88
+ Ok(js_sys::Float32Array::from(samples.as_slice()))
89
+ }
90
+
91
+ fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
92
+ let entry_path = normalize_path(virtual_path);
93
+ let output_path = normalize_path("./temp");
94
+
95
+ let mut global_store = GlobalStore::new();
96
+ let loader = ModuleLoader::from_raw_source(
97
+ &entry_path,
98
+ &output_path,
99
+ source,
100
+ &mut global_store
101
+ );
102
+
103
+ let module = loader
104
+ .load_single_module(&mut global_store)
105
+ .map_err(|e| format!("Error loading module: {}", e))?;
106
+
107
+ let raw_ast = ast_to_string(module.statements.clone());
108
+
109
+ let found_errors = collect_errors_recursively(&module.statements);
110
+
111
+ let result = ParseResult {
112
+ ok: true,
113
+ ast: raw_ast,
114
+ errors: found_errors,
115
+ };
116
+
117
+ Ok(result)
118
+ }
119
+
120
+ fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
121
+ let mut errors: Vec<ErrorResult> = Vec::new();
122
+
123
+ for stmt in statements {
124
+ match &stmt.kind {
125
+ StatementKind::Unknown => {
126
+ errors.push(ErrorResult {
127
+ message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
128
+ line: stmt.line,
129
+ column: stmt.column,
130
+ });
131
+ }
132
+ StatementKind::Error { message } => {
133
+ errors.push(ErrorResult {
134
+ message: message.clone(),
135
+ line: stmt.line,
136
+ column: stmt.column,
137
+ });
138
+ }
139
+ StatementKind::Loop => {
140
+ if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
141
+ errors.extend(collect_errors_recursively(body_statements));
142
+ }
143
+ }
144
+ _ => {}
145
+ }
146
+ }
147
+
148
+ errors
149
+ }
150
+
151
+ fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
152
+ if let Value::Map(map) = value {
153
+ if let Some(Value::Block(statements)) = map.get("body") {
154
+ return Some(statements);
155
+ }
156
+ }
157
+ None
158
+ }
159
+
160
+ fn ast_to_string(statements: Vec<Statement>) -> String {
161
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
162
+ }
package/rust/main.rs CHANGED
@@ -1,48 +1,121 @@
1
+ #![cfg(feature = "cli")]
2
+
1
3
  pub mod core;
2
4
  pub mod cli;
3
- pub mod runner;
4
- pub mod audio;
5
5
  pub mod utils;
6
+ pub mod config;
7
+ pub mod common;
8
+ pub mod installer;
6
9
 
7
- use std::{ io };
10
+ use std::io;
8
11
  use clap::Parser;
9
12
  use crate::{
10
- cli::{ build::handle_build_command, check::handle_check_command },
11
- core::types::cli::{ Cli, CliCommands },
13
+ cli::{
14
+ bank::{
15
+ handle_bank_available_command,
16
+ handle_bank_info_command,
17
+ handle_bank_list_command,
18
+ handle_remove_bank_command, handle_update_bank_command,
19
+ },
20
+ build::handle_build_command,
21
+ check::handle_check_command,
22
+ driver::{ BankCommand, Cli, Commands, InstallCommand, TemplateCommand },
23
+ init::handle_init_command,
24
+ install::handle_install_bank_command,
25
+ play::handle_play_command,
26
+ template::{ handle_template_info_command, handle_template_list_command },
27
+ update::handle_update_command,
28
+ },
29
+ config::{ driver::Config, loader::load_config },
12
30
  };
13
31
 
14
- fn main() -> io::Result<()> {
15
- let cli = Cli::parse();
32
+ #[tokio::main]
33
+ async fn main() -> io::Result<()> {
34
+ let cli: Cli = Cli::parse();
35
+ let mut config: Option<Config> = None;
36
+
37
+ if !cli.no_config {
38
+ config = load_config(None);
39
+ } else {
40
+ println!("No configuration file loaded. Running with arguments only.");
41
+ }
16
42
 
17
43
  match cli.command {
18
- // TODO - Implement the new command
19
- // CliCommands::New { name, template } => {
20
- // log_message("Command 'new project' is not implemented yet.", "WARNING");
21
- // }
22
-
23
- // TODO - Implement the template command
24
- // CliCommands::Template { command } =>
25
- // match command {
26
- // CliTemplateCommand::List => {
27
- // log_message("Command 'template list' is not implemented yet.", "WARNING");
28
- // }
29
- // CliTemplateCommand::Info { name } => {
30
- // log_message("Command 'template info' is not implemented yet.", "WARNING");
31
- // }
32
- // }
33
-
34
- CliCommands::Build { entry, output, watch, compilation_mode, debug, compress } => {
35
- handle_build_command(entry, output);
44
+ Commands::Init { name, template } => {
45
+ handle_init_command(name, template);
46
+ }
47
+
48
+ Commands::Template { command } =>
49
+ match command {
50
+ TemplateCommand::List => {
51
+ handle_template_list_command();
52
+ }
53
+ TemplateCommand::Info { name } => {
54
+ handle_template_info_command(name);
55
+ }
56
+ }
57
+
58
+ Commands::Check { entry, output, watch, compilation_mode, debug } => {
59
+ handle_check_command(config, entry, output, watch);
60
+ }
61
+
62
+ Commands::Build { entry, output, watch, compilation_mode, debug, compress } => {
63
+ handle_build_command(config, entry, output, watch);
36
64
  }
37
65
 
38
- CliCommands::Check { entry, output, watch, compilation_mode, debug } => {
39
- handle_check_command(entry, output);
66
+ Commands::Play { entry, output, watch, repeat } => {
67
+ handle_play_command(config, entry, output, watch, repeat);
68
+ }
69
+
70
+ Commands::Install { command } =>
71
+ match command {
72
+ InstallCommand::Bank { name } => {
73
+ if let Err(err) = handle_install_bank_command(name).await {
74
+ eprintln!("❌ Failed to install bank: {}", err);
75
+ }
76
+ }
77
+ }
78
+
79
+ Commands::Bank { command } =>
80
+ match command {
81
+ BankCommand::List => {
82
+ if let Err(err) = handle_bank_list_command().await {
83
+ eprintln!("❌ Failed to list local banks: {}", err);
84
+ }
85
+ }
86
+
87
+ BankCommand::Available => {
88
+ if let Err(err) = handle_bank_available_command().await {
89
+ eprintln!("❌ Failed to list available banks: {}", err);
90
+ }
91
+ }
92
+
93
+ BankCommand::Info { name } => {
94
+ if let Err(err) = handle_bank_info_command(name).await {
95
+ eprintln!("❌ Failed to get bank info: {}", err);
96
+ }
97
+ }
98
+
99
+ BankCommand::Remove { name } => {
100
+ if let Err(err) = handle_remove_bank_command(name).await {
101
+ eprintln!("❌ Failed to remove bank: {}", err);
102
+ }
103
+ }
104
+
105
+ BankCommand::Update { name } => {
106
+ if let Err(err) = handle_update_bank_command(name).await {
107
+ eprintln!("❌ Failed to update bank: {}", err);
108
+ }
109
+ }
110
+ }
111
+
112
+ Commands::Update { only } => {
113
+ if let Err(err) = handle_update_command(only).await {
114
+ eprintln!("❌ Update failed: {}", err);
115
+ }
40
116
  }
41
117
 
42
- // TODO - Implement the play command
43
- // CliCommands::Play {} => {
44
- // log_message("Command 'play' is not implemented yet.", "WARNING");
45
- // }
118
+ _ => {}
46
119
  }
47
120
 
48
121
  Ok(())
@@ -0,0 +1,35 @@
1
+ use std::{ fs::{ self }, path::Path };
2
+ use include_dir::{ Dir, DirEntry };
3
+
4
+ pub fn copy_dir_recursive(dir: &Dir, target_root: &Path, base_path: &Path) {
5
+ for entry in dir.entries() {
6
+ match entry {
7
+ DirEntry::Dir(subdir) => {
8
+ copy_dir_recursive(subdir, target_root, base_path);
9
+ }
10
+ DirEntry::File(file) => {
11
+ let rel_path = file.path().strip_prefix(base_path).unwrap();
12
+ let dest_path = target_root.join(rel_path);
13
+
14
+ if let Some(parent) = dest_path.parent() {
15
+ fs::create_dir_all(parent).unwrap();
16
+ }
17
+
18
+ fs::write(&dest_path, file.contents()).expect("Error writing file");
19
+ }
20
+ }
21
+ }
22
+ }
23
+
24
+ pub fn format_file_size(bytes: u64) -> String {
25
+ const KB: u64 = 1024;
26
+ const MB: u64 = 1024 * 1024;
27
+
28
+ if bytes >= MB {
29
+ format!("{:.2} Mb", (bytes as f64) / (MB as f64))
30
+ } else if bytes >= KB {
31
+ format!("{:.2} Kb", (bytes as f64) / (KB as f64))
32
+ } else {
33
+ format!("{} bytes", bytes)
34
+ }
35
+ }
@@ -0,0 +1,56 @@
1
+ use std::fs::File;
2
+ use std::path::Path;
3
+ use std::io::BufReader;
4
+ use std::error::Error;
5
+ use std::io::{ copy, Cursor };
6
+ use zip::ZipArchive;
7
+
8
+ pub async fn download_file(url: &str, destination: &Path) -> Result<(), Box<dyn Error>> {
9
+ let response = reqwest::get(url).await?;
10
+
11
+ if !response.status().is_success() {
12
+ return Err(format!("Failed to download file: HTTP {}", response.status()).into());
13
+ }
14
+
15
+ if let Some(parent) = destination.parent() {
16
+ std::fs::create_dir_all(parent)?;
17
+ }
18
+
19
+ let bytes = response.bytes().await?;
20
+ let mut content = Cursor::new(bytes);
21
+ let mut file = File::create(destination)?;
22
+ copy(&mut content, &mut file)?;
23
+
24
+ Ok(())
25
+ }
26
+
27
+ pub async fn extract_archive(
28
+ zip_path: &Path,
29
+ destination: &Path
30
+ ) -> Result<(), Box<dyn std::error::Error>> {
31
+ let file = File::open(zip_path)?;
32
+ let mut archive = ZipArchive::new(BufReader::new(file))?;
33
+
34
+ for i in 0..archive.len() {
35
+ let mut file = archive.by_index(i)?;
36
+ let outpath = destination.join(file.mangled_name());
37
+
38
+ if file.name().ends_with('/') {
39
+ std::fs::create_dir_all(&outpath)?;
40
+ } else {
41
+ if let Some(p) = outpath.parent() {
42
+ std::fs::create_dir_all(p)?;
43
+ }
44
+
45
+ let mut outfile = File::create(&outpath)?;
46
+ std::io::copy(&mut file, &mut outfile)?;
47
+ }
48
+ }
49
+
50
+ // Clear the temporary folder after extraction
51
+ if zip_path.exists() {
52
+ std::fs::remove_file(zip_path)?;
53
+ }
54
+
55
+ Ok(())
56
+ }
@@ -1,49 +1,123 @@
1
+ #[cfg(feature = "cli")]
1
2
  use crossterm::style::{Attribute, Color, ResetColor, SetAttribute, SetForegroundColor};
2
- use std::{ fmt::Write };
3
+ use std::fmt::Write;
3
4
 
4
- pub fn log_message(message: &str, status: &str) {
5
- let formatted_status = format_status(status);
6
- println!("🦊 {} {} {}", language_signature(), formatted_status, message);
5
+ #[derive(Debug, Clone, PartialEq)]
6
+ pub enum LogLevel {
7
+ Success,
8
+ Error,
9
+ Info,
10
+ Warning,
11
+ Watcher,
12
+ Debug,
7
13
  }
8
14
 
9
- fn language_signature() -> String {
10
- let mut s = String::new();
15
+ #[derive(Debug, Clone)]
16
+ pub struct Logger;
11
17
 
12
- write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
13
- s.push('[');
18
+ impl Logger {
19
+ pub fn new() -> Self {
20
+ Logger
21
+ }
14
22
 
15
- write!(&mut s, "{}", SetForegroundColor(Color::Rgb { r: 29, g: 211, b: 176 })).unwrap();
16
- write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
17
- s.push_str("Devalang");
18
- write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
23
+ // --- log_message ---
19
24
 
20
- write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
21
- s.push(']');
25
+ #[cfg(feature = "cli")]
26
+ pub fn log_message(&self, level: LogLevel, message: &str) {
27
+ let formatted_status = self.format_status(level);
28
+ println!("🦊 {} {} {}", self.language_signature(), formatted_status, message);
29
+ }
22
30
 
23
- write!(&mut s, "{}", ResetColor).unwrap();
31
+ #[cfg(not(feature = "cli"))]
32
+ pub fn log_message(&self, _level: LogLevel, _message: &str) {
33
+ // no-op for WASM
34
+ }
24
35
 
25
- s
26
- }
36
+ // --- log_error_with_stacktrace ---
37
+
38
+ #[cfg(feature = "cli")]
39
+ pub fn log_error_with_stacktrace(&self, message: &str, stacktrace: &str) {
40
+ let formatted_status = self.format_status(LogLevel::Error);
41
+ println!("🦊 {} {} {}", self.language_signature(), formatted_status, message);
42
+ println!(" ↳ {}", stacktrace);
43
+ }
44
+
45
+ #[cfg(not(feature = "cli"))]
46
+ pub fn log_error_with_stacktrace(&self, _message: &str, _stacktrace: &str) {
47
+ // no-op for WASM
48
+ }
49
+
50
+ // --- language_signature ---
51
+
52
+ #[cfg(feature = "cli")]
53
+ fn language_signature(&self) -> String {
54
+ let mut s = String::new();
55
+
56
+ write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
57
+ s.push('[');
58
+
59
+ write!(&mut s, "{}", SetForegroundColor(Color::Rgb { r: 29, g: 211, b: 176 })).unwrap();
60
+ write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
61
+ s.push_str("Devalang");
62
+ write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
63
+
64
+ write!(&mut s, "{}", SetForegroundColor(Color::Grey)).unwrap();
65
+ s.push(']');
66
+ write!(&mut s, "{}", ResetColor).unwrap();
67
+
68
+ s
69
+ }
70
+
71
+ #[cfg(not(feature = "cli"))]
72
+ fn language_signature(&self) -> String {
73
+ "[Devalang]".to_string()
74
+ }
75
+
76
+ // --- format_status ---
77
+
78
+ #[cfg(feature = "cli")]
79
+ fn format_status(&self, level: LogLevel) -> String {
80
+ let mut s = String::new();
27
81
 
28
- fn format_status(status: &str) -> String {
29
- let mut s = String::new();
82
+ let color = match level {
83
+ LogLevel::Success => Color::Rgb { r: 76, g: 175, b: 80 },
84
+ LogLevel::Error => Color::Rgb { r: 244, g: 67, b: 54 },
85
+ LogLevel::Info => Color::Rgb { r: 33, g: 150, b: 243 },
86
+ LogLevel::Warning => Color::Rgb { r: 255, g: 152, b: 0 },
87
+ LogLevel::Watcher => Color::Rgb { r: 156, g: 39, b: 176 },
88
+ LogLevel::Debug => Color::Rgb { r: 103, g: 58, b: 183 },
89
+ };
30
90
 
31
- let color = match status {
32
- "SUCCESS" => Color::Rgb { r: 76, g: 175, b: 80 },
33
- "ERROR" => Color::Rgb { r: 244, g: 67, b: 54 },
34
- "INFO" => Color::Rgb { r: 33, g: 150, b: 243 },
35
- "WARNING" => Color::Rgb { r: 255, g: 152, b: 0 },
36
- _ => Color::Grey,
37
- };
91
+ let status = match level {
92
+ LogLevel::Success => "SUCCESS",
93
+ LogLevel::Error => "ERROR",
94
+ LogLevel::Info => "INFO",
95
+ LogLevel::Warning => "WARNING",
96
+ LogLevel::Watcher => "WATCHER",
97
+ LogLevel::Debug => "DEBUG",
98
+ };
38
99
 
39
- s.push('[');
40
- write!(&mut s, "{}", SetForegroundColor(color)).unwrap();
41
- write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
42
- s.push_str(status);
43
- write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
44
- s.push(']');
100
+ s.push('[');
101
+ write!(&mut s, "{}", SetForegroundColor(color)).unwrap();
102
+ write!(&mut s, "{}", SetAttribute(Attribute::Bold)).unwrap();
103
+ s.push_str(status);
104
+ write!(&mut s, "{}", SetAttribute(Attribute::Reset)).unwrap();
105
+ s.push(']');
106
+ write!(&mut s, "{}", ResetColor).unwrap();
45
107
 
46
- write!(&mut s, "{}", ResetColor).unwrap();
108
+ s
109
+ }
47
110
 
48
- s
111
+ #[cfg(not(feature = "cli"))]
112
+ fn format_status(&self, level: LogLevel) -> String {
113
+ match level {
114
+ LogLevel::Success => "[SUCCESS]",
115
+ LogLevel::Error => "[ERROR]",
116
+ LogLevel::Info => "[INFO]",
117
+ LogLevel::Warning => "[WARNING]",
118
+ LogLevel::Watcher => "[WATCHER]",
119
+ LogLevel::Debug => "[DEBUG]",
120
+ }
121
+ .to_string()
122
+ }
49
123
  }
package/rust/utils/mod.rs CHANGED
@@ -1,5 +1,7 @@
1
1
  pub mod version;
2
2
  pub mod signature;
3
- pub mod path;
4
- pub mod loader;
5
- pub mod logger;
3
+ pub mod spinner;
4
+ pub mod watcher;
5
+ pub mod file;
6
+ pub mod logger;
7
+ pub mod installer;
@@ -1,6 +1,8 @@
1
+ #[cfg(feature = "cli")]
1
2
  use indicatif::{ ProgressBar, ProgressStyle };
2
3
  use std::{ time::Duration };
3
4
 
5
+ #[cfg(feature = "cli")]
4
6
  pub fn with_spinner<T, F>(start_msg: &str, f: F) -> T where F: FnOnce() -> T {
5
7
  let spinner = ProgressBar::new_spinner();
6
8
  spinner.set_style(
@@ -0,0 +1,33 @@
1
+ use notify::{ Watcher, RecursiveMode, Config, RecommendedWatcher };
2
+ use std::sync::mpsc::channel;
3
+
4
+ use std::time::{ Duration, Instant };
5
+
6
+ pub fn watch_directory<F>(entry: String, callback: F) -> notify::Result<()>
7
+ where F: Fn() + Send + 'static
8
+ {
9
+ let (tx, rx) = channel();
10
+
11
+ let mut watcher: RecommendedWatcher = Watcher::new(tx, Config::default())?;
12
+ watcher.watch(&entry.as_ref(), RecursiveMode::Recursive)?;
13
+
14
+ let mut last_trigger = Instant::now();
15
+
16
+ loop {
17
+ match rx.recv() {
18
+ Ok(_) => {
19
+ let now = Instant::now();
20
+ if now.duration_since(last_trigger) > Duration::from_millis(200) {
21
+ callback();
22
+ last_trigger = now;
23
+ }
24
+ }
25
+ Err(e) => {
26
+ eprintln!("Channel error: {:?}", e);
27
+ break;
28
+ }
29
+ }
30
+ }
31
+
32
+ Ok(())
33
+ }
@@ -0,0 +1,5 @@
1
+ [defaults]
2
+ entry = "./src"
3
+ output = "./output"
4
+ watch = false
5
+ repeat = false