@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.15

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 (110) hide show
  1. package/.devalang +2 -3
  2. package/Cargo.toml +58 -54
  3. package/README.md +59 -27
  4. package/docs/CHANGELOG.md +99 -2
  5. package/docs/CONTRIBUTING.md +1 -0
  6. package/docs/ROADMAP.md +3 -3
  7. package/docs/TODO.md +5 -4
  8. package/examples/automation.deva +44 -0
  9. package/examples/bank.deva +2 -4
  10. package/examples/function.deva +15 -0
  11. package/examples/index.deva +41 -11
  12. package/examples/plugin.deva +15 -0
  13. package/out-tsc/bin/devalang.exe +0 -0
  14. package/package.json +6 -6
  15. package/project-version.json +3 -3
  16. package/rust/cli/bank.rs +16 -16
  17. package/rust/cli/build.rs +69 -30
  18. package/rust/cli/check.rs +46 -6
  19. package/rust/cli/driver.rs +40 -28
  20. package/rust/cli/install.rs +22 -7
  21. package/rust/cli/login.rs +134 -0
  22. package/rust/cli/mod.rs +2 -1
  23. package/rust/cli/play.rs +44 -19
  24. package/rust/cli/update.rs +1 -1
  25. package/rust/common/api.rs +5 -0
  26. package/rust/common/cdn.rs +3 -9
  27. package/rust/common/mod.rs +3 -1
  28. package/rust/common/sso.rs +5 -0
  29. package/rust/config/driver.rs +19 -1
  30. package/rust/config/loader.rs +56 -10
  31. package/rust/core/audio/engine.rs +314 -63
  32. package/rust/core/audio/evaluator.rs +101 -0
  33. package/rust/core/audio/interpreter/arrow_call.rs +60 -15
  34. package/rust/core/audio/interpreter/automate.rs +18 -0
  35. package/rust/core/audio/interpreter/call.rs +4 -4
  36. package/rust/core/audio/interpreter/condition.rs +3 -3
  37. package/rust/core/audio/interpreter/driver.rs +68 -30
  38. package/rust/core/audio/interpreter/let_.rs +14 -7
  39. package/rust/core/audio/interpreter/loop_.rs +39 -6
  40. package/rust/core/audio/interpreter/mod.rs +2 -1
  41. package/rust/core/audio/interpreter/sleep.rs +2 -4
  42. package/rust/core/audio/interpreter/spawn.rs +4 -4
  43. package/rust/core/audio/loader/trigger.rs +2 -5
  44. package/rust/core/audio/mod.rs +2 -1
  45. package/rust/core/audio/renderer.rs +1 -1
  46. package/rust/core/audio/special/easing.rs +120 -0
  47. package/rust/core/audio/special/env.rs +41 -0
  48. package/rust/core/audio/special/math.rs +92 -0
  49. package/rust/core/audio/special/mod.rs +9 -0
  50. package/rust/core/audio/special/modulator.rs +120 -0
  51. package/rust/core/builder/mod.rs +11 -6
  52. package/rust/core/debugger/store.rs +1 -1
  53. package/rust/core/error/mod.rs +4 -1
  54. package/rust/core/lexer/handler/arrow.rs +60 -9
  55. package/rust/core/lexer/handler/at.rs +4 -4
  56. package/rust/core/lexer/handler/brace.rs +8 -8
  57. package/rust/core/lexer/handler/colon.rs +4 -4
  58. package/rust/core/lexer/handler/comment.rs +2 -2
  59. package/rust/core/lexer/handler/dot.rs +4 -4
  60. package/rust/core/lexer/handler/driver.rs +42 -13
  61. package/rust/core/lexer/handler/identifier.rs +5 -4
  62. package/rust/core/lexer/handler/indent.rs +16 -2
  63. package/rust/core/lexer/handler/newline.rs +1 -1
  64. package/rust/core/lexer/handler/number.rs +3 -3
  65. package/rust/core/lexer/handler/operator.rs +3 -1
  66. package/rust/core/lexer/handler/parenthesis.rs +8 -8
  67. package/rust/core/lexer/handler/slash.rs +5 -5
  68. package/rust/core/lexer/handler/string.rs +1 -1
  69. package/rust/core/lexer/mod.rs +1 -1
  70. package/rust/core/lexer/token.rs +4 -0
  71. package/rust/core/mod.rs +2 -1
  72. package/rust/core/parser/driver.rs +134 -11
  73. package/rust/core/parser/handler/arrow_call.rs +141 -65
  74. package/rust/core/parser/handler/at.rs +1 -1
  75. package/rust/core/parser/handler/bank.rs +35 -7
  76. package/rust/core/parser/handler/dot.rs +43 -22
  77. package/rust/core/parser/handler/identifier/automate.rs +194 -0
  78. package/rust/core/parser/handler/identifier/function.rs +2 -3
  79. package/rust/core/parser/handler/identifier/let_.rs +16 -0
  80. package/rust/core/parser/handler/identifier/mod.rs +14 -10
  81. package/rust/core/parser/handler/identifier/print.rs +29 -0
  82. package/rust/core/parser/handler/identifier/sleep.rs +1 -1
  83. package/rust/core/parser/handler/identifier/synth.rs +7 -9
  84. package/rust/core/parser/handler/loop_.rs +60 -43
  85. package/rust/core/parser/statement.rs +5 -0
  86. package/rust/core/plugin/loader.rs +48 -0
  87. package/rust/core/plugin/mod.rs +1 -0
  88. package/rust/core/preprocessor/loader.rs +7 -5
  89. package/rust/core/preprocessor/processor.rs +4 -4
  90. package/rust/core/preprocessor/resolver/bank.rs +1 -2
  91. package/rust/core/preprocessor/resolver/call.rs +19 -18
  92. package/rust/core/preprocessor/resolver/driver.rs +7 -5
  93. package/rust/core/preprocessor/resolver/function.rs +3 -13
  94. package/rust/core/preprocessor/resolver/loop_.rs +31 -1
  95. package/rust/core/preprocessor/resolver/spawn.rs +3 -22
  96. package/rust/core/preprocessor/resolver/tempo.rs +1 -1
  97. package/rust/core/preprocessor/resolver/trigger.rs +2 -3
  98. package/rust/core/preprocessor/resolver/value.rs +6 -12
  99. package/rust/core/shared/bank.rs +1 -1
  100. package/rust/core/utils/path.rs +1 -1
  101. package/rust/core/utils/validation.rs +0 -1
  102. package/rust/installer/addon.rs +80 -0
  103. package/rust/installer/bank.rs +25 -15
  104. package/rust/installer/mod.rs +4 -1
  105. package/rust/installer/plugin.rs +55 -0
  106. package/rust/main.rs +32 -10
  107. package/rust/utils/error.rs +51 -0
  108. package/rust/utils/logger.rs +20 -0
  109. package/rust/utils/mod.rs +1 -44
  110. package/rust/utils/spinner.rs +3 -5
