@devaloop/devalang 0.0.1-alpha.15 → 0.0.1-alpha.16-hotfix.0

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 (173) hide show
  1. package/.devalang +2 -0
  2. package/.github/workflows/ci.yml +92 -0
  3. package/Cargo.toml +60 -58
  4. package/README.md +1 -1
  5. package/docs/CHANGELOG.md +34 -1
  6. package/docs/CONTRIBUTING.md +101 -1
  7. package/docs/ROADMAP.md +1 -1
  8. package/docs/TODO.md +1 -1
  9. package/examples/automation.deva +1 -3
  10. package/examples/bank.deva +4 -4
  11. package/examples/events.deva +12 -0
  12. package/examples/function.deva +4 -4
  13. package/examples/index.deva +3 -5
  14. package/examples/loop.deva +5 -11
  15. package/examples/pattern.deva +8 -0
  16. package/examples/plugin.deva +12 -11
  17. package/examples/variables.deva +1 -1
  18. package/out-tsc/bin/index.js +51 -7
  19. package/out-tsc/index.js +3 -1
  20. package/out-tsc/scripts/postbuild.js +9 -10
  21. package/out-tsc/scripts/postinstall.js +49 -0
  22. package/package.json +12 -4
  23. package/project-version.json +3 -3
  24. package/rust/cli/bank.rs +462 -455
  25. package/rust/cli/build.rs +252 -199
  26. package/rust/cli/check.rs +221 -180
  27. package/rust/cli/driver.rs +297 -292
  28. package/rust/cli/generator.rs +1 -0
  29. package/rust/cli/init.rs +87 -79
  30. package/rust/cli/install.rs +35 -32
  31. package/rust/cli/login.rs +127 -134
  32. package/rust/cli/mod.rs +13 -11
  33. package/rust/cli/play.rs +1123 -218
  34. package/rust/cli/telemetry.rs +19 -0
  35. package/rust/cli/template.rs +69 -57
  36. package/rust/cli/update.rs +6 -4
  37. package/rust/common/api.rs +5 -5
  38. package/rust/common/mod.rs +3 -3
  39. package/rust/config/driver.rs +118 -94
  40. package/rust/config/loader.rs +165 -156
  41. package/rust/config/mod.rs +4 -2
  42. package/rust/config/settings.rs +91 -0
  43. package/rust/config/stats.rs +257 -0
  44. package/rust/core/audio/engine.rs +696 -659
  45. package/rust/core/audio/evaluator.rs +263 -132
  46. package/rust/core/audio/interpreter/arrow_call.rs +198 -187
  47. package/rust/core/audio/interpreter/call.rs +98 -95
  48. package/rust/core/audio/interpreter/condition.rs +70 -71
  49. package/rust/core/audio/interpreter/driver.rs +487 -231
  50. package/rust/core/audio/interpreter/function.rs +26 -21
  51. package/rust/core/audio/interpreter/let_.rs +38 -26
  52. package/rust/core/audio/interpreter/load.rs +18 -18
  53. package/rust/core/audio/interpreter/loop_.rs +113 -106
  54. package/rust/core/audio/interpreter/mod.rs +14 -14
  55. package/rust/core/audio/interpreter/sleep.rs +27 -28
  56. package/rust/core/audio/interpreter/spawn.rs +105 -102
  57. package/rust/core/audio/interpreter/tempo.rs +19 -16
  58. package/rust/core/audio/interpreter/trigger.rs +239 -210
  59. package/rust/core/audio/loader/mod.rs +1 -1
  60. package/rust/core/audio/loader/trigger.rs +100 -94
  61. package/rust/core/audio/mod.rs +7 -7
  62. package/rust/core/audio/player.rs +64 -64
  63. package/rust/core/audio/renderer.rs +56 -53
  64. package/rust/core/audio/special/easing.rs +189 -120
  65. package/rust/core/audio/special/env.rs +43 -41
  66. package/rust/core/audio/special/math.rs +102 -92
  67. package/rust/core/audio/special/mod.rs +9 -9
  68. package/rust/core/audio/special/modulator.rs +143 -120
  69. package/rust/core/builder/mod.rs +80 -85
  70. package/rust/core/debugger/lexer.rs +27 -27
  71. package/rust/core/debugger/mod.rs +24 -23
  72. package/rust/core/debugger/module.rs +55 -47
  73. package/rust/core/debugger/preprocessor.rs +27 -27
  74. package/rust/core/debugger/store.rs +40 -39
  75. package/rust/core/error/mod.rs +80 -69
  76. package/rust/core/lexer/handler/arrow.rs +82 -82
  77. package/rust/core/lexer/handler/at.rs +21 -21
  78. package/rust/core/lexer/handler/brace.rs +41 -41
  79. package/rust/core/lexer/handler/colon.rs +21 -21
  80. package/rust/core/lexer/handler/comment.rs +30 -30
  81. package/rust/core/lexer/handler/dot.rs +21 -21
  82. package/rust/core/lexer/handler/driver.rs +337 -292
  83. package/rust/core/lexer/handler/identifier.rs +46 -43
  84. package/rust/core/lexer/handler/indent.rs +66 -66
  85. package/rust/core/lexer/handler/mod.rs +16 -16
  86. package/rust/core/lexer/handler/newline.rs +23 -23
  87. package/rust/core/lexer/handler/number.rs +31 -31
  88. package/rust/core/lexer/handler/operator.rs +46 -46
  89. package/rust/core/lexer/handler/parenthesis.rs +41 -41
  90. package/rust/core/lexer/handler/slash.rs +21 -21
  91. package/rust/core/lexer/handler/string.rs +63 -63
  92. package/rust/core/lexer/mod.rs +54 -51
  93. package/rust/core/lexer/token.rs +97 -94
  94. package/rust/core/mod.rs +11 -11
  95. package/rust/core/parser/driver.rs +513 -490
  96. package/rust/core/parser/handler/arrow_call.rs +233 -227
  97. package/rust/core/parser/handler/at.rs +245 -162
  98. package/rust/core/parser/handler/bank.rs +94 -69
  99. package/rust/core/parser/handler/condition.rs +80 -74
  100. package/rust/core/parser/handler/dot.rs +143 -135
  101. package/rust/core/parser/handler/identifier/automate.rs +257 -194
  102. package/rust/core/parser/handler/identifier/call.rs +91 -88
  103. package/rust/core/parser/handler/identifier/emit.rs +66 -0
  104. package/rust/core/parser/handler/identifier/function.rs +100 -91
  105. package/rust/core/parser/handler/identifier/group.rs +85 -75
  106. package/rust/core/parser/handler/identifier/let_.rs +158 -143
  107. package/rust/core/parser/handler/identifier/mod.rs +54 -56
  108. package/rust/core/parser/handler/identifier/on.rs +98 -0
  109. package/rust/core/parser/handler/identifier/print.rs +52 -29
  110. package/rust/core/parser/handler/identifier/sleep.rs +36 -33
  111. package/rust/core/parser/handler/identifier/spawn.rs +91 -88
  112. package/rust/core/parser/handler/identifier/synth.rs +65 -63
  113. package/rust/core/parser/handler/loop_.rs +170 -89
  114. package/rust/core/parser/handler/mod.rs +8 -8
  115. package/rust/core/parser/handler/tempo.rs +53 -47
  116. package/rust/core/parser/mod.rs +4 -4
  117. package/rust/core/parser/statement.rs +142 -113
  118. package/rust/core/plugin/loader.rs +123 -48
  119. package/rust/core/plugin/mod.rs +2 -1
  120. package/rust/core/plugin/runner.rs +296 -0
  121. package/rust/core/preprocessor/loader.rs +515 -326
  122. package/rust/core/preprocessor/mod.rs +4 -4
  123. package/rust/core/preprocessor/module.rs +60 -58
  124. package/rust/core/preprocessor/processor.rs +99 -101
  125. package/rust/core/preprocessor/resolver/bank.rs +51 -48
  126. package/rust/core/preprocessor/resolver/call.rs +100 -101
  127. package/rust/core/preprocessor/resolver/condition.rs +97 -97
  128. package/rust/core/preprocessor/resolver/driver.rs +310 -280
  129. package/rust/core/preprocessor/resolver/function.rs +69 -68
  130. package/rust/core/preprocessor/resolver/group.rs +96 -91
  131. package/rust/core/preprocessor/resolver/let_.rs +32 -28
  132. package/rust/core/preprocessor/resolver/loop_.rs +320 -121
  133. package/rust/core/preprocessor/resolver/mod.rs +15 -15
  134. package/rust/core/preprocessor/resolver/spawn.rs +76 -73
  135. package/rust/core/preprocessor/resolver/synth.rs +56 -50
  136. package/rust/core/preprocessor/resolver/tempo.rs +50 -49
  137. package/rust/core/preprocessor/resolver/trigger.rs +113 -115
  138. package/rust/core/preprocessor/resolver/value.rs +81 -81
  139. package/rust/core/shared/duration.rs +9 -9
  140. package/rust/core/shared/mod.rs +3 -3
  141. package/rust/core/shared/value.rs +35 -32
  142. package/rust/core/store/function.rs +34 -34
  143. package/rust/core/store/global.rs +55 -38
  144. package/rust/core/store/mod.rs +5 -5
  145. package/rust/core/store/variable.rs +37 -34
  146. package/rust/core/utils/mod.rs +2 -2
  147. package/rust/core/utils/path.rs +37 -31
  148. package/rust/core/utils/validation.rs +35 -36
  149. package/rust/installer/addon.rs +84 -80
  150. package/rust/installer/bank.rs +62 -65
  151. package/rust/installer/mod.rs +5 -5
  152. package/rust/installer/plugin.rs +54 -55
  153. package/rust/installer/utils.rs +56 -56
  154. package/rust/lib.rs +156 -164
  155. package/rust/main.rs +250 -144
  156. package/rust/utils/error.rs +200 -51
  157. package/rust/utils/file.rs +38 -35
  158. package/rust/utils/first_usage.rs +76 -0
  159. package/rust/utils/logger.rs +195 -143
  160. package/rust/utils/mod.rs +9 -7
  161. package/rust/utils/signature.rs +19 -17
  162. package/rust/utils/spinner.rs +22 -19
  163. package/rust/utils/telemetry.rs +292 -0
  164. package/rust/utils/watcher.rs +34 -33
  165. package/templates/minimal/README.md +97 -121
  166. package/templates/welcome/README.md +97 -121
  167. package/typescript/bin/index.ts +19 -5
  168. package/typescript/index.ts +3 -1
  169. package/typescript/scripts/postbuild.ts +10 -6
  170. package/typescript/scripts/postinstall.ts +56 -0
  171. package/typescript/scripts/version/bump.ts +0 -1
  172. package/typescript/scripts/version/index.ts +0 -1
  173. package/out-tsc/bin/devalang.exe +0 -0
