@devaloop/devalang 0.0.1-alpha.13 → 0.0.1-alpha.14
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.
- package/.devalang +8 -9
- package/Cargo.toml +58 -54
- package/README.md +36 -21
- package/docs/CHANGELOG.md +40 -2
- package/docs/CONTRIBUTING.md +1 -0
- package/docs/ROADMAP.md +2 -2
- package/docs/TODO.md +5 -4
- package/examples/bank.deva +2 -4
- package/examples/function.deva +15 -0
- package/examples/index.deva +25 -11
- package/out-tsc/bin/devalang.exe +0 -0
- package/package.json +6 -6
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +2 -1
- package/rust/cli/build.rs +69 -30
- package/rust/cli/check.rs +45 -5
- package/rust/cli/driver.rs +40 -28
- package/rust/cli/install.rs +22 -7
- package/rust/cli/login.rs +134 -0
- package/rust/cli/mod.rs +2 -1
- package/rust/cli/play.rs +44 -19
- package/rust/common/api.rs +8 -0
- package/rust/common/cdn.rs +2 -5
- package/rust/common/mod.rs +3 -1
- package/rust/common/sso.rs +8 -0
- package/rust/config/driver.rs +19 -1
- package/rust/config/loader.rs +56 -10
- package/rust/core/audio/engine.rs +152 -42
- package/rust/core/audio/interpreter/arrow_call.rs +34 -15
- package/rust/core/audio/interpreter/call.rs +2 -2
- package/rust/core/audio/interpreter/driver.rs +19 -14
- package/rust/core/audio/interpreter/spawn.rs +2 -2
- package/rust/core/builder/mod.rs +11 -6
- package/rust/core/lexer/handler/indent.rs +16 -2
- package/rust/core/lexer/token.rs +1 -0
- package/rust/core/mod.rs +2 -1
- package/rust/core/parser/driver.rs +46 -5
- package/rust/core/parser/handler/arrow_call.rs +78 -18
- package/rust/core/parser/handler/bank.rs +35 -7
- package/rust/core/parser/handler/dot.rs +43 -22
- package/rust/core/plugin/loader.rs +48 -0
- package/rust/core/plugin/mod.rs +1 -0
- package/rust/core/preprocessor/loader.rs +6 -4
- package/rust/installer/addon.rs +80 -0
- package/rust/installer/bank.rs +24 -14
- package/rust/installer/mod.rs +4 -1
- package/rust/installer/plugin.rs +55 -0
- package/rust/main.rs +32 -9
- package/rust/utils/logger.rs +16 -0
- package/rust/utils/spinner.rs +2 -4
|
@@ -7,7 +7,7 @@ use crate::core::{
|
|
|
7
7
|
bank::parse_bank_token,
|
|
8
8
|
condition::parse_condition_token,
|
|
9
9
|
dot::parse_dot_token,
|
|
10
|
-
identifier::{function::parse_function_token, parse_identifier_token},
|
|
10
|
+
identifier::{ function::parse_function_token, parse_identifier_token },
|
|
11
11
|
loop_::parse_loop_token,
|
|
12
12
|
tempo::parse_tempo_token,
|
|
13
13
|
},
|
|
@@ -107,7 +107,11 @@ impl Parser {
|
|
|
107
107
|
tokens: Vec<Token>,
|
|
108
108
|
global_store: &mut GlobalStore
|
|
109
109
|
) -> Vec<Statement> {
|
|
110
|
-
|
|
110
|
+
// Filtrer les tokens Whitespace et Newline avant parsing
|
|
111
|
+
self.tokens = tokens
|
|
112
|
+
.into_iter()
|
|
113
|
+
.filter(|t| t.kind != TokenKind::Whitespace && t.kind != TokenKind::Newline)
|
|
114
|
+
.collect();
|
|
111
115
|
self.token_index = 0;
|
|
112
116
|
|
|
113
117
|
let mut statements = Vec::new();
|
|
@@ -120,6 +124,11 @@ impl Parser {
|
|
|
120
124
|
}
|
|
121
125
|
};
|
|
122
126
|
|
|
127
|
+
if token.kind == TokenKind::Newline {
|
|
128
|
+
self.advance();
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
123
132
|
let statement = match &token.kind {
|
|
124
133
|
TokenKind::At => parse_at_token(self, global_store),
|
|
125
134
|
TokenKind::Identifier => {
|
|
@@ -149,7 +158,6 @@ impl Parser {
|
|
|
149
158
|
| TokenKind::LBrace
|
|
150
159
|
| TokenKind::RBrace
|
|
151
160
|
| TokenKind::Comma
|
|
152
|
-
| TokenKind::Newline
|
|
153
161
|
| TokenKind::Dedent
|
|
154
162
|
| TokenKind::Indent => {
|
|
155
163
|
self.advance();
|
|
@@ -189,17 +197,50 @@ impl Parser {
|
|
|
189
197
|
let mut map = std::collections::HashMap::new();
|
|
190
198
|
|
|
191
199
|
while !self.check_token(TokenKind::RBrace) && !self.is_eof() {
|
|
200
|
+
// Skip newlines, whitespace, indent, dedent before the key
|
|
201
|
+
while
|
|
202
|
+
self.check_token(TokenKind::Newline) ||
|
|
203
|
+
self.check_token(TokenKind::Whitespace) ||
|
|
204
|
+
self.check_token(TokenKind::Indent) ||
|
|
205
|
+
self.check_token(TokenKind::Dedent)
|
|
206
|
+
{
|
|
207
|
+
self.advance();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check if we are at the closing brace of the map
|
|
211
|
+
if self.check_token(TokenKind::RBrace) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
|
|
192
215
|
let key = if let Some(token) = self.advance() {
|
|
193
|
-
token.
|
|
216
|
+
match token.kind {
|
|
217
|
+
| TokenKind::Whitespace
|
|
218
|
+
| TokenKind::Indent
|
|
219
|
+
| TokenKind::Dedent
|
|
220
|
+
| TokenKind::Newline => {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
_ => token.lexeme.clone(),
|
|
224
|
+
}
|
|
194
225
|
} else {
|
|
195
226
|
break;
|
|
196
227
|
};
|
|
197
228
|
|
|
229
|
+
// Skip newlines and whitespace before colon
|
|
230
|
+
while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
|
|
231
|
+
self.advance();
|
|
232
|
+
}
|
|
233
|
+
|
|
198
234
|
if !self.match_token(TokenKind::Colon) {
|
|
199
235
|
println!("Expected ':' after map key '{}'", key);
|
|
200
236
|
break;
|
|
201
237
|
}
|
|
202
238
|
|
|
239
|
+
// Skip newlines and whitespace before value
|
|
240
|
+
while self.check_token(TokenKind::Newline) || self.check_token(TokenKind::Whitespace) {
|
|
241
|
+
self.advance();
|
|
242
|
+
}
|
|
243
|
+
|
|
203
244
|
let value = if let Some(token) = self.peek_clone() {
|
|
204
245
|
match token.kind {
|
|
205
246
|
TokenKind::String => {
|
|
@@ -304,7 +345,7 @@ impl Parser {
|
|
|
304
345
|
}
|
|
305
346
|
collected.push(self.advance().unwrap().clone());
|
|
306
347
|
}
|
|
307
|
-
|
|
348
|
+
|
|
308
349
|
collected
|
|
309
350
|
}
|
|
310
351
|
|
|
@@ -54,11 +54,43 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
|
|
|
54
54
|
parser.advance(); // method
|
|
55
55
|
|
|
56
56
|
let mut args = Vec::new();
|
|
57
|
+
let mut paren_depth = 0;
|
|
58
|
+
let mut map_depth = 0;
|
|
57
59
|
|
|
58
60
|
while let Some(token) = parser.peek_clone() {
|
|
59
61
|
if token.kind == TokenKind::Newline || token.kind == TokenKind::EOF {
|
|
60
62
|
break;
|
|
61
63
|
}
|
|
64
|
+
if token.kind == TokenKind::LParen {
|
|
65
|
+
paren_depth += 1;
|
|
66
|
+
}
|
|
67
|
+
if token.kind == TokenKind::RParen {
|
|
68
|
+
if paren_depth > 0 {
|
|
69
|
+
paren_depth -= 1;
|
|
70
|
+
parser.advance();
|
|
71
|
+
if paren_depth == 0 {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
} else {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if token.kind == TokenKind::LBrace {
|
|
80
|
+
map_depth += 1;
|
|
81
|
+
}
|
|
82
|
+
if token.kind == TokenKind::RBrace {
|
|
83
|
+
if map_depth > 0 {
|
|
84
|
+
map_depth -= 1;
|
|
85
|
+
parser.advance();
|
|
86
|
+
if map_depth == 0 {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
} else {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
62
94
|
|
|
63
95
|
parser.advance();
|
|
64
96
|
|
|
@@ -79,49 +111,68 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
|
|
|
79
111
|
}
|
|
80
112
|
parser.advance(); // consume key token
|
|
81
113
|
let key = inner_token.lexeme.clone();
|
|
82
|
-
|
|
83
114
|
if let Some(colon_token) = parser.peek_clone() {
|
|
84
115
|
if colon_token.kind == TokenKind::Colon {
|
|
85
116
|
parser.advance(); // consume colon
|
|
86
117
|
if let Some(value_token) = parser.peek_clone() {
|
|
87
118
|
parser.advance(); // consume value token
|
|
88
119
|
let value = match value_token.kind {
|
|
89
|
-
TokenKind::Identifier =>
|
|
90
|
-
|
|
120
|
+
TokenKind::Identifier => {
|
|
121
|
+
// Interpret bare true/false as booleans
|
|
122
|
+
if value_token.lexeme == "true" {
|
|
123
|
+
Value::Boolean(true)
|
|
124
|
+
} else if value_token.lexeme == "false" {
|
|
125
|
+
Value::Boolean(false)
|
|
126
|
+
} else {
|
|
127
|
+
Value::Identifier(value_token.lexeme.clone())
|
|
128
|
+
}
|
|
129
|
+
},
|
|
91
130
|
TokenKind::String => Value::String(value_token.lexeme.clone()),
|
|
92
131
|
TokenKind::Number => {
|
|
132
|
+
// Support decimals (e.g., 0.8) and beats (e.g., 1/4)
|
|
93
133
|
if let Some(TokenKind::Slash) = parser.peek_kind() {
|
|
94
|
-
|
|
134
|
+
// Beat fraction
|
|
135
|
+
parser.advance(); // consume '/'
|
|
95
136
|
if let Some(denominator_token) = parser.peek_clone() {
|
|
96
137
|
if denominator_token.kind == TokenKind::Number {
|
|
97
138
|
parser.advance(); // consume denominator
|
|
98
|
-
let denominator =
|
|
99
|
-
|
|
100
|
-
Value::Beat(
|
|
101
|
-
format!(
|
|
102
|
-
"{}/{}",
|
|
103
|
-
value_token.lexeme,
|
|
104
|
-
denominator
|
|
105
|
-
)
|
|
106
|
-
)
|
|
139
|
+
let denominator = denominator_token.lexeme.clone();
|
|
140
|
+
Value::Beat(format!("{}/{}", value_token.lexeme, denominator))
|
|
107
141
|
} else {
|
|
108
142
|
Value::Unknown
|
|
109
143
|
}
|
|
110
144
|
} else {
|
|
111
145
|
Value::Unknown
|
|
112
146
|
}
|
|
147
|
+
} else if let Some(next) = parser.peek_clone() {
|
|
148
|
+
// Decimal number handling: NUMBER '.' NUMBER -> f32
|
|
149
|
+
if next.kind == TokenKind::Dot {
|
|
150
|
+
// consume '.'
|
|
151
|
+
parser.advance();
|
|
152
|
+
if let Some(after_dot) = parser.peek_clone() {
|
|
153
|
+
if after_dot.kind == TokenKind::Number {
|
|
154
|
+
parser.advance(); // consume fractional digits
|
|
155
|
+
let combined = format!("{}.{}", value_token.lexeme, after_dot.lexeme);
|
|
156
|
+
Value::Number(combined.parse::<f32>().unwrap_or(0.0))
|
|
157
|
+
} else {
|
|
158
|
+
// Lone dot without number, fallback to integer part
|
|
159
|
+
Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
// Regular integer number
|
|
166
|
+
Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
|
|
167
|
+
}
|
|
113
168
|
} else {
|
|
114
|
-
|
|
115
|
-
Value::Number(
|
|
116
|
-
value_token.lexeme.parse::<f32>().unwrap_or(0.0)
|
|
117
|
-
)
|
|
169
|
+
Value::Number(value_token.lexeme.parse::<f32>().unwrap_or(0.0))
|
|
118
170
|
}
|
|
119
171
|
}
|
|
120
172
|
TokenKind::Boolean =>
|
|
121
173
|
Value::Boolean(
|
|
122
174
|
value_token.lexeme.parse::<bool>().unwrap_or(false)
|
|
123
175
|
),
|
|
124
|
-
|
|
125
176
|
_ => Value::Unknown,
|
|
126
177
|
};
|
|
127
178
|
map.insert(key, value);
|
|
@@ -135,6 +186,15 @@ pub fn parse_arrow_call(parser: &mut Parser, global_store: &mut GlobalStore) ->
|
|
|
135
186
|
};
|
|
136
187
|
|
|
137
188
|
args.push(value);
|
|
189
|
+
|
|
190
|
+
// Stop if we reach the end of the statement
|
|
191
|
+
if
|
|
192
|
+
paren_depth == 0 &&
|
|
193
|
+
map_depth == 0 &&
|
|
194
|
+
(token.kind == TokenKind::RParen || token.kind == TokenKind::RBrace)
|
|
195
|
+
{
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
138
198
|
}
|
|
139
199
|
|
|
140
200
|
Statement {
|
|
@@ -14,13 +14,41 @@ pub fn parse_bank_token(parser: &mut Parser, _global_store: &mut GlobalStore) ->
|
|
|
14
14
|
|
|
15
15
|
let bank_value = if let Some(token) = parser.peek_clone() {
|
|
16
16
|
match token.kind {
|
|
17
|
-
TokenKind::Identifier => {
|
|
18
|
-
parser.advance();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
TokenKind::Identifier | TokenKind::Number => {
|
|
18
|
+
parser.advance(); // consume identifier or number
|
|
19
|
+
|
|
20
|
+
let mut value = token.lexeme.clone();
|
|
21
|
+
|
|
22
|
+
// Support namespaced banks: <author>.<bank_name>
|
|
23
|
+
if let Some(next) = parser.peek_clone() {
|
|
24
|
+
if next.kind == TokenKind::Dot {
|
|
25
|
+
parser.advance(); // consume '.'
|
|
26
|
+
if let Some(last) = parser.peek_clone() {
|
|
27
|
+
match last.kind {
|
|
28
|
+
TokenKind::Identifier | TokenKind::Number => {
|
|
29
|
+
parser.advance();
|
|
30
|
+
value = format!("{}.{}", value, last.lexeme);
|
|
31
|
+
Value::String(value)
|
|
32
|
+
}
|
|
33
|
+
_ => Value::Unknown,
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
Value::Unknown
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
match token.kind {
|
|
40
|
+
TokenKind::Identifier => Value::Identifier(value),
|
|
41
|
+
TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
|
|
42
|
+
_ => Value::Unknown,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
match token.kind {
|
|
47
|
+
TokenKind::Identifier => Value::Identifier(value),
|
|
48
|
+
TokenKind::Number => Value::Number(value.parse::<f32>().unwrap_or(0.0)),
|
|
49
|
+
_ => Value::Unknown,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
24
52
|
}
|
|
25
53
|
_ => Value::Unknown,
|
|
26
54
|
}
|
|
@@ -16,13 +16,23 @@ pub fn parse_dot_token(
|
|
|
16
16
|
|
|
17
17
|
// Parse a single entity (namespace-friendly, stops at newline)
|
|
18
18
|
let mut parts = Vec::new();
|
|
19
|
+
let current_line = dot_token.line;
|
|
19
20
|
|
|
20
21
|
while let Some(token) = parser.peek_clone() {
|
|
22
|
+
// Ne jamais traverser une nouvelle ligne
|
|
23
|
+
if token.line != current_line {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
21
26
|
match token.kind {
|
|
22
27
|
TokenKind::Identifier | TokenKind::Number => {
|
|
23
28
|
parts.push(token.lexeme.clone());
|
|
24
29
|
parser.advance();
|
|
25
|
-
|
|
30
|
+
// Le séparateur doit être un '.' sur la même ligne, sinon on s'arrête
|
|
31
|
+
if let Some(next) = parser.peek_clone() {
|
|
32
|
+
if next.line != current_line || next.kind != TokenKind::Dot {
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
26
36
|
break;
|
|
27
37
|
}
|
|
28
38
|
}
|
|
@@ -51,42 +61,53 @@ pub fn parse_dot_token(
|
|
|
51
61
|
let mut value = Value::Null;
|
|
52
62
|
|
|
53
63
|
if let Some(token) = parser.peek_clone() {
|
|
54
|
-
|
|
64
|
+
// La durée et la map d'effets ne sont valides que sur la même ligne
|
|
65
|
+
if token.line == current_line {
|
|
66
|
+
match token.kind {
|
|
55
67
|
TokenKind::Number => {
|
|
56
68
|
let numerator = token.lexeme.clone();
|
|
57
69
|
parser.advance();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
if let Some(peek) = parser.peek_clone() {
|
|
71
|
+
if peek.line == current_line {
|
|
72
|
+
if let Some(TokenKind::Slash) = parser.peek_kind() {
|
|
73
|
+
parser.advance();
|
|
74
|
+
if let Some(denominator_token) = parser.peek_clone() {
|
|
75
|
+
if denominator_token.line == current_line && denominator_token.kind == TokenKind::Number {
|
|
76
|
+
let denominator = denominator_token.lexeme.clone();
|
|
77
|
+
parser.advance();
|
|
78
|
+
duration = Duration::Beat(format!("{}/{}", numerator, denominator));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
duration = parse_duration(numerator);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
duration = parse_duration(numerator);
|
|
65
86
|
}
|
|
87
|
+
} else {
|
|
88
|
+
duration = parse_duration(numerator);
|
|
66
89
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if next.kind == TokenKind::LBrace {
|
|
72
|
-
value = parser.parse_map_value().unwrap_or(Value::Null);
|
|
90
|
+
if let Some(next) = parser.peek_clone() {
|
|
91
|
+
if next.line == current_line && next.kind == TokenKind::LBrace {
|
|
92
|
+
value = parser.parse_map_value().unwrap_or(Value::Null);
|
|
93
|
+
}
|
|
73
94
|
}
|
|
74
|
-
}
|
|
75
95
|
}
|
|
76
96
|
TokenKind::Identifier => {
|
|
77
97
|
let id = token.lexeme.clone();
|
|
78
98
|
parser.advance();
|
|
79
99
|
duration = parse_duration(id);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
100
|
+
if let Some(next) = parser.peek_clone() {
|
|
101
|
+
if next.line == current_line && next.kind == TokenKind::LBrace {
|
|
102
|
+
value = parser.parse_map_value().unwrap_or(Value::Null);
|
|
103
|
+
}
|
|
83
104
|
}
|
|
84
|
-
}
|
|
85
105
|
}
|
|
86
106
|
TokenKind::LBrace => {
|
|
87
|
-
|
|
107
|
+
value = parser.parse_map_value().unwrap_or(Value::Null);
|
|
108
|
+
}
|
|
109
|
+
_ => {}
|
|
88
110
|
}
|
|
89
|
-
_ => {}
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
113
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
use serde::Deserialize;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
5
|
+
pub struct PluginInfo {
|
|
6
|
+
pub name: String,
|
|
7
|
+
pub version: Option<String>,
|
|
8
|
+
pub description: Option<String>,
|
|
9
|
+
pub author: Option<String>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Deserialize, Clone)]
|
|
13
|
+
pub struct PluginFile {
|
|
14
|
+
pub plugin: PluginInfo,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub fn load_plugin(name: &str) -> Result<(PluginInfo, Vec<u8>), String> {
|
|
18
|
+
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
19
|
+
let plugin_dir = root.join(".deva").join("plugin").join(name);
|
|
20
|
+
let toml_path = plugin_dir.join("plugin.toml");
|
|
21
|
+
let wasm_path = plugin_dir.join(format!("{}_bg.wasm", name));
|
|
22
|
+
|
|
23
|
+
if !toml_path.exists() {
|
|
24
|
+
return Err(format!("❌ Plugin file not found: {}", toml_path.display()));
|
|
25
|
+
}
|
|
26
|
+
if !wasm_path.exists() {
|
|
27
|
+
return Err(format!("❌ Plugin wasm not found: {}", wasm_path.display()));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let toml_content = std::fs::read_to_string(&toml_path)
|
|
31
|
+
.map_err(|e| format!("Failed to read '{}': {}", toml_path.display(), e))?;
|
|
32
|
+
let plugin_file: PluginFile = toml::from_str(&toml_content)
|
|
33
|
+
.map_err(|e| format!("Failed to parse '{}': {}", toml_path.display(), e))?;
|
|
34
|
+
|
|
35
|
+
let wasm_bytes = std::fs::read(&wasm_path)
|
|
36
|
+
.map_err(|e| format!("Failed to read '{}': {}", wasm_path.display(), e))?;
|
|
37
|
+
|
|
38
|
+
Ok((plugin_file.plugin, wasm_bytes))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn load_plugin_from_uri(uri: &str) -> Result<(PluginInfo, Vec<u8>), String> {
|
|
42
|
+
if !uri.starts_with("devalang://plugin/") {
|
|
43
|
+
return Err("Invalid plugin URI".into());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let name = uri.trim_start_matches("devalang://plugin/");
|
|
47
|
+
load_plugin(name)
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pub mod loader;
|
|
@@ -254,6 +254,8 @@ impl ModuleLoader {
|
|
|
254
254
|
module: &mut Module,
|
|
255
255
|
bank_name: &str
|
|
256
256
|
) -> Result<Module, String> {
|
|
257
|
+
let alias = bank_name.split('.').last().unwrap_or(bank_name);
|
|
258
|
+
|
|
257
259
|
let bank_path = Path::new("./.deva/bank").join(bank_name);
|
|
258
260
|
let bank_toml_path = bank_path.join("bank.toml");
|
|
259
261
|
|
|
@@ -277,23 +279,23 @@ impl ModuleLoader {
|
|
|
277
279
|
|
|
278
280
|
bank_map.insert(bank_trigger.name.clone(), Value::String(bank_trigger_path.clone()));
|
|
279
281
|
|
|
280
|
-
if module.variable_table.variables.contains_key(
|
|
282
|
+
if module.variable_table.variables.contains_key(alias) {
|
|
281
283
|
eprintln!(
|
|
282
284
|
"⚠️ Trigger '{}' already defined in module '{}', skipping injection.",
|
|
283
|
-
|
|
285
|
+
alias,
|
|
284
286
|
module.path
|
|
285
287
|
);
|
|
286
288
|
continue;
|
|
287
289
|
}
|
|
288
290
|
|
|
289
291
|
module.variable_table.set(
|
|
290
|
-
format!("{}.{}",
|
|
292
|
+
format!("{}.{}", alias, bank_trigger.name),
|
|
291
293
|
Value::String(bank_trigger_path.clone())
|
|
292
294
|
);
|
|
293
295
|
}
|
|
294
296
|
|
|
295
297
|
// Inject the map under the bank name
|
|
296
|
-
module.variable_table.set(
|
|
298
|
+
module.variable_table.set(alias.to_string(), Value::Map(bank_map));
|
|
297
299
|
|
|
298
300
|
Ok(module.clone())
|
|
299
301
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
use crate::{
|
|
3
|
+
common::{ api::get_api_url },
|
|
4
|
+
installer::{ bank::install_bank, plugin::install_plugin },
|
|
5
|
+
};
|
|
6
|
+
use dirs::home_dir;
|
|
7
|
+
|
|
8
|
+
#[derive(Debug, Clone)]
|
|
9
|
+
pub enum AddonType {
|
|
10
|
+
Bank,
|
|
11
|
+
Plugin,
|
|
12
|
+
Preset,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub async fn install_addon(
|
|
16
|
+
addon_type: AddonType,
|
|
17
|
+
name: &str,
|
|
18
|
+
target_dir: &Path
|
|
19
|
+
) -> Result<(), String> {
|
|
20
|
+
match addon_type {
|
|
21
|
+
AddonType::Bank => install_bank(name, target_dir).await,
|
|
22
|
+
AddonType::Plugin => install_plugin(name, target_dir).await,
|
|
23
|
+
AddonType::Preset => Err("Preset installation not implemented".into()),
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub async fn ask_api_for_signed_url(addon_type: AddonType, slug: &str) -> Result<String, String> {
|
|
28
|
+
let api_url = get_api_url();
|
|
29
|
+
|
|
30
|
+
let mut stored_token_path = home_dir().unwrap();
|
|
31
|
+
stored_token_path.push(".devalang");
|
|
32
|
+
stored_token_path.push("session_token.json");
|
|
33
|
+
|
|
34
|
+
let stored_token = std::fs::read_to_string(&stored_token_path).unwrap_or_default();
|
|
35
|
+
|
|
36
|
+
let request_url = format!(
|
|
37
|
+
"{}/v1/assets/url?type={}&slug={}&token={}",
|
|
38
|
+
api_url,
|
|
39
|
+
match addon_type {
|
|
40
|
+
AddonType::Bank => "bank",
|
|
41
|
+
AddonType::Plugin => "plugin",
|
|
42
|
+
AddonType::Preset => "preset",
|
|
43
|
+
},
|
|
44
|
+
slug,
|
|
45
|
+
stored_token
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
let mut headers = reqwest::header::HeaderMap::new();
|
|
49
|
+
|
|
50
|
+
headers.insert("Authorization", format!("Bearer {}", stored_token).parse().unwrap());
|
|
51
|
+
|
|
52
|
+
let client: reqwest::Client = reqwest::Client
|
|
53
|
+
::builder()
|
|
54
|
+
.default_headers(headers)
|
|
55
|
+
.build()
|
|
56
|
+
.map_err(|_| "Failed to build HTTP client".to_string())?;
|
|
57
|
+
|
|
58
|
+
let req = client
|
|
59
|
+
.get(&request_url)
|
|
60
|
+
.send().await
|
|
61
|
+
.map_err(|_| "Failed to receive response".to_string())?;
|
|
62
|
+
|
|
63
|
+
let response_body: serde_json::Value = req
|
|
64
|
+
.json().await
|
|
65
|
+
.map_err(|_| "Failed to read response body".to_string())?;
|
|
66
|
+
|
|
67
|
+
let signed_url: String = serde_json
|
|
68
|
+
::from_value(
|
|
69
|
+
response_body
|
|
70
|
+
.get("payload")
|
|
71
|
+
.cloned()
|
|
72
|
+
.unwrap_or_default()
|
|
73
|
+
.get("url")
|
|
74
|
+
.cloned()
|
|
75
|
+
.unwrap_or_default()
|
|
76
|
+
)
|
|
77
|
+
.map_err(|_| "Failed to parse response body".to_string())?;
|
|
78
|
+
|
|
79
|
+
Ok(signed_url)
|
|
80
|
+
}
|
package/rust/installer/bank.rs
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
use std::path::{ Path, PathBuf };
|
|
2
2
|
use crate::{
|
|
3
|
-
common::cdn::get_cdn_url,
|
|
4
3
|
config::loader::{ add_bank_to_config, load_config },
|
|
5
|
-
installer::
|
|
4
|
+
installer::{
|
|
5
|
+
addon::{ ask_api_for_signed_url, AddonType },
|
|
6
|
+
utils::{ download_file, extract_archive },
|
|
7
|
+
},
|
|
8
|
+
utils::logger::{ LogLevel, Logger },
|
|
6
9
|
};
|
|
7
10
|
|
|
8
11
|
pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
|
|
9
|
-
let
|
|
10
|
-
|
|
12
|
+
let logger = Logger::new();
|
|
13
|
+
|
|
14
|
+
let signed_url = ask_api_for_signed_url(AddonType::Bank, name).await?;
|
|
11
15
|
|
|
12
16
|
let bank_dir = target_dir.join("bank");
|
|
13
17
|
let archive_path = PathBuf::from(format!("./.deva/tmp/{}.devabank", name));
|
|
14
18
|
let extract_path = bank_dir.join(name);
|
|
15
19
|
|
|
20
|
+
download_file(&signed_url, &archive_path).await.map_err(|e|
|
|
21
|
+
format!("Failed to download: {}", e)
|
|
22
|
+
)?;
|
|
23
|
+
|
|
16
24
|
if extract_path.exists() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
logger.log_message(
|
|
26
|
+
LogLevel::Warning,
|
|
27
|
+
&format!(
|
|
28
|
+
"Bank '{}' already exists at '{}'. Skipping install.",
|
|
29
|
+
name,
|
|
30
|
+
extract_path.display()
|
|
31
|
+
)
|
|
21
32
|
);
|
|
33
|
+
|
|
22
34
|
return Ok(());
|
|
23
35
|
}
|
|
24
36
|
|
|
25
|
-
download_file(&url, &archive_path).await.map_err(|e| format!("Failed to download: {}", e))?;
|
|
26
|
-
|
|
27
|
-
extract_archive(&archive_path, &extract_path).await.map_err(|e|
|
|
28
|
-
format!("Failed to extract: {}", e)
|
|
29
|
-
)?;
|
|
30
|
-
|
|
31
37
|
// Add the bank to the config
|
|
32
38
|
let root_dir = target_dir
|
|
33
39
|
.parent()
|
|
@@ -49,6 +55,10 @@ pub async fn install_bank(name: &str, target_dir: &Path) -> Result<(), String> {
|
|
|
49
55
|
|
|
50
56
|
let dependency_path = &format!("devalang://bank/{}", name);
|
|
51
57
|
|
|
58
|
+
extract_archive(&archive_path, &extract_path).await.map_err(|e|
|
|
59
|
+
format!("Failed to extract: {}", e)
|
|
60
|
+
)?;
|
|
61
|
+
|
|
52
62
|
add_bank_to_config(&mut config, &extract_path, &dependency_path);
|
|
53
63
|
|
|
54
64
|
Ok(())
|
package/rust/installer/mod.rs
CHANGED