package/rust/cli/bank.rs CHANGED
@@ -1,6 +1,6 @@
1
1
  use std::fs;
2
2
 
3
- use serde::{ Deserialize, Serialize };
3
+ use serde::Deserialize;
4
4
  use crate::{
5
5
  common::cdn::get_cdn_url,
6
6
  config::loader::{ load_config, remove_bank_from_config, update_bank_version_in_config },
@@ -102,7 +102,7 @@ pub async fn handle_update_bank_command(name: Option<String>) -> Result<(), Stri
102
102
  );
103
103
  }
104
104
 
105
- let mut config = load_config(Some(&config_path)).ok_or_else(||
105
+ let config = load_config(Some(&config_path)).ok_or_else(||
106
106
  format!("Failed to load config from '{}'", config_path.display())
107
107
  )?;
108
108
 
@@ -255,8 +255,8 @@ pub async fn handle_remove_bank_command(name: String) -> Result<(), String> {
255
255
  pub async fn handle_bank_available_command() -> Result<(), String> {
256
256
  let bank_list = match list_external_banks().await {
257
257
  Ok(list) => list,
258
- Err(err) => {
259
- eprintln!("❌ Error fetching bank list: {}", err);
258
+ Err(_err) => {
259
+ eprintln!("❌ Error fetching bank list");
260
260
  return Err("Failed to fetch bank list".into());
261
261
  }
262
262
  };
@@ -283,7 +283,7 @@ pub async fn handle_bank_info_command(
283
283
 
284
284
  let response = match reqwest::get(&url).await {
285
285
  Ok(resp) => resp,
286
- Err(err) => {
286
+ Err(_err) => {
287
287
  return Err("Failed to fetch bank info".into());
288
288
  }
289
289
  };
@@ -294,8 +294,8 @@ pub async fn handle_bank_info_command(
294
294
 
295
295
  let bytes = match response.bytes().await {
296
296
  Ok(b) => b,
297
- Err(err) => {
298
- eprintln!("❌ Error reading response body: {}", err);
297
+ Err(_err) => {
298
+ eprintln!("❌ Error reading response body");
299
299
  return Err("Failed to read response body".into());
300
300
  }
301
301
  };
@@ -314,8 +314,8 @@ pub async fn handle_bank_info_command(
314
314
  pub async fn handle_bank_list_command() -> Result<(), String> {
315
315
  let bank_list = match list_installed_banks().await {
316
316
  Ok(list) => list,
317
- Err(err) => {
318
- eprintln!("❌ Error fetching bank list: {}", err);
317
+ Err(_err) => {
318
+ eprintln!("❌ Error fetching bank list");
319
319
  return Err("Failed to fetch bank list".into());
320
320
  }
321
321
  };
@@ -325,11 +325,10 @@ pub async fn handle_bank_list_command() -> Result<(), String> {
325
325
  for bank_toml in bank_list {
326
326
  let latest_version = match fetch_latest_version(bank_toml.bank.name.clone()).await {
327
327
  Ok(version) => version,
328
- Err(err) => {
328
+ Err(_err) => {
329
329
  eprintln!(
330
- "❌ Error fetching latest version for '{}': {}",
331
- bank_toml.bank.name,
332
- err
330
+ "❌ Error fetching latest version for '{}'",
331
+ bank_toml.bank.name
333
332
  );
334
333
  continue;
335
334
  }
@@ -398,6 +397,7 @@ async fn list_installed_banks() -> Result<Vec<BankFile>, String> {
398
397
  return Ok(banks); // No banks installed
399
398
  }
400
399
 
400
+ // TODO: Verify installed banks in files
401
401
  // let installed_banks = std::fs
402
402
  // ::read_dir(bank_dir)
403
403
  // .map_err(|e| format!("Failed to read bank directory: {}", e))?;
@@ -416,7 +416,7 @@ async fn list_installed_banks() -> Result<Vec<BankFile>, String> {
416
416
  );
417
417
  }
418
418
 
419
- let mut config = load_config(Some(&config_path)).ok_or_else(||
419
+ let config = load_config(Some(&config_path)).ok_or_else(||
420
420
  format!("Failed to load config from '{}'", config_path.display())
421
421
  )?;
422
422
 
@@ -440,8 +440,8 @@ async fn list_installed_banks() -> Result<Vec<BankFile>, String> {
440
440
 
441
441
  match toml::from_str::<BankFile>(&content) {
442
442
  Ok(bank_info) => banks.push(bank_info),
443
- Err(err) => {
444
- eprintln!("❌ Error parsing bank info for '{}': {}", bank_name, err);
443
+ Err(_err) => {
444
+ eprintln!("❌ Error parsing bank info for '{}'", bank_name);
445
445
  }
446
446
  }
447
447
  } else {
package/rust/cli/build.rs CHANGED
@@ -21,7 +21,9 @@ pub fn handle_build_command(
21
21
  config: Option<Config>,
22
22
  entry: Option<String>,
23
23
  output: Option<String>,
24
- watch: bool
24
+ watch: bool,
25
+ debug: bool,
26
+ compress: bool
25
27
  ) {
26
28
  let fetched_entry = if entry.is_none() {
27
29
  config
@@ -77,7 +79,7 @@ pub fn handle_build_command(
77
79
 
78
80
  // SECTION Begin build
79
81
  if fetched_watch {
80
- begin_build(entry_file.clone(), fetched_output.clone());
82
+ begin_build(entry_file.clone(), fetched_output.clone(), debug, compress);
81
83
 
82
84
  logger.log_message(
83
85
  LogLevel::Watcher,
@@ -87,14 +89,14 @@ pub fn handle_build_command(
87
89
  watch_directory(entry_file.clone(), move || {
88
90
  logger.log_message(LogLevel::Watcher, "Detected changes, re-building...");
89
91
 
90
- begin_build(entry_file.clone(), fetched_output.clone());
92
+ begin_build(entry_file.clone(), fetched_output.clone(), debug, compress);
91
93
  }).unwrap();
92
94
  } else {
93
- begin_build(entry_file.clone(), fetched_output.clone());
95
+ begin_build(entry_file.clone(), fetched_output.clone(), debug, compress);
94
96
  }
95
97
  }
96
98
 
97
- fn begin_build(entry: String, output: String) {
99
+ fn begin_build(entry: String, output: String, debug: bool, compress: bool) {
98
100
  let spinner = with_spinner("Building...", || {
99
101
  thread::sleep(Duration::from_millis(800));
100
102
  });
@@ -112,49 +114,86 @@ fn begin_build(entry: String, output: String) {
112
114
  let (modules_tokens, modules_statements) = module_loader.load_all_modules(&mut global_store);
113
115
 
114
116
  // SECTION Write logs
115
- for (module_path, module) in global_store.modules.clone() {
116
- write_module_variable_log_file(
117
+ if debug {
118
+ for (module_path, module) in global_store.modules.clone() {
119
+ write_module_variable_log_file(
120
+ &normalized_output_dir,
121
+ &module_path,
122
+ &module.variable_table
123
+ );
124
+ write_module_function_log_file(
125
+ &normalized_output_dir,
126
+ &module_path,
127
+ &module.function_table
128
+ );
129
+ }
130
+
131
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
132
+ write_preprocessor_log_file(
117
133
  &normalized_output_dir,
118
- &module_path,
119
- &module.variable_table
134
+ "resolved_statements.log",
135
+ modules_statements.clone()
120
136
  );
121
- write_module_function_log_file(
137
+ write_variables_log_file(
122
138
  &normalized_output_dir,
123
- &module_path,
124
- &module.function_table
139
+ "global_variables.log",
140
+ global_store.variables.clone()
141
+ );
142
+ write_function_log_file(
143
+ &normalized_output_dir,
144
+ "global_functions.log",
145
+ global_store.functions.clone()
125
146
  );
126
147
  }
127
148
 
128
- write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
129
- write_preprocessor_log_file(
130
- &normalized_output_dir,
131
- "resolved_statements.log",
132
- modules_statements.clone()
133
- );
134
- write_variables_log_file(
135
- &normalized_output_dir,
136
- "global_variables.log",
137
- global_store.variables.clone()
138
- );
139
- write_function_log_file(
140
- &normalized_output_dir,
141
- "global_functions.log",
142
- global_store.functions.clone()
143
- );
144
-
145
149
  // SECTION Building AST and Audio
146
150
  let builder = Builder::new();
147
- builder.build_ast(&modules_statements, &normalized_output_dir);
151
+ builder.build_ast(&modules_statements, &normalized_output_dir, compress);
148
152
  builder.build_audio(&modules_statements, &normalized_output_dir, &mut global_store);
149
153
 
150
154
  // SECTION Logging
151
155
  let logger = Logger::new();
152
156
 
157
+ if debug {
158
+ let modules_loaded = global_store.modules
159
+ .iter()
160
+ .map(|(k, _)| k)
161
+ .collect::<Vec<_>>();
162
+ let global_variables_loaded = global_store.variables.variables.keys().collect::<Vec<_>>();
163
+ let global_functions_loaded = global_store.functions.functions.keys().collect::<Vec<_>>();
164
+
165
+ logger.log_message_with_trace(
166
+ LogLevel::Debug,
167
+ &format!("Modules loaded: {}", global_store.modules.len()),
168
+ modules_loaded
169
+ .iter()
170
+ .map(|s| s.as_str())
171
+ .collect()
172
+ );
173
+ logger.log_message_with_trace(
174
+ LogLevel::Debug,
175
+ &format!("Global variables: {}", global_store.variables.variables.len()),
176
+ global_variables_loaded
177
+ .iter()
178
+ .map(|s| s.as_str())
179
+ .collect()
180
+ );
181
+ logger.log_message_with_trace(
182
+ LogLevel::Debug,
183
+ &format!("Global functions: {}", global_store.functions.functions.len()),
184
+ global_functions_loaded
185
+ .iter()
186
+ .map(|s| s.as_str())
187
+ .collect()
188
+ );
189
+ }
190
+
153
191
  let success_message = format!(
154
192
  "Build completed successfully in {:.2?}. Output files written to: '{}'",
155
193
  duration.elapsed(),
156
194
  normalized_output_dir
157
195
  );
158
196
 
197
+ spinner.finish_and_clear();
159
198
  logger.log_message(LogLevel::Success, &success_message);
160
199
  }
package/rust/cli/check.rs CHANGED
@@ -1,12 +1,18 @@
1
1
  use crate::{
2
2
  config::driver::Config,
3
3
  core::{
4
+ debugger::{
5
+ lexer::write_lexer_log_file,
6
+ module::{ write_module_function_log_file, write_module_variable_log_file },
7
+ preprocessor::write_preprocessor_log_file,
8
+ store::{ write_function_log_file, write_variables_log_file },
9
+ },
4
10
  preprocessor::loader::ModuleLoader,
5
11
  store::global::GlobalStore,
6
12
  utils::path::{ find_entry_file, normalize_path },
7
13
  },
8
14
  utils::{
9
- collect_errors_recursively,
15
+ error::collect_errors_recursively,
10
16
  logger::{ LogLevel, Logger },
11
17
  spinner::with_spinner,
12
18
  watcher::watch_directory,
@@ -19,7 +25,8 @@ pub fn handle_check_command(
19
25
  config: Option<Config>,
20
26
  entry: Option<String>,
21
27
  output: Option<String>,
22
- watch: bool
28
+ watch: bool,
29
+ debug: bool
23
30
  ) {
24
31
  let fetched_entry = if entry.is_none() {
25
32
  config
@@ -75,7 +82,7 @@ pub fn handle_check_command(
75
82
 
76
83
  // SECTION Begin check
77
84
  if fetched_watch {
78
- begin_check(entry_file.clone(), fetched_output.clone());
85
+ begin_check(entry_file.clone(), fetched_output.clone(), debug);
79
86
 
80
87
  logger.log_message(
81
88
  LogLevel::Watcher,
@@ -85,14 +92,14 @@ pub fn handle_check_command(
85
92
  watch_directory(entry_file.clone(), move || {
86
93
  logger.log_message(LogLevel::Watcher, "Detected changes, re-checking...");
87
94
 
88
- begin_check(entry_file.clone(), fetched_output.clone());
95
+ begin_check(entry_file.clone(), fetched_output.clone(), debug);
89
96
  }).unwrap();
90
97
  } else {
91
- begin_check(entry_file.clone(), fetched_output.clone());
98
+ begin_check(entry_file.clone(), fetched_output.clone(), debug);
92
99
  }
93
100
  }
94
101
 
95
- fn begin_check(entry: String, output: String) {
102
+ fn begin_check(entry: String, output: String, debug: bool) {
96
103
  let spinner = with_spinner("Checking...", || {
97
104
  thread::sleep(Duration::from_millis(800));
98
105
  });
@@ -116,6 +123,38 @@ fn begin_check(entry: String, output: String) {
116
123
  logger.log_message(LogLevel::Info, &format!("- {}", module_name));
117
124
  }
118
125
 
126
+ if debug {
127
+ for (module_path, module) in global_store.modules.clone() {
128
+ write_module_variable_log_file(
129
+ &normalized_output_dir,
130
+ &module_path,
131
+ &module.variable_table
132
+ );
133
+ write_module_function_log_file(
134
+ &normalized_output_dir,
135
+ &module_path,
136
+ &module.function_table
137
+ );
138
+ }
139
+
140
+ write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules.0.clone());
141
+ write_preprocessor_log_file(
142
+ &normalized_output_dir,
143
+ "resolved_statements.log",
144
+ modules.1.clone()
145
+ );
146
+ write_variables_log_file(
147
+ &normalized_output_dir,
148
+ "global_variables.log",
149
+ global_store.variables.clone()
150
+ );
151
+ write_function_log_file(
152
+ &normalized_output_dir,
153
+ "global_functions.log",
154
+ global_store.functions.clone()
155
+ );
156
+ }
157
+
119
158
  let mut all_errors = Vec::new();
120
159
  for (_, statements) in &modules.1 {
121
160
  all_errors.extend(collect_errors_recursively(statements));
@@ -136,5 +175,6 @@ fn begin_check(entry: String, output: String) {
136
175
  normalized_output_dir
137
176
  );
138
177
 
178
+ spinner.finish_and_clear();
139
179
  logger.log_message(LogLevel::Success, &success_message);
140
180
  }
@@ -21,6 +21,14 @@ pub enum InstallCommand {
21
21
  Bank {
22
22
  name: String,
23
23
  },
24
+ /// Installs a plugin.
25
+ Plugin {
26
+ name: String,
27
+ },
28
+ /// Installs a preset.
29
+ Preset {
30
+ name: String,
31
+ },
24
32
  }
25
33
 
26
34
  #[derive(Subcommand)]
@@ -103,10 +111,12 @@ pub enum Commands {
103
111
  /// - `entry` - The entry point of the program to build. Defaults to "./src".
104
112
  /// - `output` - The directory where the output files will be generated. Defaults to "./output".
105
113
  /// - `watch` - Whether to watch for changes and rebuild. Defaults to "true".
114
+ /// - `debug` - Whether to print debug information. Defaults to "false".
115
+ /// - `compress` - Whether to compress the output files. Defaults to "false".
106
116
  ///
107
117
  /// ### Example
108
118
  /// ```bash
109
- /// devalang build --entry ./src --output ./output --watch true
119
+ /// devalang build --entry ./src --output ./output --watch true --debug false --compress false
110
120
  /// ```
111
121
  ///
112
122
  Build {
@@ -128,19 +138,6 @@ pub enum Commands {
128
138
  ///
129
139
  watch: bool,
130
140
 
131
- #[arg(long, default_value = "real-time")]
132
- /// The mode of compilation.
133
- ///
134
- /// ### Default value
135
- /// - `real-time`
136
- ///
137
- /// ### Possible values
138
- /// - `real-time` - Compiles files as soon as possible.
139
- /// - `batch` - Compiles files one by one.
140
- /// - `check` - Analyzes the code without compiling it.
141
- ///
142
- compilation_mode: String,
143
-
144
141
  #[arg(short, long, default_value_t = false)]
145
142
  /// Whether to print debug information.
146
143
  ///
@@ -164,9 +161,13 @@ pub enum Commands {
164
161
  /// - `entry` - The entry point of the program to analyze. Defaults to "./src".
165
162
  /// - `output` - The directory where the output files will be generated. Defaults to "./output".
166
163
  /// - `watch` - Whether to watch for changes and re-analyze. Defaults to "true".
167
- /// - `compilation_mode` - The mode of compilation. Defaults to "real-time".
168
164
  /// - `debug` - Whether to print debug information. Defaults to "false".
169
165
  ///
166
+ /// ### Example
167
+ /// ```bash
168
+ /// devalang check --entry ./src --output ./output --watch true --debug false
169
+ /// ```
170
+ ///
170
171
  Check {
171
172
  #[arg(short, long)]
172
173
  /// The entry point of the program to analyze.
@@ -184,19 +185,6 @@ pub enum Commands {
184
185
  ///
185
186
  watch: bool,
186
187
 
187
- #[arg(short, long, default_value = "real-time")]
188
- /// The mode of compilation.
189
- ///
190
- /// ### Default value
191
- /// - `real-time`
192
- ///
193
- /// ### Possible values
194
- /// - `real-time` - Analyzes files as soon as possible.
195
- /// - `batch` - Analyzes files one by one.
196
- /// - `check` - Analyzes the code without compiling it.
197
- ///
198
- compilation_mode: String,
199
-
200
188
  #[arg(short, long, default_value_t = false)]
201
189
  /// Whether to print debug information.
202
190
  ///
@@ -213,6 +201,14 @@ pub enum Commands {
213
201
  /// - `output` - The directory where the output files will be generated. Defaults to "./output".
214
202
  /// - `watch` - Whether to watch for changes and re-play. Defaults to "false".
215
203
  /// - `repeat` - Whether to replay the program after it finishes. Defaults to "false".
204
+ /// - `debug` - Whether to print debug information. Defaults to "false".
205
+ ///
206
+ /// Note: `--repeat` and `--watch` cannot be used together. Instead use `repeat` to watch for changes and replay the program.
207
+ ///
208
+ /// ### Example
209
+ /// ```bash
210
+ /// devalang play --entry ./src --output ./output --repeat true --debug false
211
+ /// ```
216
212
  ///
217
213
  Play {
218
214
  #[arg(short, long)]
@@ -238,6 +234,14 @@ pub enum Commands {
238
234
  /// - `false`
239
235
  ///
240
236
  repeat: bool,
237
+
238
+ #[arg(short, long, default_value_t = false)]
239
+ /// Whether to print debug information.
240
+ ///
241
+ /// ### Default value
242
+ /// - `false`
243
+ ///
244
+ debug: bool,
241
245
  },
242
246
 
243
247
  /// Update the Devalang CLI to the latest version.
@@ -277,4 +281,12 @@ pub enum Commands {
277
281
  #[command(subcommand)]
278
282
  command: BankCommand,
279
283
  },
284
+
285
+ /// Log in to your Devaloop account.
286
+ ///
287
+ Login {},
288
+
289
+ /// Log out of your Devaloop account.
290
+ ///
291
+ Logout {},
280
292
  }
@@ -1,16 +1,31 @@
1
- use crate::installer::bank::install_bank;
1
+ use crate::installer::addon::{ install_addon, AddonType };
2
2
 
3
+ /// Handles the installation command for a given addon type and name.
3
4
  #[cfg(feature = "cli")]
4
- pub async fn handle_install_bank_command(name: String) -> Result<(), String> {
5
+ pub async fn handle_install_command(name: String, addon_type: AddonType) -> Result<(), String> {
6
+ use crate::utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner };
7
+ use std::{ thread, time::Duration };
8
+
9
+ let logger = Logger::new();
5
10
  let deva_dir = std::path::Path::new("./.deva/");
6
11
 
7
- println!("⬇️ Installing bank '{}'...", name);
8
- println!("📂 Target directory: {}", deva_dir.display());
12
+ let spinner = with_spinner("Installing...", || {
13
+ thread::sleep(Duration::from_millis(800));
14
+ });
9
15
 
10
- if let Err(e) = install_bank(name.as_str(), deva_dir).await {
11
- eprintln!("❌ Error installing bank '{}': {}", name, e);
16
+ if let Err(e) = install_addon(addon_type.clone(), name.as_str(), deva_dir).await {
17
+ spinner.finish_and_clear();
18
+ logger.log_message_with_trace(
19
+ LogLevel::Error,
20
+ &format!("Error installing {:?} '{}'", addon_type, name),
21
+ vec![&e]
22
+ );
12
23
  } else {
13
- println!("✅ Bank '{}' installed successfully!", name);
24
+ spinner.finish_and_clear();
25
+ logger.log_message(
26
+ LogLevel::Success,
27
+ &format!("{:?} '{}' installed successfully!", addon_type, name)
28
+ );
14
29
  }
15
30
 
16
31
  Ok(())
@@ -0,0 +1,134 @@
1
+ use std::fs;
2
+ use tiny_http::{ Server, Response };
3
+ use webbrowser;
4
+ use serde::{ Serialize, Deserialize };
5
+ use dirs::home_dir;
6
+ use crate::common::sso::get_sso_url;
7
+ use std::{ thread, time::Duration };
8
+ use tiny_http::Header;
9
+ use std::str::FromStr;
10
+
11
+ #[derive(Serialize, Deserialize)]
12
+ struct UserConfig {
13
+ token: String,
14
+ }
15
+
16
+ /// Handle the login command
17
+ /// This function initiates the login process by opening the browser and waiting for the callback.
18
+ #[cfg(feature = "cli")]
19
+ pub async fn handle_login_command() -> Result<(), String> {
20
+ use crate::utils::spinner::with_spinner;
21
+ use crate::utils::logger::Logger;
22
+ use crate::utils::logger::LogLevel;
23
+
24
+ let logger = Logger::new();
25
+
26
+ let mut listener_port = 7878;
27
+
28
+ let test_port_already_in_use = format!("127.0.0.1:{}", listener_port);
29
+ while std::net::TcpListener::bind(&test_port_already_in_use).is_err() {
30
+ listener_port += 1;
31
+ }
32
+
33
+ let redirect_uri = format!("http://127.0.0.1:{}/callback", listener_port);
34
+ let login_url = format!(
35
+ "{}/?response_type=code&referer=cli&redirect_uri={}",
36
+ get_sso_url(),
37
+ redirect_uri
38
+ );
39
+
40
+ if webbrowser::open(&login_url).is_ok() {
41
+ logger.log_message(LogLevel::Info, "Opening browser for login...");
42
+ logger.log_message(
43
+ LogLevel::Info,
44
+ &format!("If the browser does not open, please visit the following URL: {}", login_url)
45
+ );
46
+ } else {
47
+ logger.log_message(
48
+ LogLevel::Info,
49
+ "Please open the following URL in your browser to login:"
50
+ );
51
+ logger.log_message(LogLevel::Info, &login_url);
52
+ }
53
+
54
+ let server = Server::http(format!("127.0.0.1:{}", listener_port)).unwrap();
55
+
56
+ let spinner = with_spinner("Waiting for authentication...", || {
57
+ thread::sleep(Duration::from_millis(800));
58
+ });
59
+
60
+ for request in server.incoming_requests() {
61
+ let query = request.url().to_string();
62
+ if request.url().starts_with("/callback") {
63
+ if query.contains("session=") || query.contains("error=") {
64
+ let token = query.split("session=").nth(1).unwrap_or("").to_string();
65
+
66
+ if token.len() > 0 {
67
+ let response_html =
68
+ r#"
69
+ <html>
70
+ <body>
71
+ <h1>Authentication Successful</h1>
72
+ <h2>You can now close this window.</h2>
73
+ </body>
74
+ </html>
75
+ "#;
76
+
77
+ let response = Response::from_string(response_html)
78
+ .with_status_code(200)
79
+ .with_header(Header::from_str("Content-Type: text/html").unwrap());
80
+
81
+ request.respond(response).unwrap();
82
+
83
+ save_token(&token);
84
+
85
+ spinner.finish_and_clear();
86
+
87
+ logger.log_message(
88
+ LogLevel::Success,
89
+ "Authentication successful. Token saved to ~/.devalang/session_token.json"
90
+ );
91
+
92
+ break;
93
+ } else {
94
+ spinner.finish_and_clear();
95
+ logger.log_message(LogLevel::Error, "Invalid session token.");
96
+ request.respond(Response::from_string("Invalid session token.")).unwrap();
97
+
98
+ break;
99
+ }
100
+ } else {
101
+ println!("Invalid callback: {}", request.url());
102
+
103
+ spinner.finish_and_clear();
104
+ logger.log_message(LogLevel::Error, "Invalid callback.");
105
+ request.respond(Response::from_string("Invalid callback.")).unwrap();
106
+
107
+ break;
108
+ }
109
+ } else if request.url().starts_with("/favicon.ico") {
110
+ // Ignore favicon requests
111
+ } else {
112
+ spinner.finish_and_clear();
113
+ logger.log_message(LogLevel::Error, "Invalid request.");
114
+ request.respond(Response::from_string("Invalid request.")).unwrap();
115
+
116
+ break;
117
+ }
118
+ }
119
+
120
+ Ok(())
121
+ }
122
+
123
+ /// Save the session token to a file in the user's home directory
124
+ fn save_token(token: &str) {
125
+ let config = UserConfig { token: token.to_string() };
126
+ let json = serde_json::to_string(&config).unwrap();
127
+
128
+ let mut path = home_dir().unwrap();
129
+ path.push(".devalang");
130
+ fs::create_dir_all(&path).unwrap();
131
+
132
+ path.push("session_token.json");
133
+ fs::write(path, json).unwrap();
134
+ }
package/rust/cli/mod.rs CHANGED
@@ -7,4 +7,5 @@ pub mod template;
7
7
  pub mod play;
8
8
  pub mod install;
9
9
  pub mod bank;
10
- pub mod update;
10
+ pub mod update;
11
+ pub mod login;