@@ -1,156 +1,165 @@
1
- use std::{ fs, path::Path };
2
- use crate::config::driver::{ BankEntry, BankMetadata, Config };
3
-
4
- pub fn load_config(path: Option<&Path>) -> Option<Config> {
5
- let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
6
-
7
- if config_path.exists() {
8
- let content = fs::read_to_string(config_path).ok()?;
9
- toml::from_str(&content).ok()
10
- } else {
11
- None
12
- }
13
- }
14
-
15
- pub fn update_bank_version_in_config(config: &mut Config, dependency: &str, new_version: &str) {
16
- // Si le vecteur banks n'existe pas, on ne fait rien
17
- if config.banks.is_none() {
18
- println!("No banks configured.");
19
- return;
20
- }
21
-
22
- let banks = config.banks.as_mut().unwrap();
23
-
24
- if let Some(bank) = banks.iter_mut().find(|b| b.path.contains(dependency)) {
25
- bank.version = Some(new_version.to_string());
26
-
27
- if let Err(e) = config.write(config) {
28
- eprintln!("❌ Failed to write config: {}", e);
29
- } else {
30
- println!("✅ Bank '{}' updated to version '{}'", dependency, new_version);
31
- }
32
- } else {
33
- println!("Bank '{}' not found in config", dependency);
34
- }
35
- }
36
-
37
- pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
38
- if config.banks.is_none() {
39
- println!("No banks configured.");
40
- return;
41
- }
42
-
43
- let banks = config.banks.as_mut().unwrap();
44
-
45
- if let Some(index) = banks.iter().position(|b| b.path.contains(dependency)) {
46
- banks.remove(index);
47
-
48
- if let Err(e) = config.write(config) {
49
- eprintln!("❌ Failed to write config: {}", e);
50
- } else {
51
- println!("✅ Bank '{}' removed from config", dependency);
52
- }
53
- } else {
54
- println!("Bank '{}' not found in config", dependency);
55
- }
56
- }
57
-
58
- pub fn add_plugin_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
59
- if config.plugins.is_none() {
60
- config.plugins = Some(Vec::new());
61
- }
62
-
63
- let plugins = config.plugins.as_mut().unwrap();
64
-
65
- let exists = plugins.iter().any(|p| p.path == dependency);
66
- if exists {
67
- println!("Plugin '{}' already in config", dependency);
68
- return;
69
- }
70
-
71
- let metadata_path = Path::new(real_path).join("plugin.toml");
72
-
73
- if !metadata_path.exists() {
74
- eprintln!("❌ Plugin metadata file '{}' does not exist", metadata_path.display());
75
- return;
76
- }
77
-
78
- let metadata_content = std::fs
79
- ::read_to_string(&metadata_path)
80
- .expect("Failed to read plugin metadata file");
81
-
82
- let metadata: std::collections::HashMap<String, String> = toml
83
- ::from_str(&metadata_content)
84
- .expect("Failed to parse plugin metadata file");
85
-
86
- let plugin_entry = crate::config::driver::PluginEntry {
87
- path: dependency.to_string(),
88
- version: metadata
89
- .get("version")
90
- .cloned()
91
- .unwrap_or_else(|| "0.0.1".to_string()),
92
- author: metadata
93
- .get("author")
94
- .cloned()
95
- .unwrap_or_else(|| "unknown".to_string()),
96
- access: metadata
97
- .get("access")
98
- .cloned()
99
- .unwrap_or_else(|| "public".to_string()),
100
- };
101
-
102
- plugins.push(plugin_entry);
103
-
104
- if let Err(e) = config.write(config) {
105
- eprintln!("❌ Failed to write config: {}", e);
106
- } else {
107
- println!("✅ Plugin '{}' added to config", dependency);
108
- }
109
- }
110
-
111
- pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
112
- if config.banks.is_none() {
113
- config.banks = Some(Vec::new());
114
- }
115
-
116
- let banks = config.banks.as_mut().unwrap();
117
-
118
- let exists = banks.iter().any(|b| b.path == dependency);
119
- if exists {
120
- println!("Bank '{}' already in config", dependency);
121
- return;
122
- }
123
-
124
- let metadata_path = Path::new(real_path).join("bank.toml");
125
-
126
- if !metadata_path.exists() {
127
- eprintln!("Bank metadata file '{}' does not exist", metadata_path.display());
128
- return;
129
- }
130
-
131
- let metadata_content = fs
132
- ::read_to_string(&metadata_path)
133
- .expect("Failed to read bank metadata file");
134
-
135
- let metadata: BankMetadata = toml
136
- ::from_str(&metadata_content)
137
- .expect("Failed to parse bank metadata file");
138
-
139
- let bank_to_insert = BankEntry {
140
- path: dependency.to_string(),
141
- version: Some(
142
- metadata.bank
143
- .get("version")
144
- .cloned()
145
- .unwrap_or_else(|| "0.0.1".to_string())
146
- ),
147
- };
148
-
149
- banks.push(bank_to_insert);
150
-
151
- if let Err(e) = config.write(config) {
152
- eprintln!("❌ Failed to write config: {}", e);
153
- } else {
154
- println!("✅ Bank '{}' added to config", dependency);
155
- }
156
- }
1
+ use crate::config::driver::{ProjectConfig, ProjectConfigBankEntry, ProjectConfigBankMetadata};
2
+ use std::{fs, path::Path};
3
+
4
+ pub fn load_config(path: Option<&Path>) -> Option<ProjectConfig> {
5
+ let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
6
+
7
+ if config_path.exists() {
8
+ let content = fs::read_to_string(config_path).ok()?;
9
+ toml::from_str(&content).ok()
10
+ } else {
11
+ None
12
+ }
13
+ }
14
+
15
+ pub fn update_bank_version_in_config(
16
+ config: &mut ProjectConfig,
17
+ dependency: &str,
18
+ new_version: &str,
19
+ ) {
20
+ if config.banks.is_none() {
21
+ println!("No banks configured.");
22
+ return;
23
+ }
24
+
25
+ let banks = config.banks.as_mut().unwrap();
26
+
27
+ if let Some(bank) = banks.iter_mut().find(|b| b.path.contains(dependency)) {
28
+ bank.version = Some(new_version.to_string());
29
+
30
+ if let Err(e) = config.write(config) {
31
+ eprintln!("❌ Failed to write config: {}", e);
32
+ } else {
33
+ println!(
34
+ "✅ Bank '{}' updated to version '{}'",
35
+ dependency, new_version
36
+ );
37
+ }
38
+ } else {
39
+ println!("Bank '{}' not found in config", dependency);
40
+ }
41
+ }
42
+
43
+ pub fn remove_bank_from_config(config: &mut ProjectConfig, dependency: &str) {
44
+ if config.banks.is_none() {
45
+ println!("No banks configured.");
46
+ return;
47
+ }
48
+
49
+ let banks = config.banks.as_mut().unwrap();
50
+
51
+ if let Some(index) = banks.iter().position(|b| b.path.contains(dependency)) {
52
+ banks.remove(index);
53
+
54
+ if let Err(e) = config.write(config) {
55
+ eprintln!("❌ Failed to write config: {}", e);
56
+ } else {
57
+ println!("✅ Bank '{}' removed from config", dependency);
58
+ }
59
+ } else {
60
+ println!("Bank '{}' not found in config", dependency);
61
+ }
62
+ }
63
+
64
+ pub fn add_plugin_to_config(config: &mut ProjectConfig, real_path: &Path, dependency: &str) {
65
+ if config.plugins.is_none() {
66
+ config.plugins = Some(Vec::new());
67
+ }
68
+
69
+ let plugins = config.plugins.as_mut().unwrap();
70
+
71
+ let exists = plugins.iter().any(|p| p.path == dependency);
72
+ if exists {
73
+ println!("Plugin '{}' already in config", dependency);
74
+ return;
75
+ }
76
+
77
+ let metadata_path = Path::new(real_path).join("plugin.toml");
78
+
79
+ if !metadata_path.exists() {
80
+ eprintln!(
81
+ "❌ Plugin metadata file '{}' does not exist",
82
+ metadata_path.display()
83
+ );
84
+ return;
85
+ }
86
+
87
+ let metadata_content =
88
+ std::fs::read_to_string(&metadata_path).expect("Failed to read plugin metadata file");
89
+
90
+ let metadata: std::collections::HashMap<String, String> =
91
+ toml::from_str(&metadata_content).expect("Failed to parse plugin metadata file");
92
+
93
+ let plugin_entry = crate::config::driver::PluginEntry {
94
+ path: dependency.to_string(),
95
+ version: metadata
96
+ .get("version")
97
+ .cloned()
98
+ .unwrap_or_else(|| "0.0.1".to_string()),
99
+ author: metadata
100
+ .get("author")
101
+ .cloned()
102
+ .unwrap_or_else(|| "unknown".to_string()),
103
+ access: metadata
104
+ .get("access")
105
+ .cloned()
106
+ .unwrap_or_else(|| "public".to_string()),
107
+ };
108
+
109
+ plugins.push(plugin_entry);
110
+
111
+ if let Err(e) = config.write(config) {
112
+ eprintln!("❌ Failed to write config: {}", e);
113
+ } else {
114
+ println!("✅ Plugin '{}' added to config", dependency);
115
+ }
116
+ }
117
+
118
+ pub fn add_bank_to_config(config: &mut ProjectConfig, real_path: &Path, dependency: &str) {
119
+ if config.banks.is_none() {
120
+ config.banks = Some(Vec::new());
121
+ }
122
+
123
+ let banks = config.banks.as_mut().unwrap();
124
+
125
+ let exists = banks.iter().any(|b| b.path == dependency);
126
+ if exists {
127
+ println!("Bank '{}' already in config", dependency);
128
+ return;
129
+ }
130
+
131
+ let metadata_path = Path::new(real_path).join("bank.toml");
132
+
133
+ if !metadata_path.exists() {
134
+ eprintln!(
135
+ "❌ Bank metadata file '{}' does not exist",
136
+ metadata_path.display()
137
+ );
138
+ return;
139
+ }
140
+
141
+ let metadata_content =
142
+ fs::read_to_string(&metadata_path).expect("Failed to read bank metadata file");
143
+
144
+ let metadata: ProjectConfigBankMetadata =
145
+ toml::from_str(&metadata_content).expect("Failed to parse bank metadata file");
146
+
147
+ let bank_to_insert = ProjectConfigBankEntry {
148
+ path: dependency.to_string(),
149
+ version: Some(
150
+ metadata
151
+ .bank
152
+ .get("version")
153
+ .cloned()
154
+ .unwrap_or_else(|| "0.0.1".to_string()),
155
+ ),
156
+ };
157
+
158
+ banks.push(bank_to_insert);
159
+
160
+ if let Err(e) = config.write(config) {
161
+ eprintln!("❌ Failed to write config: {}", e);
162
+ } else {
163
+ println!("✅ Bank '{}' added to config", dependency);
164
+ }
165
+ }
@@ -1,2 +1,4 @@
1
- pub mod loader;
2
- pub mod driver;
1
+ pub mod driver;
2
+ pub mod loader;
3
+ pub mod settings;
4
+ pub mod stats;
@@ -0,0 +1,91 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use std::{any::Any, io::Write};
3
+
4
+ #[derive(Serialize, Deserialize, Default, Debug)]
5
+ pub struct UserSettings {
6
+ pub session: String,
7
+ pub telemetry: TelemetrySettings,
8
+ }
9
+
10
+ #[derive(Serialize, Deserialize, Default, Debug)]
11
+ pub struct TelemetrySettings {
12
+ pub uuid: String,
13
+ pub stats: bool,
14
+ pub level: String,
15
+ pub enabled: bool,
16
+ }
17
+
18
+ pub fn get_home_dir() -> std::path::PathBuf {
19
+ dirs::home_dir().unwrap_or_default()
20
+ }
21
+
22
+ pub fn get_devalang_homedir() -> std::path::PathBuf {
23
+ get_home_dir().join(".devalang")
24
+ }
25
+
26
+ pub fn get_default_user_config() -> UserSettings {
27
+ UserSettings {
28
+ session: "".into(),
29
+ telemetry: TelemetrySettings {
30
+ uuid: uuid::Uuid::new_v4().to_string(),
31
+ enabled: true,
32
+ level: "basic".into(),
33
+ stats: true,
34
+ },
35
+ }
36
+ }
37
+
38
+ pub fn get_user_config() -> Option<UserSettings> {
39
+ let config_path = get_devalang_homedir().join("config.json");
40
+ if config_path.try_exists().is_ok() {
41
+ let file = std::fs::File::open(config_path).ok()?;
42
+ let settings = serde_json::from_reader(file).ok()?;
43
+ Some(settings)
44
+ } else {
45
+ None
46
+ }
47
+ }
48
+
49
+ pub fn write_user_config_file() {
50
+ let settings = get_user_config().unwrap_or(get_default_user_config());
51
+
52
+ let config_path = get_devalang_homedir().join("config.json");
53
+ let mut file = std::fs::File::create(config_path).unwrap();
54
+ let config_json = serde_json::to_string(&settings).unwrap();
55
+
56
+ file.write_all(config_json.as_bytes()).unwrap();
57
+ }
58
+
59
+ pub fn set_user_config_bool(key: &str, value: bool) {
60
+ let mut settings = get_user_config().unwrap_or_default();
61
+
62
+ match key {
63
+ "telemetry" => {
64
+ settings.telemetry.enabled = value;
65
+ }
66
+ _ => {}
67
+ }
68
+
69
+ let config_path = get_devalang_homedir().join("config.json");
70
+ let config_json = serde_json::to_string(&settings).unwrap();
71
+ let mut file = std::fs::File::create(config_path).unwrap();
72
+
73
+ file.write_all(config_json.as_bytes()).unwrap();
74
+ }
75
+
76
+ pub fn set_user_config_string(key: &str, value: String) {
77
+ let mut settings = get_user_config().unwrap_or_default();
78
+
79
+ match key {
80
+ "session" => {
81
+ settings.session = value;
82
+ }
83
+ _ => {}
84
+ }
85
+
86
+ let config_path = get_devalang_homedir().join("config.json");
87
+ let config_json = serde_json::to_string(&settings).unwrap();
88
+ let mut file = std::fs::File::create(config_path).unwrap();
89
+
90
+ file.write_all(config_json.as_bytes()).unwrap();
91
+ }
@@ -0,0 +1,257 @@
1
+ use super::settings::get_devalang_homedir;
2
+ use crate::config::driver::ProjectConfig;
3
+ use crate::core::{
4
+ parser::statement::{Statement, StatementKind},
5
+ store::global::GlobalStore,
6
+ };
7
+ use serde::{Deserialize, Serialize};
8
+ use std::collections::HashMap;
9
+ use std::{
10
+ fs,
11
+ io::Write,
12
+ path::Path,
13
+ path::PathBuf,
14
+ sync::{Mutex, OnceLock},
15
+ };
16
+
17
+ #[derive(Debug, Deserialize, Clone, Serialize)]
18
+ pub struct StatsCounts {
19
+ pub nb_files: usize,
20
+ pub nb_modules: usize,
21
+ pub nb_lines: usize,
22
+ pub nb_banks: usize,
23
+ pub nb_plugins: usize,
24
+ }
25
+
26
+ #[derive(Debug, Deserialize, Clone, Serialize)]
27
+ pub struct StatsFeatures {
28
+ pub uses_imports: bool,
29
+ pub uses_functions: bool,
30
+ pub uses_groups: bool,
31
+ pub uses_automations: bool,
32
+ pub uses_loops: bool,
33
+ }
34
+
35
+ #[derive(Debug, Deserialize, Clone, Serialize)]
36
+ pub struct StatsAudio {
37
+ pub avg_bpm: Option<u32>,
38
+ pub has_synths: bool,
39
+ pub has_samples: bool,
40
+ }
41
+
42
+ #[derive(Debug, Deserialize, Clone, Serialize)]
43
+ pub struct ProjectStats {
44
+ pub counts: StatsCounts,
45
+ pub features: StatsFeatures,
46
+ pub audio: StatsAudio,
47
+ }
48
+
49
+ impl ProjectStats {
50
+ pub fn new() -> Self {
51
+ ProjectStats {
52
+ counts: StatsCounts {
53
+ nb_files: 0,
54
+ nb_modules: 0,
55
+ nb_lines: 0,
56
+ nb_banks: 0,
57
+ nb_plugins: 0,
58
+ },
59
+ features: StatsFeatures {
60
+ uses_imports: false,
61
+ uses_functions: false,
62
+ uses_groups: false,
63
+ uses_automations: false,
64
+ uses_loops: false,
65
+ },
66
+ audio: StatsAudio {
67
+ avg_bpm: None,
68
+ has_synths: false,
69
+ has_samples: false,
70
+ },
71
+ }
72
+ }
73
+
74
+ // Returns the in-memory stats if available, otherwise loads from file,
75
+ // otherwise returns a default struct.
76
+ pub fn get() -> Result<Self, String> {
77
+ if let Some(stats) = get_memory_stats() {
78
+ return Ok(stats);
79
+ }
80
+ if let Some(stats) = load_from_file() {
81
+ return Ok(stats);
82
+ }
83
+ Ok(Self::new())
84
+ }
85
+
86
+ // Saves stats to stats.json under the devalang home directory.
87
+ pub fn persist(&self) -> Result<(), String> {
88
+ save_to_file(self)
89
+ }
90
+ }
91
+
92
+ // ----------------------------
93
+ // Storage helpers (memory/file)
94
+ // ----------------------------
95
+
96
+ static STATS_MEMORY: OnceLock<Mutex<ProjectStats>> = OnceLock::new();
97
+
98
+ fn stats_file_path() -> PathBuf {
99
+ get_devalang_homedir().join("stats.json")
100
+ }
101
+
102
+ pub fn set_memory_stats(stats: ProjectStats) {
103
+ let m = STATS_MEMORY.get_or_init(|| Mutex::new(ProjectStats::new()));
104
+ if let Ok(mut guard) = m.lock() {
105
+ *guard = stats;
106
+ }
107
+ }
108
+
109
+ pub fn get_memory_stats() -> Option<ProjectStats> {
110
+ let m = STATS_MEMORY.get_or_init(|| Mutex::new(ProjectStats::new()));
111
+ if let Ok(guard) = m.lock() {
112
+ // If it's the default value (all zero/false/None), still return it; caller can decide.
113
+ return Some(guard.clone());
114
+ }
115
+ None
116
+ }
117
+
118
+ pub fn load_from_file() -> Option<ProjectStats> {
119
+ let path = stats_file_path();
120
+ if !path.exists() {
121
+ return None;
122
+ }
123
+ match fs::read_to_string(&path) {
124
+ Ok(content) => match serde_json::from_str::<ProjectStats>(&content) {
125
+ Ok(stats) => Some(stats),
126
+ Err(_) => None,
127
+ },
128
+ Err(_) => None,
129
+ }
130
+ }
131
+
132
+ pub fn save_to_file(stats: &ProjectStats) -> Result<(), String> {
133
+ let path = stats_file_path();
134
+ let dir = path
135
+ .parent()
136
+ .unwrap_or(&get_devalang_homedir())
137
+ .to_path_buf();
138
+ if !dir.exists() {
139
+ fs::create_dir_all(&dir).map_err(|e| format!("failed to create stats dir: {}", e))?;
140
+ }
141
+ let json = serde_json::to_string_pretty(stats)
142
+ .map_err(|e| format!("failed to serialize stats: {}", e))?;
143
+ let mut file =
144
+ fs::File::create(&path).map_err(|e| format!("failed to create stats file: {}", e))?;
145
+ file.write_all(json.as_bytes())
146
+ .map_err(|e| format!("failed to write stats file: {}", e))?;
147
+ Ok(())
148
+ }
149
+
150
+ // Compute stats from modules and store
151
+ pub fn compute_from(
152
+ statements_by_module: &HashMap<String, Vec<Statement>>,
153
+ global_store: &GlobalStore,
154
+ config: &Option<ProjectConfig>,
155
+ _output_dir: Option<&str>,
156
+ ) -> ProjectStats {
157
+ let mut stats = ProjectStats::new();
158
+
159
+ // Counts
160
+ stats.counts.nb_modules = statements_by_module.len();
161
+ stats.counts.nb_files = stats.counts.nb_modules; // number of source files loaded
162
+
163
+ let mut total_lines = 0usize;
164
+ for (path, _stmts) in statements_by_module.iter() {
165
+ let p = Path::new(path);
166
+ if let Ok(content) = fs::read_to_string(p) {
167
+ total_lines += content.lines().count();
168
+ }
169
+ }
170
+ stats.counts.nb_lines = total_lines;
171
+
172
+ // Banks/Plugins from config
173
+ if let Some(cfg) = config {
174
+ stats.counts.nb_banks = cfg.banks.as_ref().map(|v| v.len()).unwrap_or(0);
175
+ stats.counts.nb_plugins = cfg.plugins.as_ref().map(|v| v.len()).unwrap_or(0);
176
+ }
177
+
178
+ // Features and audio
179
+ let mut bpm_sum: f32 = 0.0;
180
+ let mut bpm_count: usize = 0;
181
+
182
+ fn visit(
183
+ stmts: &[Statement],
184
+ acc: &mut ProjectStats,
185
+ bpm_sum: &mut f32,
186
+ bpm_count: &mut usize,
187
+ ) {
188
+ for s in stmts {
189
+ match &s.kind {
190
+ StatementKind::Import { .. } => acc.features.uses_imports = true,
191
+ StatementKind::Function { body, .. } => {
192
+ acc.features.uses_functions = true;
193
+ visit(body, acc, bpm_sum, bpm_count);
194
+ }
195
+ StatementKind::Group => {
196
+ acc.features.uses_groups = true;
197
+ if let Some(body) = extract_body_block(&s.value) {
198
+ visit(body, acc, bpm_sum, bpm_count);
199
+ }
200
+ }
201
+ StatementKind::Automate { .. } => acc.features.uses_automations = true,
202
+ StatementKind::Loop => {
203
+ acc.features.uses_loops = true;
204
+ if let Some(body) = extract_body_block(&s.value) {
205
+ visit(body, acc, bpm_sum, bpm_count);
206
+ }
207
+ }
208
+ StatementKind::Synth => acc.audio.has_synths = true,
209
+ StatementKind::Tempo => {
210
+ if let crate::core::shared::value::Value::Number(bpm) = &s.value {
211
+ *bpm_sum += *bpm as f32;
212
+ *bpm_count += 1;
213
+ }
214
+ }
215
+ StatementKind::Trigger { entity, .. } => {
216
+ let e = entity.to_lowercase();
217
+ if e.ends_with(".wav") || e.ends_with(".mp3") || e.contains("devalang://bank/")
218
+ {
219
+ acc.audio.has_samples = true;
220
+ }
221
+ }
222
+ _ => {}
223
+ }
224
+ }
225
+ }
226
+
227
+ fn extract_body_block(v: &crate::core::shared::value::Value) -> Option<&[Statement]> {
228
+ use crate::core::shared::value::Value;
229
+ if let Value::Map(map) = v {
230
+ if let Some(Value::Block(stmts)) = map.get("body") {
231
+ return Some(stmts.as_slice());
232
+ }
233
+ }
234
+ None
235
+ }
236
+
237
+ for (_path, stmts) in statements_by_module.iter() {
238
+ visit(stmts, &mut stats, &mut bpm_sum, &mut bpm_count);
239
+ }
240
+
241
+ if !stats.audio.has_samples {
242
+ for (_k, v) in global_store.variables.variables.iter() {
243
+ if let crate::core::shared::value::Value::String(s) = v {
244
+ if s.starts_with("devalang://bank/") {
245
+ stats.audio.has_samples = true;
246
+ break;
247
+ }
248
+ }
249
+ }
250
+ }
251
+
252
+ if bpm_count > 0 {
253
+ stats.audio.avg_bpm = Some((bpm_sum / bpm_count as f32).round() as u32);
254
+ }
255
+
256
+ stats
257
+ }