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

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 (155) hide show
  1. package/.devalang +4 -0
  2. package/Cargo.toml +49 -45
  3. package/README.md +127 -46
  4. package/docs/CHANGELOG.md +172 -0
  5. package/docs/COMMANDS.md +60 -6
  6. package/docs/CONFIG.md +30 -0
  7. package/docs/ROADMAP.md +10 -7
  8. package/docs/SYNTAX.md +100 -18
  9. package/docs/TODO.md +31 -28
  10. package/examples/condition.deva +20 -0
  11. package/examples/group.deva +12 -0
  12. package/examples/index.deva +13 -4
  13. package/examples/loop.deva +16 -0
  14. package/examples/samples/hat-808.wav +0 -0
  15. package/examples/synth.deva +14 -0
  16. package/examples/variables.deva +9 -0
  17. package/out-tsc/bin/devalang.exe +0 -0
  18. package/out-tsc/scripts/version/fetch.js +1 -5
  19. package/package.json +5 -4
  20. package/project-version.json +3 -3
  21. package/rust/cli/build.rs +114 -28
  22. package/rust/cli/check.rs +96 -103
  23. package/rust/cli/init.rs +79 -0
  24. package/rust/cli/mod.rs +203 -1
  25. package/rust/cli/play.rs +193 -0
  26. package/rust/cli/template.rs +57 -0
  27. package/rust/config/loader.rs +13 -0
  28. package/rust/config/mod.rs +16 -0
  29. package/rust/core/audio/engine.rs +214 -0
  30. package/rust/core/audio/evaluator.rs +31 -0
  31. package/rust/core/audio/interpreter/arrow_call.rs +129 -0
  32. package/rust/core/audio/interpreter/call.rs +70 -0
  33. package/rust/core/audio/interpreter/condition.rs +69 -0
  34. package/rust/core/audio/interpreter/driver.rs +236 -0
  35. package/rust/core/audio/interpreter/let_.rs +19 -0
  36. package/rust/core/audio/interpreter/load.rs +18 -0
  37. package/rust/core/audio/interpreter/loop_.rs +67 -0
  38. package/rust/core/audio/interpreter/mod.rs +12 -0
  39. package/rust/core/audio/interpreter/sleep.rs +36 -0
  40. package/rust/core/audio/interpreter/spawn.rs +84 -0
  41. package/rust/core/audio/interpreter/tempo.rs +16 -0
  42. package/rust/core/audio/interpreter/trigger.rs +69 -0
  43. package/rust/core/audio/loader/mod.rs +1 -0
  44. package/rust/core/audio/loader/trigger.rs +52 -0
  45. package/rust/core/audio/mod.rs +6 -0
  46. package/rust/core/audio/player.rs +54 -0
  47. package/rust/core/audio/renderer.rs +54 -0
  48. package/rust/core/builder/mod.rs +70 -27
  49. package/rust/core/debugger/lexer.rs +27 -0
  50. package/rust/core/debugger/mod.rs +13 -49
  51. package/rust/core/debugger/preprocessor.rs +27 -0
  52. package/rust/core/debugger/store.rs +25 -0
  53. package/rust/core/error/mod.rs +60 -0
  54. package/rust/core/lexer/handler/arrow.rs +31 -0
  55. package/rust/core/lexer/handler/at.rs +21 -0
  56. package/rust/core/lexer/handler/brace.rs +41 -0
  57. package/rust/core/lexer/handler/colon.rs +21 -0
  58. package/rust/core/lexer/handler/comment.rs +30 -0
  59. package/rust/core/lexer/handler/dot.rs +21 -0
  60. package/rust/core/lexer/handler/driver.rs +230 -0
  61. package/rust/core/lexer/handler/identifier.rs +41 -0
  62. package/rust/core/lexer/handler/indent.rs +52 -0
  63. package/rust/core/lexer/handler/mod.rs +14 -0
  64. package/rust/core/lexer/handler/newline.rs +23 -0
  65. package/rust/core/lexer/handler/number.rs +31 -0
  66. package/rust/core/lexer/handler/operator.rs +44 -0
  67. package/rust/core/lexer/handler/string.rs +63 -0
  68. package/rust/core/lexer/mod.rs +37 -319
  69. package/rust/core/lexer/token.rs +86 -0
  70. package/rust/core/mod.rs +6 -2
  71. package/rust/core/parser/driver.rs +331 -0
  72. package/rust/core/parser/handler/arrow_call.rs +126 -0
  73. package/rust/core/parser/handler/at.rs +162 -0
  74. package/rust/core/parser/handler/bank.rs +41 -0
  75. package/rust/core/parser/handler/condition.rs +74 -0
  76. package/rust/core/parser/handler/dot.rs +112 -0
  77. package/rust/core/parser/handler/identifier/call.rs +41 -0
  78. package/rust/core/parser/handler/identifier/group.rs +75 -0
  79. package/rust/core/parser/handler/identifier/let_.rs +133 -0
  80. package/rust/core/parser/handler/identifier/mod.rs +51 -0
  81. package/rust/core/parser/handler/identifier/sleep.rs +33 -0
  82. package/rust/core/parser/handler/identifier/spawn.rs +41 -0
  83. package/rust/core/parser/handler/identifier/synth.rs +65 -0
  84. package/rust/core/parser/handler/loop_.rs +72 -0
  85. package/rust/core/parser/handler/mod.rs +8 -0
  86. package/rust/core/parser/handler/tempo.rs +47 -0
  87. package/rust/core/parser/mod.rs +3 -200
  88. package/rust/core/parser/statement.rs +96 -0
  89. package/rust/core/preprocessor/loader.rs +229 -0
  90. package/rust/core/preprocessor/mod.rs +2 -24
  91. package/rust/core/preprocessor/module.rs +42 -56
  92. package/rust/core/preprocessor/processor.rs +76 -0
  93. package/rust/core/preprocessor/resolver/bank.rs +41 -51
  94. package/rust/core/preprocessor/resolver/call.rs +123 -0
  95. package/rust/core/preprocessor/resolver/condition.rs +92 -0
  96. package/rust/core/preprocessor/resolver/driver.rs +227 -0
  97. package/rust/core/preprocessor/resolver/group.rs +61 -0
  98. package/rust/core/preprocessor/resolver/let_.rs +31 -0
  99. package/rust/core/preprocessor/resolver/loop_.rs +76 -67
  100. package/rust/core/preprocessor/resolver/mod.rs +12 -111
  101. package/rust/core/preprocessor/resolver/spawn.rs +58 -0
  102. package/rust/core/preprocessor/resolver/synth.rs +50 -0
  103. package/rust/core/preprocessor/resolver/tempo.rs +40 -61
  104. package/rust/core/preprocessor/resolver/trigger.rs +90 -154
  105. package/rust/core/preprocessor/resolver/value.rs +78 -0
  106. package/rust/core/shared/duration.rs +8 -0
  107. package/rust/core/shared/mod.rs +2 -0
  108. package/rust/core/shared/value.rs +28 -0
  109. package/rust/core/store/export.rs +28 -0
  110. package/rust/core/store/global.rs +39 -0
  111. package/rust/core/store/import.rs +28 -0
  112. package/rust/core/store/mod.rs +4 -0
  113. package/rust/core/store/variable.rs +28 -0
  114. package/rust/core/utils/mod.rs +2 -0
  115. package/rust/core/utils/path.rs +31 -0
  116. package/rust/core/utils/validation.rs +37 -0
  117. package/rust/lib.rs +161 -1
  118. package/rust/main.rs +46 -30
  119. package/rust/utils/file.rs +35 -0
  120. package/rust/utils/logger.rs +108 -34
  121. package/rust/utils/mod.rs +3 -2
  122. package/rust/utils/{loader.rs → spinner.rs} +2 -0
  123. package/rust/utils/watcher.rs +33 -0
  124. package/templates/minimal/.devalang +5 -0
  125. package/templates/minimal/README.md +202 -0
  126. package/templates/minimal/src/index.deva +2 -0
  127. package/templates/welcome/.devalang +5 -0
  128. package/templates/welcome/README.md +202 -0
  129. package/templates/welcome/samples/kick-808.wav +0 -0
  130. package/templates/welcome/src/index.deva +13 -0
  131. package/templates/welcome/src/variables.deva +5 -0
  132. package/typescript/scripts/version/fetch.ts +1 -6
  133. package/examples/exported.deva +0 -7
  134. package/rust/audio/mod.rs +0 -1
  135. package/rust/cli/new.rs +0 -1
  136. package/rust/core/parser/at.rs +0 -142
  137. package/rust/core/parser/bank.rs +0 -42
  138. package/rust/core/parser/dot.rs +0 -107
  139. package/rust/core/parser/identifer.rs +0 -91
  140. package/rust/core/parser/loop_.rs +0 -62
  141. package/rust/core/parser/tempo.rs +0 -42
  142. package/rust/core/parser/variable.rs +0 -129
  143. package/rust/core/preprocessor/dependencies.rs +0 -54
  144. package/rust/core/preprocessor/resolver/at.rs +0 -24
  145. package/rust/core/types/cli.rs +0 -160
  146. package/rust/core/types/mod.rs +0 -7
  147. package/rust/core/types/module.rs +0 -41
  148. package/rust/core/types/parser.rs +0 -73
  149. package/rust/core/types/statement.rs +0 -105
  150. package/rust/core/types/store.rs +0 -116
  151. package/rust/core/types/token.rs +0 -83
  152. package/rust/core/types/variable.rs +0 -32
  153. package/rust/runner/executer.rs +0 -44
  154. package/rust/runner/mod.rs +0 -1
  155. package/rust/utils/path.rs +0 -46
