From 5a2025fb80fa8f9277e0b3c45b588310c945e9e1 Mon Sep 17 00:00:00 2001 From: Dexoron Date: Fri, 20 Feb 2026 17:54:01 +0800 Subject: [PATCH] Changed: - Reworked `dcr.toml` handling: added read, validation, and edit through `core::config` - `new` and `init` use the new config creation logic - `build` and `run` now require `dcr.toml` and read `compiler` and `name` from it --- Cargo.lock | 57 ++++++++++++- Cargo.toml | 5 +- src/cli/build.rs | 32 +++++--- src/cli/init.rs | 16 ++-- src/cli/new.rs | 18 +++-- src/cli/run.rs | 19 ++++- src/config.rs | 20 ----- src/core/config.rs | 193 +++++++++++++++++++++++++++++++++++++++++++++ src/core/mod.rs | 1 + src/main.rs | 1 + 10 files changed, 312 insertions(+), 50 deletions(-) create mode 100644 src/core/config.rs create mode 100644 src/core/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 57bbc14..14c5b0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,11 +62,12 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "dcr" -version = "0.2.1" +version = "0.2.2" dependencies = [ "reqwest", "self-replace", "serde", + "toml", ] [[package]] @@ -878,6 +879,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1043,6 +1053,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "1.0.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7614eaf19ad818347db24addfa201729cf2a9b6fdfd9eb0ab870fcacc606c0c" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tower" version = "0.5.3" @@ -1466,6 +1515,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/Cargo.toml b/Cargo.toml index 9005917..422496b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "dcr" -version = "0.2.1" +version = "0.2.2" edition = "2024" build = "build.rs" [dependencies] reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } -self-replace = "1" \ No newline at end of file +self-replace = "1" +toml = "1.0.3" diff --git a/src/cli/build.rs b/src/cli/build.rs index be2a08b..0fd964c 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -1,4 +1,5 @@ -use crate::config::{PROFILE, PROJECT_COMPILER, flags}; +use crate::config::{PROFILE, flags}; +use crate::core::config::Config; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; @@ -8,10 +9,6 @@ use std::time::Instant; pub fn build(args: &[String]) -> i32 { let mut active_profile = PROFILE.to_string(); - let project_name = std::env::current_dir() - .ok() - .and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string())) - .unwrap_or_else(|| "project".to_string()); let items = check_dir(None).unwrap_or_default(); if !items.contains(&"dcr.toml".to_string()) { @@ -19,6 +16,22 @@ pub fn build(args: &[String]) -> i32 { return 1; } + let config = match Config::open("./dcr.toml") { + Ok(cfg) => cfg, + Err(_) => { + error("dcr.toml file not found"); + return 1; + } + }; + let project_compiler: &str = config + .get("package.compiler") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let project_name: &str = config + .get("package.name") + .and_then(|v| v.as_str()) + .unwrap_or(""); + if let Some(first_arg) = args.first() { if first_arg.starts_with("--") { let candidate = first_arg.trim_start_matches("--"); @@ -36,9 +49,9 @@ pub fn build(args: &[String]) -> i32 { println!( " Building project `{}`\n Profile: {}\n Compiler: {}\n", - colored(&project_name, BOLD_GREEN), + colored(project_name, BOLD_GREEN), colored(&active_profile, BOLD_GREEN), - colored(PROJECT_COMPILER, BOLD_GREEN) + colored(project_compiler, BOLD_GREEN) ); if !items.contains(&"target".to_string()) { @@ -53,12 +66,13 @@ pub fn build(args: &[String]) -> i32 { if src_items.contains(&"main.c".to_string()) { let compile_flags = flags(&active_profile).unwrap_or(&[]); let start_time = Instant::now(); - let mut cmd = Command::new(PROJECT_COMPILER); + let mut cmd = Command::new(project_compiler); cmd.arg("./src/main.c"); for flag in compile_flags { cmd.arg(flag); } - cmd.arg("-o").arg(format!("./target/{active_profile}/main")); + cmd.arg("-o") + .arg(format!("./target/{active_profile}/{project_name}")); match cmd.status() { Ok(status) if status.success() => { diff --git a/src/cli/init.rs b/src/cli/init.rs index 8971405..60e36f2 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -1,9 +1,11 @@ -use crate::config::{FILE_MAIN_C, file_dcr_toml}; +use crate::config::FILE_MAIN_C; +use crate::core::config::Config; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use std::fs; use std::io::Write; +use toml::Value; pub fn init(args: &[String]) -> i32 { if !args.is_empty() { @@ -27,15 +29,15 @@ pub fn init(args: &[String]) -> i32 { .unwrap_or_else(|_| ".".to_string()); println!("Initializing the project in {cwd}"); - let mut dcr_toml = match fs::File::create("./dcr.toml") { - Ok(file) => file, + let mut config = match Config::new("./dcr.toml") { + Ok(cfg) => cfg, Err(_) => { error("Failed to create dcr.toml"); return 1; } }; - if dcr_toml - .write_all(file_dcr_toml(&project_name).as_bytes()) + if config + .edit("package.name", Value::String(project_name.clone())) .is_err() { error("Failed to write dcr.toml"); @@ -43,7 +45,7 @@ pub fn init(args: &[String]) -> i32 { } println!( " {} Created file {}", - colored("✔", BOLD_CYAN), + colored("✔", BOLD_GREEN), colored("dcr.toml", BOLD_CYAN) ); @@ -73,6 +75,6 @@ pub fn init(args: &[String]) -> i32 { colored(project_name.as_str(), BOLD_GREEN) ); printc("Next step:", BOLD_GREEN); - printc(" cd {}\n dcr run", project_name.as_str()); + printc(" dcr run", BOLD_CYAN); 0 } diff --git a/src/cli/new.rs b/src/cli/new.rs index de504d3..1d14148 100644 --- a/src/cli/new.rs +++ b/src/cli/new.rs @@ -1,9 +1,11 @@ -use crate::config::{FILE_MAIN_C, file_dcr_toml}; +use crate::config::FILE_MAIN_C; +use crate::core::config::Config; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use std::fs; use std::io::Write; +use toml::Value; pub fn new(args: &[String]) -> i32 { let items = check_dir(None).unwrap_or_default(); @@ -42,20 +44,20 @@ pub fn new(args: &[String]) -> i32 { } println!( " {} Directory created {}", - colored("✔", BOLD_CYAN), + colored("✔", BOLD_GREEN), project_name ); let toml_path = format!("./{project_name}/dcr.toml"); - let mut dcr_toml = match fs::File::create(&toml_path) { - Ok(file) => file, + let mut config = match Config::new(&toml_path) { + Ok(cfg) => cfg, Err(_) => { error("Failed to create dcr.toml"); return 1; } }; - if dcr_toml - .write_all(file_dcr_toml(project_name).as_bytes()) + if config + .edit("package.name", Value::String(project_name.to_string())) .is_err() { error("Failed to write dcr.toml"); @@ -63,7 +65,7 @@ pub fn new(args: &[String]) -> i32 { } println!( " {} Created file {}", - colored("✔", BOLD_CYAN), + colored("✔", BOLD_GREEN), colored("dcr.toml", BOLD_CYAN) ); @@ -94,6 +96,6 @@ pub fn new(args: &[String]) -> i32 { colored(project_name, BOLD_GREEN) ); printc("Next step:", BOLD_GREEN); - printc(" cd {}\n dcr run", project_name); + printc(&format!(" cd {}\n dcr run", project_name), BOLD_CYAN); 0 } diff --git a/src/cli/run.rs b/src/cli/run.rs index 5361973..ff14163 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -1,5 +1,6 @@ use crate::cli::build::build; use crate::config::{PROFILE, flags}; +use crate::core::config::Config; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; @@ -14,6 +15,18 @@ pub fn run(args: &[String]) -> i32 { return 1; } + let config = match Config::open("./dcr.toml") { + Ok(cfg) => cfg, + Err(_) => { + error("dcr.toml file not found"); + return 1; + } + }; + let project_name: &str = config + .get("package.name") + .and_then(|v| v.as_str()) + .unwrap_or(""); + if let Some(first_arg) = args.first() { if first_arg.starts_with("--") { let candidate = first_arg.trim_start_matches("--"); @@ -32,11 +45,11 @@ pub fn run(args: &[String]) -> i32 { let build_status = build(args); if build_status == 0 { println!( - "\n {} target/{active_profile}/main", + "\n {} target/{active_profile}/{project_name}", colored("Running", BOLD_GREEN) ); println!("--------------------------------"); - return Command::new(format!("./target/{active_profile}/main")) + return Command::new(format!("./target/{active_profile}/{project_name}")) .status() .map(|status| status.code().unwrap_or(1)) .unwrap_or(1); @@ -47,7 +60,7 @@ pub fn run(args: &[String]) -> i32 { .contains(&active_profile) { warn("Launch of the latest release"); - return Command::new(format!("./target/{active_profile}/main")) + return Command::new(format!("./target/{active_profile}/{project_name}")) .status() .map(|status| status.code().unwrap_or(1)) .unwrap_or(1); diff --git a/src/config.rs b/src/config.rs index d293c69..7f8b4ff 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,24 +1,4 @@ -pub const PROJECT_VERSION: &str = "0.1.0"; -pub const PROJECT_LANGUAGE: &str = "c"; -pub const PROJECT_COMPILER: &str = "clang"; pub const PROFILE: &str = "debug"; - -pub fn file_dcr_toml(project_name: &str) -> String { - format!( - r#"[package] -name = "{project_name}" -version = "{project_version}" -language = "{project_language}" -compiler = "{project_compiler}" - -[dependencies] -"#, - project_version = PROJECT_VERSION, - project_language = PROJECT_LANGUAGE, - project_compiler = PROJECT_COMPILER - ) -} - pub const FILE_MAIN_C: &str = r#"#include int main(void) { diff --git a/src/core/config.rs b/src/core/config.rs new file mode 100644 index 0000000..bae951f --- /dev/null +++ b/src/core/config.rs @@ -0,0 +1,193 @@ +use std::fs; +use std::path::{Path, PathBuf}; +use toml::Value; +use toml::map::Map; + +const DEFAULT_VERSION: &str = "0.1.0"; +const DEFAULT_LANGUAGE: &str = "c"; +const DEFAULT_COMPILER: &str = "clang"; + +#[derive(Debug)] +pub enum ConfigError { + Io(std::io::Error), + TomlDe(toml::de::Error), + TomlSer(toml::ser::Error), + Invalid(String), +} + +impl std::fmt::Display for ConfigError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ConfigError::Io(err) => write!(f, "I/O error: {err}"), + ConfigError::TomlDe(err) => write!(f, "TOML parse error: {err}"), + ConfigError::TomlSer(err) => write!(f, "TOML serialize error: {err}"), + ConfigError::Invalid(msg) => write!(f, "Invalid config: {msg}"), + } + } +} + +impl std::error::Error for ConfigError {} + +impl From for ConfigError { + fn from(err: std::io::Error) -> Self { + ConfigError::Io(err) + } +} + +impl From for ConfigError { + fn from(err: toml::de::Error) -> Self { + ConfigError::TomlDe(err) + } +} + +impl From for ConfigError { + fn from(err: toml::ser::Error) -> Self { + ConfigError::TomlSer(err) + } +} + +pub struct Config { + path: PathBuf, + data: Value, +} + +impl Config { + pub fn new(path: &str) -> Result { + let path = PathBuf::from(path); + let data = if path.exists() { + read_toml(&path)? + } else { + let default_value = default_toml()?; + write_toml(&path, &default_value)?; + default_value + }; + let cfg = Self { path, data }; + cfg.validate()?; + Ok(cfg) + } + + pub fn open(path: &str) -> Result { + let path = PathBuf::from(path); + if !path.exists() { + return Err(ConfigError::Invalid("dcr.toml not found".into())); + } + let data = read_toml(&path)?; + let cfg = Self { path, data }; + cfg.validate()?; + Ok(cfg) + } + + pub fn get(&self, key: &str) -> Option<&Value> { + let parts: Vec<&str> = key.split('.').collect(); + get_path(&self.data, &parts) + } + + #[allow(dead_code)] + pub fn add(&mut self, key: &str, value: Value) -> Result<(), ConfigError> { + self.set(key, value) + } + + pub fn edit(&mut self, key: &str, value: Value) -> Result<(), ConfigError> { + self.set(key, value) + } + #[allow(dead_code)] + pub fn check(&self) -> bool { + self.validate().is_ok() + } + + pub fn validate(&self) -> Result<(), ConfigError> { + let package = self + .get("package") + .and_then(|v| v.as_table()) + .ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?; + + for key in ["name", "version", "language", "compiler"] { + let value = package.get(key).and_then(|v| v.as_str()).unwrap_or(""); + if value.trim().is_empty() { + return Err(ConfigError::Invalid(format!("package.{key} is empty"))); + } + } + Ok(()) + } + + pub fn save(&self) -> Result<(), ConfigError> { + write_toml(&self.path, &self.data) + } + + fn set(&mut self, key: &str, value: Value) -> Result<(), ConfigError> { + let parts: Vec<&str> = key.split('.').collect(); + set_path(&mut self.data, &parts, value)?; + self.save()?; + Ok(()) + } +} + +fn read_toml(path: &Path) -> Result { + let content = fs::read_to_string(path)?; + Ok(toml::from_str(&content)?) +} + +fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> { + let content = toml::to_string(value)?; + fs::write(path, content)?; + Ok(()) +} + +fn default_toml() -> Result { + let name = std::env::current_dir() + .ok() + .and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string())) + .unwrap_or_else(|| "project".to_string()); + + let mut package = Map::new(); + package.insert("name".to_string(), Value::String(name)); + package.insert( + "version".to_string(), + Value::String(DEFAULT_VERSION.to_string()), + ); + package.insert( + "language".to_string(), + Value::String(DEFAULT_LANGUAGE.to_string()), + ); + package.insert( + "compiler".to_string(), + Value::String(DEFAULT_COMPILER.to_string()), + ); + + let mut root = Map::new(); + root.insert("package".to_string(), Value::Table(package)); + root.insert("dependencies".to_string(), Value::Table(Map::new())); + + Ok(Value::Table(root)) +} + +fn get_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> { + let mut current = value; + for key in path { + current = current.as_table()?.get(*key)?; + } + Some(current) +} + +fn set_path(value: &mut Value, path: &[&str], new_value: Value) -> Result<(), ConfigError> { + let mut current = value + .as_table_mut() + .ok_or_else(|| ConfigError::Invalid("root is not a table".into()))?; + + for key in &path[..path.len().saturating_sub(1)] { + if !current.contains_key(*key) { + current.insert((*key).to_string(), Value::Table(Map::new())); + } + current = current + .get_mut(*key) + .and_then(|v| v.as_table_mut()) + .ok_or_else(|| ConfigError::Invalid(format!("'{key}' is not a table")))?; + } + + if let Some(last) = path.last() { + current.insert((*last).to_string(), new_value); + Ok(()) + } else { + Err(ConfigError::Invalid("empty key".into())) + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..ef68c36 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1 @@ +pub mod config; diff --git a/src/main.rs b/src/main.rs index 9348f4b..6ab1345 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use crate::utils::log::error; mod cli; mod config; +mod core; mod utils; fn main() {