@devaloop/devalang 0.0.1-alpha.1 → 0.0.1-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.devalang +9 -0
- package/Cargo.toml +15 -6
- package/README.md +79 -81
- package/docs/CHANGELOG.md +213 -0
- package/docs/ROADMAP.md +11 -8
- package/docs/TODO.md +32 -29
- package/examples/bank.deva +9 -0
- package/examples/condition.deva +20 -0
- package/examples/duration.deva +9 -0
- package/examples/group.deva +12 -0
- package/examples/index.deva +12 -5
- package/examples/loop.deva +16 -0
- package/examples/samples/hat-808.wav +0 -0
- package/examples/synth.deva +14 -0
- package/examples/variables.deva +9 -0
- package/out-tsc/bin/devalang.exe +0 -0
- package/out-tsc/scripts/version/fetch.js +1 -5
- package/package.json +5 -4
- package/project-version.json +3 -3
- package/rust/cli/bank.rs +455 -0
- package/rust/cli/build.rs +114 -28
- package/rust/cli/check.rs +96 -103
- package/rust/cli/driver.rs +280 -0
- package/rust/cli/init.rs +79 -0
- package/rust/cli/install.rs +17 -0
- package/rust/cli/mod.rs +8 -1
- package/rust/cli/play.rs +193 -0
- package/rust/cli/template.rs +57 -0
- package/rust/cli/update.rs +4 -0
- package/rust/common/cdn.rs +11 -0
- package/rust/common/mod.rs +1 -0
- package/rust/config/driver.rs +76 -0
- package/rust/config/loader.rs +110 -0
- package/rust/config/mod.rs +2 -0
- package/rust/core/audio/engine.rs +242 -0
- package/rust/core/audio/evaluator.rs +31 -0
- package/rust/core/audio/interpreter/arrow_call.rs +142 -0
- package/rust/core/audio/interpreter/call.rs +70 -0
- package/rust/core/audio/interpreter/condition.rs +69 -0
- package/rust/core/audio/interpreter/driver.rs +236 -0
- package/rust/core/audio/interpreter/let_.rs +19 -0
- package/rust/core/audio/interpreter/load.rs +18 -0
- package/rust/core/audio/interpreter/loop_.rs +67 -0
- package/rust/core/audio/interpreter/mod.rs +12 -0
- package/rust/core/audio/interpreter/sleep.rs +36 -0
- package/rust/core/audio/interpreter/spawn.rs +84 -0
- package/rust/core/audio/interpreter/tempo.rs +16 -0
- package/rust/core/audio/interpreter/trigger.rs +102 -0
- package/rust/core/audio/loader/mod.rs +1 -0
- package/rust/core/audio/loader/trigger.rs +64 -0
- package/rust/core/audio/mod.rs +6 -0
- package/rust/core/audio/player.rs +54 -0
- package/rust/core/audio/renderer.rs +54 -0
- package/rust/core/builder/mod.rs +70 -27
- package/rust/core/debugger/lexer.rs +27 -0
- package/rust/core/debugger/mod.rs +13 -49
- package/rust/core/debugger/preprocessor.rs +27 -0
- package/rust/core/debugger/store.rs +25 -0
- package/rust/core/error/mod.rs +60 -0
- package/rust/core/lexer/handler/arrow.rs +31 -0
- package/rust/core/lexer/handler/at.rs +21 -0
- package/rust/core/lexer/handler/brace.rs +41 -0
- package/rust/core/lexer/handler/colon.rs +21 -0
- package/rust/core/lexer/handler/comment.rs +30 -0
- package/rust/core/lexer/handler/dot.rs +21 -0
- package/rust/core/lexer/handler/driver.rs +241 -0
- package/rust/core/lexer/handler/identifier.rs +41 -0
- package/rust/core/lexer/handler/indent.rs +52 -0
- package/rust/core/lexer/handler/mod.rs +15 -0
- package/rust/core/lexer/handler/newline.rs +23 -0
- package/rust/core/lexer/handler/number.rs +31 -0
- package/rust/core/lexer/handler/operator.rs +44 -0
- package/rust/core/lexer/handler/slash.rs +21 -0
- package/rust/core/lexer/handler/string.rs +63 -0
- package/rust/core/lexer/mod.rs +37 -319
- package/rust/core/lexer/token.rs +87 -0
- package/rust/core/mod.rs +6 -2
- package/rust/core/parser/driver.rs +339 -0
- package/rust/core/parser/handler/arrow_call.rs +151 -0
- package/rust/core/parser/handler/at.rs +162 -0
- package/rust/core/parser/handler/bank.rs +41 -0
- package/rust/core/parser/handler/condition.rs +74 -0
- package/rust/core/parser/handler/dot.rs +178 -0
- package/rust/core/parser/handler/identifier/call.rs +41 -0
- package/rust/core/parser/handler/identifier/group.rs +75 -0
- package/rust/core/parser/handler/identifier/let_.rs +133 -0
- package/rust/core/parser/handler/identifier/mod.rs +51 -0
- package/rust/core/parser/handler/identifier/sleep.rs +33 -0
- package/rust/core/parser/handler/identifier/spawn.rs +41 -0
- package/rust/core/parser/handler/identifier/synth.rs +65 -0
- package/rust/core/parser/handler/loop_.rs +72 -0
- package/rust/core/parser/handler/mod.rs +8 -0
- package/rust/core/parser/handler/tempo.rs +47 -0
- package/rust/core/parser/mod.rs +3 -200
- package/rust/core/parser/statement.rs +96 -0
- package/rust/core/preprocessor/loader.rs +308 -0
- package/rust/core/preprocessor/mod.rs +2 -24
- package/rust/core/preprocessor/module.rs +42 -56
- package/rust/core/preprocessor/processor.rs +76 -0
- package/rust/core/preprocessor/resolver/bank.rs +41 -51
- package/rust/core/preprocessor/resolver/call.rs +123 -0
- package/rust/core/preprocessor/resolver/condition.rs +92 -0
- package/rust/core/preprocessor/resolver/driver.rs +232 -0
- package/rust/core/preprocessor/resolver/group.rs +61 -0
- package/rust/core/preprocessor/resolver/let_.rs +31 -0
- package/rust/core/preprocessor/resolver/loop_.rs +76 -67
- package/rust/core/preprocessor/resolver/mod.rs +12 -111
- package/rust/core/preprocessor/resolver/spawn.rs +58 -0
- package/rust/core/preprocessor/resolver/synth.rs +50 -0
- package/rust/core/preprocessor/resolver/tempo.rs +40 -61
- package/rust/core/preprocessor/resolver/trigger.rs +90 -154
- package/rust/core/preprocessor/resolver/value.rs +78 -0
- package/rust/core/shared/bank.rs +21 -0
- package/rust/core/shared/duration.rs +9 -0
- package/rust/core/shared/mod.rs +3 -0
- package/rust/core/shared/value.rs +29 -0
- package/rust/core/store/export.rs +28 -0
- package/rust/core/store/global.rs +39 -0
- package/rust/core/store/import.rs +28 -0
- package/rust/core/store/mod.rs +4 -0
- package/rust/core/store/variable.rs +28 -0
- package/rust/core/utils/mod.rs +2 -0
- package/rust/core/utils/path.rs +31 -0
- package/rust/core/utils/validation.rs +37 -0
- package/rust/installer/bank.rs +55 -0
- package/rust/installer/mod.rs +1 -0
- package/rust/lib.rs +162 -1
- package/rust/main.rs +104 -31
- package/rust/utils/file.rs +35 -0
- package/rust/utils/installer.rs +56 -0
- package/rust/utils/logger.rs +108 -34
- package/rust/utils/mod.rs +5 -3
- package/rust/utils/{loader.rs → spinner.rs} +2 -0
- package/rust/utils/watcher.rs +33 -0
- package/templates/minimal/.devalang +5 -0
- package/templates/minimal/README.md +202 -0
- package/templates/minimal/src/index.deva +2 -0
- package/templates/welcome/.devalang +5 -0
- package/templates/welcome/README.md +202 -0
- package/templates/welcome/samples/kick-808.wav +0 -0
- package/templates/welcome/src/index.deva +13 -0
- package/templates/welcome/src/variables.deva +5 -0
- package/typescript/scripts/version/fetch.ts +1 -6
- package/docs/COMMANDS.md +0 -31
- package/docs/SYNTAX.md +0 -148
- package/examples/exported.deva +0 -7
- package/rust/audio/mod.rs +0 -1
- package/rust/cli/new.rs +0 -1
- package/rust/core/parser/at.rs +0 -142
- package/rust/core/parser/bank.rs +0 -42
- package/rust/core/parser/dot.rs +0 -107
- package/rust/core/parser/identifer.rs +0 -91
- package/rust/core/parser/loop_.rs +0 -62
- package/rust/core/parser/tempo.rs +0 -42
- package/rust/core/parser/variable.rs +0 -129
- package/rust/core/preprocessor/dependencies.rs +0 -54
- package/rust/core/preprocessor/resolver/at.rs +0 -24
- package/rust/core/types/cli.rs +0 -160
- package/rust/core/types/mod.rs +0 -7
- package/rust/core/types/module.rs +0 -41
- package/rust/core/types/parser.rs +0 -73
- package/rust/core/types/statement.rs +0 -105
- package/rust/core/types/store.rs +0 -116
- package/rust/core/types/token.rs +0 -83
- package/rust/core/types/variable.rs +0 -32
- package/rust/runner/executer.rs +0 -44
- package/rust/runner/mod.rs +0 -1
- package/rust/utils/path.rs +0 -46
package/rust/cli/init.rs
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
use std::{ fs, path::Path };
|
|
2
|
+
use include_dir::{ include_dir, Dir };
|
|
3
|
+
use crate::{ cli::template::get_available_templates, utils::file::copy_dir_recursive };
|
|
4
|
+
|
|
5
|
+
#[cfg(feature = "cli")]
|
|
6
|
+
use inquire::{ Select, Confirm };
|
|
7
|
+
|
|
8
|
+
static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
|
9
|
+
|
|
10
|
+
#[cfg(feature = "cli")]
|
|
11
|
+
pub fn handle_init_command(name: Option<String>, template: Option<String>) {
|
|
12
|
+
let current_dir = std::env::current_dir().unwrap();
|
|
13
|
+
let project_name = name
|
|
14
|
+
.clone()
|
|
15
|
+
.unwrap_or_else(|| {
|
|
16
|
+
current_dir.file_name().unwrap_or_default().to_string_lossy().to_string()
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Select a template if not provided
|
|
20
|
+
let selected_template = template.unwrap_or_else(|| {
|
|
21
|
+
Select::new("Select a template for your project:", get_available_templates())
|
|
22
|
+
.prompt()
|
|
23
|
+
.unwrap_or_else(|_| {
|
|
24
|
+
eprintln!("No template selected. Exiting...");
|
|
25
|
+
std::process::exit(1);
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if selected_template.is_empty() {
|
|
30
|
+
eprintln!("Template cannot be empty.");
|
|
31
|
+
std::process::exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if name.is_none() {
|
|
35
|
+
// Case of initialization in the current directory
|
|
36
|
+
if fs::read_dir(¤t_dir).unwrap().next().is_some() {
|
|
37
|
+
let confirm = Confirm::new(
|
|
38
|
+
"The current directory is not empty. Do you want to continue?"
|
|
39
|
+
)
|
|
40
|
+
.with_default(false)
|
|
41
|
+
.prompt()
|
|
42
|
+
.unwrap_or(false);
|
|
43
|
+
|
|
44
|
+
if !confirm {
|
|
45
|
+
eprintln!("Operation cancelled by the user.");
|
|
46
|
+
std::process::exit(0);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
scaffold_project_current_dir(current_dir.as_path(), selected_template);
|
|
51
|
+
println!(
|
|
52
|
+
"✅ Initialized '{}' project in current directory: {}",
|
|
53
|
+
project_name,
|
|
54
|
+
current_dir.display()
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
// Case of initialization in a new directory
|
|
58
|
+
let target_path = current_dir.join(&project_name);
|
|
59
|
+
|
|
60
|
+
if target_path.exists() {
|
|
61
|
+
eprintln!("❌ A folder named '{}' already exists.", project_name);
|
|
62
|
+
std::process::exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fs::create_dir_all(&target_path).expect("Error creating project directory");
|
|
66
|
+
|
|
67
|
+
scaffold_project_current_dir(&target_path, selected_template);
|
|
68
|
+
println!("✅ Initialized '{}' project in: {}", project_name, target_path.display());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn scaffold_project_current_dir(path: &Path, template: String) {
|
|
73
|
+
let template_dir = TEMPLATES_DIR.get_dir(&template).unwrap_or_else(|| {
|
|
74
|
+
eprintln!("❌ The template '{}' doesn't exist.", template);
|
|
75
|
+
std::process::exit(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
copy_dir_recursive(template_dir, path, &template_dir.path());
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
use crate::installer::bank::install_bank;
|
|
2
|
+
|
|
3
|
+
#[cfg(feature = "cli")]
|
|
4
|
+
pub async fn handle_install_bank_command(name: String) -> Result<(), String> {
|
|
5
|
+
let deva_dir = std::path::Path::new("./.deva/");
|
|
6
|
+
|
|
7
|
+
println!("⬇️ Installing bank '{}'...", name);
|
|
8
|
+
println!("📂 Target directory: {}", deva_dir.display());
|
|
9
|
+
|
|
10
|
+
if let Err(e) = install_bank(name.as_str(), deva_dir).await {
|
|
11
|
+
eprintln!("❌ Error installing bank '{}': {}", name, e);
|
|
12
|
+
} else {
|
|
13
|
+
println!("✅ Bank '{}' installed successfully!", name);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Ok(())
|
|
17
|
+
}
|
package/rust/cli/mod.rs
CHANGED
package/rust/cli/play.rs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
core::{
|
|
3
|
+
builder::Builder,
|
|
4
|
+
debugger::{ lexer::write_lexer_log_file, preprocessor::write_preprocessor_log_file },
|
|
5
|
+
preprocessor::loader::ModuleLoader,
|
|
6
|
+
store::global::GlobalStore,
|
|
7
|
+
utils::path::{ find_entry_file, normalize_path },
|
|
8
|
+
},
|
|
9
|
+
config::driver::Config,
|
|
10
|
+
utils::{ logger::{ LogLevel, Logger }, spinner::with_spinner, watcher::watch_directory },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
use std::{ path::Path, sync::mpsc::channel, thread, time::Duration };
|
|
14
|
+
use std::fs;
|
|
15
|
+
use std::collections::HashMap;
|
|
16
|
+
|
|
17
|
+
#[cfg(feature = "cli")]
|
|
18
|
+
pub fn handle_play_command(
|
|
19
|
+
config: Option<Config>,
|
|
20
|
+
entry: Option<String>,
|
|
21
|
+
output: Option<String>,
|
|
22
|
+
watch: bool,
|
|
23
|
+
repeat: bool
|
|
24
|
+
) {
|
|
25
|
+
use crate::core::audio::player::AudioPlayer;
|
|
26
|
+
|
|
27
|
+
let logger = Logger::new();
|
|
28
|
+
|
|
29
|
+
let entry_path = entry
|
|
30
|
+
.or_else(|| config.as_ref().and_then(|c| c.defaults.entry.clone()))
|
|
31
|
+
.unwrap_or_else(|| "".to_string());
|
|
32
|
+
|
|
33
|
+
let output_path = output
|
|
34
|
+
.or_else(|| config.as_ref().and_then(|c| c.defaults.output.clone()))
|
|
35
|
+
.unwrap_or_else(|| "".to_string());
|
|
36
|
+
|
|
37
|
+
let fetched_repeat = if repeat {
|
|
38
|
+
true
|
|
39
|
+
} else {
|
|
40
|
+
config
|
|
41
|
+
.as_ref()
|
|
42
|
+
.and_then(|c| c.defaults.repeat)
|
|
43
|
+
.unwrap_or(false)
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if entry_path.is_empty() || output_path.is_empty() {
|
|
47
|
+
logger.log_message(LogLevel::Error, "Entry or output path not specified.");
|
|
48
|
+
std::process::exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let entry_file = find_entry_file(&entry_path).unwrap_or_else(|| {
|
|
52
|
+
logger.log_message(LogLevel::Error, "index.deva not found");
|
|
53
|
+
std::process::exit(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let audio_file = format!("{}/audio/index.wav", normalize_path(&output_path));
|
|
57
|
+
let mut audio_player = AudioPlayer::new();
|
|
58
|
+
|
|
59
|
+
if watch && fetched_repeat {
|
|
60
|
+
logger.log_message(
|
|
61
|
+
LogLevel::Error,
|
|
62
|
+
"Watch and repeat cannot be used together. Use repeat instead."
|
|
63
|
+
);
|
|
64
|
+
std::process::exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if watch {
|
|
68
|
+
let (tx, rx) = channel::<()>();
|
|
69
|
+
|
|
70
|
+
// Thread 1 : Watcher sending changes
|
|
71
|
+
let entry_clone = entry_path.clone();
|
|
72
|
+
thread::spawn(move || {
|
|
73
|
+
let _ = watch_directory(entry_clone, move || {
|
|
74
|
+
let _ = tx.send(()); // signal a change
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Main thread: build + play in a loop
|
|
79
|
+
begin_play(&config, &entry_file, &output_path);
|
|
80
|
+
audio_player.play_file_once(&audio_file);
|
|
81
|
+
|
|
82
|
+
logger.log_message(LogLevel::Watcher, "Watching for changes... Press Ctrl+C to exit.");
|
|
83
|
+
|
|
84
|
+
while let Ok(_) = rx.recv() {
|
|
85
|
+
logger.log_message(LogLevel::Watcher, "Change detected, rebuilding...");
|
|
86
|
+
|
|
87
|
+
begin_play(&config, &entry_file, &output_path);
|
|
88
|
+
|
|
89
|
+
logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
|
|
90
|
+
|
|
91
|
+
audio_player.play_file_once(&audio_file);
|
|
92
|
+
}
|
|
93
|
+
} else if fetched_repeat {
|
|
94
|
+
// Initial build to start from a clean slate
|
|
95
|
+
begin_play(&config, &entry_file, &output_path);
|
|
96
|
+
|
|
97
|
+
logger.log_message(LogLevel::Info, "🎵 Playback started (repeat mode)...");
|
|
98
|
+
|
|
99
|
+
let mut last_snapshot = snapshot_files(&entry_path);
|
|
100
|
+
let mut audio_player = AudioPlayer::new();
|
|
101
|
+
audio_player.play_file_once(&audio_file);
|
|
102
|
+
|
|
103
|
+
loop {
|
|
104
|
+
let current_snapshot = snapshot_files(&entry_path);
|
|
105
|
+
let has_changed = files_changed(&last_snapshot, ¤t_snapshot);
|
|
106
|
+
|
|
107
|
+
if has_changed {
|
|
108
|
+
logger.log_message(LogLevel::Info, "Change detected, rebuilding in background...");
|
|
109
|
+
let entry_file = entry_file.clone();
|
|
110
|
+
let output_path = output_path.clone();
|
|
111
|
+
let config_clone = config.clone();
|
|
112
|
+
|
|
113
|
+
// Rebuild in a separate thread
|
|
114
|
+
std::thread::spawn(move || {
|
|
115
|
+
begin_play(&config_clone, &entry_file, &output_path);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
last_snapshot = current_snapshot;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Wait for the audio to finish without blocking the current playback
|
|
122
|
+
audio_player.wait_until_end();
|
|
123
|
+
|
|
124
|
+
// Then replay the audio (rebuilt or not)
|
|
125
|
+
audio_player.play_file_once(&audio_file);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
// Single execution
|
|
129
|
+
begin_play(&config, &entry_file, &output_path);
|
|
130
|
+
|
|
131
|
+
logger.log_message(LogLevel::Info, "🎵 Playback started (once mode)...");
|
|
132
|
+
|
|
133
|
+
audio_player.play_file_once(&audio_file);
|
|
134
|
+
audio_player.wait_until_end();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn begin_play(config: &Option<Config>, entry_file: &str, output: &str) {
|
|
139
|
+
let spinner = with_spinner("Building...", || {
|
|
140
|
+
thread::sleep(Duration::from_millis(800));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
let normalized_entry = normalize_path(entry_file);
|
|
144
|
+
let normalized_output_dir = normalize_path(&output);
|
|
145
|
+
|
|
146
|
+
let duration = std::time::Instant::now();
|
|
147
|
+
let mut global_store = GlobalStore::new();
|
|
148
|
+
let loader = ModuleLoader::new(&normalized_entry, &normalized_output_dir);
|
|
149
|
+
let (modules_tokens, modules_statements) = loader.load_all_modules(&mut global_store);
|
|
150
|
+
|
|
151
|
+
// SECTION Write logs
|
|
152
|
+
write_lexer_log_file(&normalized_output_dir, "lexer_tokens.log", modules_tokens.clone());
|
|
153
|
+
write_preprocessor_log_file(
|
|
154
|
+
&normalized_output_dir,
|
|
155
|
+
"resolved_statements.log",
|
|
156
|
+
modules_statements.clone()
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// SECTION Building AST and Audio
|
|
160
|
+
let builder = Builder::new();
|
|
161
|
+
builder.build_ast(&modules_statements, &output);
|
|
162
|
+
builder.build_audio(&modules_statements, &output, &mut global_store);
|
|
163
|
+
|
|
164
|
+
// SECTION Logging
|
|
165
|
+
let logger = Logger::new();
|
|
166
|
+
let success_message = format!(
|
|
167
|
+
"Build completed successfully in {:.2?}. Output files written to: '{}'",
|
|
168
|
+
duration.elapsed(),
|
|
169
|
+
normalized_output_dir
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
logger.log_message(LogLevel::Success, &success_message);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn snapshot_files<P: AsRef<Path>>(dir: P) -> HashMap<String, u64> {
|
|
176
|
+
let mut map = HashMap::new();
|
|
177
|
+
if let Ok(entries) = fs::read_dir(dir) {
|
|
178
|
+
for entry in entries.flatten() {
|
|
179
|
+
if let Ok(meta) = entry.metadata() {
|
|
180
|
+
if let Ok(mtime) = meta.modified() {
|
|
181
|
+
if let Ok(duration) = mtime.duration_since(std::time::UNIX_EPOCH) {
|
|
182
|
+
map.insert(entry.path().display().to_string(), duration.as_secs());
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
map
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fn files_changed(old: &HashMap<String, u64>, new: &HashMap<String, u64>) -> bool {
|
|
192
|
+
old != new
|
|
193
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
use include_dir::{ include_dir, Dir, DirEntry };
|
|
2
|
+
use crate::utils::file::format_file_size;
|
|
3
|
+
|
|
4
|
+
static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
|
5
|
+
|
|
6
|
+
#[cfg(feature = "cli")]
|
|
7
|
+
pub fn handle_template_list_command() {
|
|
8
|
+
let available_templates = get_available_templates();
|
|
9
|
+
|
|
10
|
+
println!("📦 Available templates ({}) :\n", available_templates.len());
|
|
11
|
+
|
|
12
|
+
for dir in available_templates {
|
|
13
|
+
println!("• {}", dir);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
println!("\nUsage : devalang init --name <project-name> --template <template-name>");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[cfg(feature = "cli")]
|
|
20
|
+
pub fn handle_template_info_command(name: String) {
|
|
21
|
+
let template_dir = TEMPLATES_DIR.get_dir(name.clone()).unwrap_or_else(|| {
|
|
22
|
+
println!("❌ The template '{}' is not found.", name);
|
|
23
|
+
|
|
24
|
+
std::process::exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
let mut file_count = 0;
|
|
28
|
+
let mut dir_count = 0;
|
|
29
|
+
let mut total_size: u64 = 0;
|
|
30
|
+
|
|
31
|
+
fn walk(dir: &Dir, file_count: &mut u32, dir_count: &mut u32, total_size: &mut u64) {
|
|
32
|
+
for entry in dir.entries() {
|
|
33
|
+
match entry {
|
|
34
|
+
DirEntry::File(file) => {
|
|
35
|
+
*file_count += 1;
|
|
36
|
+
*total_size += file.contents().len() as u64;
|
|
37
|
+
}
|
|
38
|
+
DirEntry::Dir(subdir) => {
|
|
39
|
+
*dir_count += 1;
|
|
40
|
+
walk(subdir, file_count, dir_count, total_size);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
walk(template_dir, &mut file_count, &mut dir_count, &mut total_size);
|
|
47
|
+
|
|
48
|
+
println!("📦 Template : {}", name);
|
|
49
|
+
println!("📂 Content : {file_count} file(s), {dir_count} folder(s)");
|
|
50
|
+
println!("💾 Size : {}", format_file_size(total_size));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn get_available_templates() -> Vec<String> {
|
|
54
|
+
TEMPLATES_DIR.dirs()
|
|
55
|
+
.map(|dir| dir.path().file_name().unwrap().to_string_lossy().to_string())
|
|
56
|
+
.collect()
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pub mod cdn;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
use serde::{ Deserialize, Serialize };
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Deserialize, Clone, Serialize)]
|
|
4
|
+
pub struct Config {
|
|
5
|
+
pub defaults: ConfigDefaults,
|
|
6
|
+
pub banks: Option<Vec<BankEntry>>,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Deserialize, Clone, Serialize)]
|
|
10
|
+
pub struct ConfigDefaults {
|
|
11
|
+
pub entry: Option<String>,
|
|
12
|
+
pub output: Option<String>,
|
|
13
|
+
pub watch: Option<bool>,
|
|
14
|
+
pub repeat: Option<bool>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
|
18
|
+
pub struct BankEntry {
|
|
19
|
+
pub path: String,
|
|
20
|
+
pub version: Option<String>,
|
|
21
|
+
pub author: Option<String>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl Config {
|
|
25
|
+
pub fn new() -> Self {
|
|
26
|
+
Config {
|
|
27
|
+
defaults: ConfigDefaults {
|
|
28
|
+
entry: None,
|
|
29
|
+
output: None,
|
|
30
|
+
watch: None,
|
|
31
|
+
repeat: None,
|
|
32
|
+
},
|
|
33
|
+
banks: Some(Vec::new()),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
pub fn with_defaults(
|
|
38
|
+
entry: Option<String>,
|
|
39
|
+
output: Option<String>,
|
|
40
|
+
watch: Option<bool>,
|
|
41
|
+
repeat: Option<bool>
|
|
42
|
+
) -> Self {
|
|
43
|
+
Config {
|
|
44
|
+
defaults: ConfigDefaults {
|
|
45
|
+
entry,
|
|
46
|
+
output,
|
|
47
|
+
watch,
|
|
48
|
+
repeat,
|
|
49
|
+
},
|
|
50
|
+
banks: Some(Vec::new()),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub fn from_string(config_string: &str) -> Result<(Self, String), String> {
|
|
55
|
+
let config: Config = toml
|
|
56
|
+
::from_str(config_string)
|
|
57
|
+
.map_err(|e| format!("Failed to parse config string: {}", e))?;
|
|
58
|
+
let config_path = ".devalang".to_string();
|
|
59
|
+
|
|
60
|
+
Ok((config, config_path))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub fn write(&self, new_config: &Config) -> Result<(), String> {
|
|
64
|
+
let config_path = ".devalang";
|
|
65
|
+
|
|
66
|
+
let content = toml
|
|
67
|
+
::to_string(new_config)
|
|
68
|
+
.map_err(|e| format!("Failed to serialize config: {}", e))?;
|
|
69
|
+
|
|
70
|
+
std::fs
|
|
71
|
+
::write(config_path, content)
|
|
72
|
+
.map_err(|e| format!("Failed to write config to file '{}': {}", config_path, e))?;
|
|
73
|
+
|
|
74
|
+
Ok(())
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
use std::{ fs, path::Path };
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use crate::config::driver::{ BankEntry, Config };
|
|
4
|
+
|
|
5
|
+
pub fn load_config(path: Option<&Path>) -> Option<Config> {
|
|
6
|
+
let config_path = path.unwrap_or_else(|| Path::new(".devalang"));
|
|
7
|
+
|
|
8
|
+
if config_path.exists() {
|
|
9
|
+
let content = fs::read_to_string(config_path).ok()?;
|
|
10
|
+
toml::from_str(&content).ok()
|
|
11
|
+
} else {
|
|
12
|
+
None
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn update_bank_version_in_config(config: &mut Config, dependency: &str, new_version: &str) {
|
|
17
|
+
// Si le vecteur banks n'existe pas, on ne fait rien
|
|
18
|
+
if config.banks.is_none() {
|
|
19
|
+
println!("No banks configured.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let banks = config.banks.as_mut().unwrap();
|
|
24
|
+
|
|
25
|
+
if let Some(bank) = banks.iter_mut().find(|b| b.path.contains(dependency)) {
|
|
26
|
+
bank.version = Some(new_version.to_string());
|
|
27
|
+
|
|
28
|
+
if let Err(e) = config.write(config) {
|
|
29
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
30
|
+
} else {
|
|
31
|
+
println!("✅ Bank '{}' updated to version '{}'", dependency, new_version);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
println!("Bank '{}' not found in config", dependency);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn remove_bank_from_config(config: &mut Config, dependency: &str) {
|
|
39
|
+
if config.banks.is_none() {
|
|
40
|
+
println!("No banks configured.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let banks = config.banks.as_mut().unwrap();
|
|
45
|
+
|
|
46
|
+
if let Some(index) = banks.iter().position(|b| b.path.contains(dependency)) {
|
|
47
|
+
banks.remove(index);
|
|
48
|
+
|
|
49
|
+
if let Err(e) = config.write(config) {
|
|
50
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
51
|
+
} else {
|
|
52
|
+
println!("✅ Bank '{}' removed from config", dependency);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
println!("Bank '{}' not found in config", dependency);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn add_bank_to_config(config: &mut Config, real_path: &Path, dependency: &str) {
|
|
60
|
+
if config.banks.is_none() {
|
|
61
|
+
config.banks = Some(Vec::new());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let banks = config.banks.as_mut().unwrap();
|
|
65
|
+
|
|
66
|
+
let exists = banks.iter().any(|b| b.path == dependency);
|
|
67
|
+
if exists {
|
|
68
|
+
println!("Bank '{}' already in config", dependency);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let metadata_path = Path::new(real_path).join("bank.toml");
|
|
73
|
+
|
|
74
|
+
if !metadata_path.exists() {
|
|
75
|
+
eprintln!("❌ Bank metadata file '{}' does not exist", metadata_path.display());
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let metadata_content = fs
|
|
80
|
+
::read_to_string(&metadata_path)
|
|
81
|
+
.expect("Failed to read bank metadata file");
|
|
82
|
+
|
|
83
|
+
let metadata: HashMap<String, String> = toml
|
|
84
|
+
::from_str(&metadata_content)
|
|
85
|
+
.expect("Failed to parse bank metadata file");
|
|
86
|
+
|
|
87
|
+
let bank_to_insert = BankEntry {
|
|
88
|
+
path: dependency.to_string(),
|
|
89
|
+
version: Some(
|
|
90
|
+
metadata
|
|
91
|
+
.get("version")
|
|
92
|
+
.cloned()
|
|
93
|
+
.unwrap_or_else(|| "0.0.1".to_string())
|
|
94
|
+
),
|
|
95
|
+
author: Some(
|
|
96
|
+
metadata
|
|
97
|
+
.get("author")
|
|
98
|
+
.cloned()
|
|
99
|
+
.unwrap_or_else(|| "unknown".to_string())
|
|
100
|
+
),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
banks.push(bank_to_insert);
|
|
104
|
+
|
|
105
|
+
if let Err(e) = config.write(config) {
|
|
106
|
+
eprintln!("❌ Failed to write config: {}", e);
|
|
107
|
+
} else {
|
|
108
|
+
println!("✅ Bank '{}' added to config", dependency);
|
|
109
|
+
}
|
|
110
|
+
}
|