@@ -0,0 +1,28 @@
1
+ use std::collections::HashMap;
2
+
3
+ use crate::core::shared::value::Value;
4
+
5
+ #[derive(Debug, Default, Clone, PartialEq)]
6
+ pub struct VariableTable {
7
+ pub variables: HashMap<String, Value>,
8
+ }
9
+
10
+ impl VariableTable {
11
+ pub fn new() -> Self {
12
+ VariableTable {
13
+ variables: HashMap::new(),
14
+ }
15
+ }
16
+
17
+ pub fn set(&mut self, name: String, value: Value) {
18
+ self.variables.insert(name, value);
19
+ }
20
+
21
+ pub fn get(&self, name: &str) -> Option<&Value> {
22
+ self.variables.get(name)
23
+ }
24
+
25
+ pub fn remove(&mut self, name: &str) -> Option<Value> {
26
+ self.variables.remove(name)
27
+ }
28
+ }
@@ -0,0 +1,2 @@
1
+ pub mod path;
2
+ pub mod validation;
@@ -0,0 +1,31 @@
1
+ use std::path::{ Component, Path, PathBuf };
2
+
3
+ pub fn find_entry_file(entry: &str) -> Option<String> {
4
+ let path = Path::new(entry);
5
+
6
+ if path.is_file() {
7
+ return Some(normalize_path(entry));
8
+ }
9
+
10
+ if path.is_dir() {
11
+ let candidate = path.join("index.deva");
12
+ if candidate.exists() {
13
+ return Some(normalize_path(&candidate));
14
+ }
15
+ }
16
+
17
+ None
18
+ }
19
+
20
+ pub fn normalize_path<P: AsRef<Path>>(path: P) -> String {
21
+ let path_buf = PathBuf::from(path.as_ref());
22
+ path_buf.components().collect::<PathBuf>().to_string_lossy().replace('\\', "/")
23
+ }
24
+
25
+ pub fn resolve_relative_path(base: &str, import: &str) -> String {
26
+ let base_path = Path::new(base)
27
+ .parent()
28
+ .unwrap_or_else(|| Path::new(""));
29
+ let full_path = base_path.join(import);
30
+ full_path.components().collect::<PathBuf>().to_string_lossy().replace("\\", "/")
31
+ }
@@ -0,0 +1,37 @@
1
+ use crate::core::{ preprocessor::module::Module, shared::value::Value, store::global::GlobalStore };
2
+
3
+ // NOTE: Deprecated functions, kept for reference
4
+
5
+ // pub fn is_valid_entity(entity: &str, module: &Module, global_store: &GlobalStore) -> bool {
6
+ // let built_ins = ["kick", "snare", "hat", "clap"];
7
+
8
+ // if built_ins.contains(&entity) {
9
+ // return true;
10
+ // }
11
+
12
+ // if let Some(val) = module.variable_table.get(entity) {
13
+ // match val {
14
+ // Value::Sample(_) => true,
15
+ // _ => false,
16
+ // }
17
+ // } else {
18
+ // false
19
+ // }
20
+ // }
21
+
22
+ // pub fn is_valid_identifier(ident: &str, module: &Module) -> bool {
23
+ // let built_ins = ["auto"];
24
+
25
+ // if built_ins.contains(&ident) {
26
+ // return true;
27
+ // }
28
+
29
+ // if let Some(val) = module.variable_table.get(ident) {
30
+ // match val {
31
+ // Value::Identifier(_) => true,
32
+ // _ => false,
33
+ // }
34
+ // } else {
35
+ // false
36
+ // }
37
+ // }
package/rust/lib.rs CHANGED
@@ -1 +1,161 @@
1
- // TODO WebAssembly support
1
+ pub mod core;
2
+ pub mod utils;
3
+ pub mod config;
4
+
5
+ use serde::{ Deserialize, Serialize };
6
+ use wasm_bindgen::prelude::*;
7
+ use serde_wasm_bindgen::to_value;
8
+
9
+ use crate::core::{
10
+ audio::{ engine::AudioEngine, interpreter::driver::{ run_audio_program } },
11
+ parser::statement::{ Statement, StatementKind },
12
+ preprocessor::loader::ModuleLoader,
13
+ shared::value::Value,
14
+ store::global::GlobalStore,
15
+ utils::path::normalize_path,
16
+ };
17
+
18
+ #[derive(Serialize, Deserialize)]
19
+ struct ParseResult {
20
+ ok: bool,
21
+ ast: String,
22
+ errors: Vec<ErrorResult>,
23
+ }
24
+
25
+ #[derive(Serialize, Deserialize)]
26
+ struct ErrorResult {
27
+ message: String,
28
+ line: usize,
29
+ column: usize,
30
+ }
31
+
32
+ #[wasm_bindgen]
33
+ pub fn parse(entry_path: &str, source: &str) -> Result<JsValue, JsValue> {
34
+ let statements = parse_internal_from_string(entry_path, source);
35
+
36
+ match statements {
37
+ Ok(value) => {
38
+ let ast_string = value;
39
+ to_value(&ast_string).map_err(|e|
40
+ JsValue::from_str(&format!("Error converting AST to JS value: {}", e))
41
+ )
42
+ }
43
+ Err(e) => { Err(JsValue::from_str(&format!("Error: {}", e))) }
44
+ }
45
+ }
46
+
47
+ #[wasm_bindgen]
48
+ pub fn render_audio(user_code: &str) -> Result<js_sys::Float32Array, JsValue> {
49
+ let entry_path = normalize_path("playground.deva");
50
+ let output_path = normalize_path("./temp");
51
+
52
+ let mut global_store = GlobalStore::new();
53
+
54
+ let loader = ModuleLoader::from_raw_source(
55
+ &entry_path,
56
+ &output_path,
57
+ user_code,
58
+ &mut global_store
59
+ );
60
+
61
+ loader
62
+ .load_wasm_module(&mut global_store)
63
+ .map_err(|e| JsValue::from_str(&format!("Module loading error: {}", e)))?;
64
+
65
+ let all_statements_map = loader.extract_statements_map(&global_store);
66
+
67
+ let main_statements = all_statements_map
68
+ .get(&entry_path)
69
+ .ok_or(JsValue::from_str("❌ No statements found for entry module"))?
70
+ .clone();
71
+
72
+ let audio_engine = AudioEngine::new("wasm_output".to_string());
73
+
74
+ let (final_engine, _, _) = run_audio_program(
75
+ &main_statements,
76
+ audio_engine,
77
+ "playground".to_string(),
78
+ "wasm_output".to_string()
79
+ );
80
+
81
+ let samples = final_engine.get_normalized_buffer();
82
+
83
+ if samples.is_empty() {
84
+ return Err(JsValue::from_str("❌ Audio buffer is empty"));
85
+ }
86
+
87
+ Ok(js_sys::Float32Array::from(samples.as_slice()))
88
+ }
89
+
90
+ fn parse_internal_from_string(virtual_path: &str, source: &str) -> Result<ParseResult, String> {
91
+ let entry_path = normalize_path(virtual_path);
92
+ let output_path = normalize_path("./temp");
93
+
94
+ let mut global_store = GlobalStore::new();
95
+ let loader = ModuleLoader::from_raw_source(
96
+ &entry_path,
97
+ &output_path,
98
+ source,
99
+ &mut global_store
100
+ );
101
+
102
+ let module = loader
103
+ .load_single_module(&mut global_store)
104
+ .map_err(|e| format!("Error loading module: {}", e))?;
105
+
106
+ let raw_ast = ast_to_string(module.statements.clone());
107
+
108
+ let found_errors = collect_errors_recursively(&module.statements);
109
+
110
+ let result = ParseResult {
111
+ ok: true,
112
+ ast: raw_ast,
113
+ errors: found_errors,
114
+ };
115
+
116
+ Ok(result)
117
+ }
118
+
119
+ fn collect_errors_recursively(statements: &[Statement]) -> Vec<ErrorResult> {
120
+ let mut errors: Vec<ErrorResult> = Vec::new();
121
+
122
+ for stmt in statements {
123
+ match &stmt.kind {
124
+ StatementKind::Unknown => {
125
+ errors.push(ErrorResult {
126
+ message: format!("Unknown statement at line {}:{}", stmt.line, stmt.column),
127
+ line: stmt.line,
128
+ column: stmt.column,
129
+ });
130
+ }
131
+ StatementKind::Error { message } => {
132
+ errors.push(ErrorResult {
133
+ message: message.clone(),
134
+ line: stmt.line,
135
+ column: stmt.column,
136
+ });
137
+ }
138
+ StatementKind::Loop => {
139
+ if let Some(body_statements) = extract_loop_body_statements(&stmt.value) {
140
+ errors.extend(collect_errors_recursively(body_statements));
141
+ }
142
+ }
143
+ _ => {}
144
+ }
145
+ }
146
+
147
+ errors
148
+ }
149
+
150
+ fn extract_loop_body_statements(value: &Value) -> Option<&[Statement]> {
151
+ if let Value::Map(map) = value {
152
+ if let Some(Value::Block(statements)) = map.get("body") {
153
+ return Some(statements);
154
+ }
155
+ }
156
+ None
157
+ }
158
+
159
+ fn ast_to_string(statements: Vec<Statement>) -> String {
160
+ serde_json::to_string_pretty(&statements).expect("Failed to serialize AST")
161
+ }
package/rust/main.rs CHANGED
@@ -1,48 +1,64 @@
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;
6
7
 
7
- use std::{ io };
8
+ use std::io;
9
+ use cli::{ Cli };
8
10
  use clap::Parser;
9
11
  use crate::{
10
- cli::{ build::handle_build_command, check::handle_check_command },
11
- core::types::cli::{ Cli, CliCommands },
12
+ cli::{
13
+ build::handle_build_command,
14
+ check::handle_check_command,
15
+ init::handle_init_command,
16
+ play::handle_play_command,
17
+ template::{ handle_template_info_command, handle_template_list_command },
18
+ Commands,
19
+ TemplateCommand,
20
+ },
21
+ config::{ loader::load_config, Config },
12
22
  };
13
23
 
14
24
  fn main() -> io::Result<()> {
15
- let cli = Cli::parse();
25
+ let cli: Cli = Cli::parse();
26
+ let mut config: Option<Config> = None;
27
+
28
+ if !cli.no_config {
29
+ config = load_config(None);
30
+ } else {
31
+ println!("No configuration file loaded. Running with arguments only.");
32
+ }
16
33
 
17
34
  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);
35
+ Commands::Init { name, template } => {
36
+ handle_init_command(name, template);
37
+ }
38
+
39
+ Commands::Template { command } =>
40
+ match command {
41
+ TemplateCommand::List => {
42
+ handle_template_list_command();
43
+ }
44
+ TemplateCommand::Info { name } => {
45
+ handle_template_info_command(name);
46
+ }
47
+ }
48
+
49
+ Commands::Check { entry, output, watch, compilation_mode, debug } => {
50
+ handle_check_command(config, entry, output, watch);
51
+ }
52
+
53
+ Commands::Build { entry, output, watch, compilation_mode, debug, compress } => {
54
+ handle_build_command(config, entry, output, watch);
36
55
  }
37
56
 
38
- CliCommands::Check { entry, output, watch, compilation_mode, debug } => {
39
- handle_check_command(entry, output);
57
+ Commands::Play { entry, output, watch, repeat } => {
58
+ handle_play_command(config, entry, output, watch, repeat);
40
59
  }
41
60
 
42
- // TODO - Implement the play command
43
- // CliCommands::Play {} => {
44
- // log_message("Command 'play' is not implemented yet.", "WARNING");
45
- // }
61
+ _ => {}
46
62
  }
47
63
 
48
64
  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
+ }
@@ -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,6 @@
1
1
  pub mod version;
2
2
  pub mod signature;
3
- pub mod path;
4
- pub mod loader;
3
+ pub mod spinner;
4
+ pub mod watcher;
5
+ pub mod file;
5
6
  pub mod logger;
@@ -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