diff --git a/CHANGELOG.md b/CHANGELOG.md index db0afaa..3098ddb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.2.3] - 2026-02-21 +Added: +- Modular builders for `gcc`, `clang`, `msvc` +- Platform-specific binary path generation (Linux/macOS/Windows) + +Changed: +- Build configuration moved to `[build]` (`language`, `standard`, `compiler`) +- `dcr.toml` formatting now includes the `[build]` section +- Build uses built-in `debug/release` flags per compiler +- Build compiles all `*.c/*.cpp` files in `src/` into a single binary (no incremental build) +- Updated `dcr.toml` examples in documentation + ## [0.2.2] - 2026-02-20 Changed: - Reworked `dcr.toml` handling: added read, validation, and edit through `core::config` @@ -14,40 +26,40 @@ Changed: - Translated installer script messages in `install.sh` and `install.ps1` to English ## [0.2.0] - 2026-02-17 -Изменено: -- Проект переведен с Python на Rust -- CLI и команды (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) перенесены на Rust -- Обновлен флаг `--update` - добавлена поддержка GNU\Linux, Windows, MacOS -- Обновлены `README.md`, `CONTRIBUTING.md`, `install.sh` под Rust-реализацию -- Добавлен `install.ps1` для Windows -- Обновлен `install.sh` для GNU/Linux и MacOS +Changed: +- Project migrated from Python to Rust +- CLI and commands (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) ported to Rust +- Updated `--update` flag: added support for GNU/Linux, Windows, macOS +- Updated `README.md`, `CONTRIBUTING.md`, and `install.sh` for the Rust implementation +- Added `install.ps1` for Windows +- Updated `install.sh` for GNU/Linux and macOS -Добавлено: -- Добавлен `install.ps1` для Windows -- Поддержка GNU\Linux, Windows, MacOS(x86_64/arm) +Added: +- Added `install.ps1` for Windows +- Support for GNU/Linux, Windows, macOS (x86_64/arm) -ВАЖНО: -- Код перенесен с помощью нейронных сетей, в будущих версиях будет баг-фикс и изменение логики +Important: +- Code was ported with neural networks; future versions will include bug fixes and logic changes ## [0.1.2] - 2026-02-12 -Добавлено: -- Команда обновления -- Скрипт установки `install.sh` и инструкции по установке в README -- Флаг `--version` для вывода версии +Added: +- Update command +- `install.sh` install script and README instructions +- `--version` flag -Изменено: -- Улучшен консольный вывод команд, добавлены цвета и обновлена справка `--help` -- Обновлен запуск проекта и обработка сборки в `run.py`/`build.py` +Changed: +- Improved CLI output, added colors, and updated `--help` +- Updated project run/build handling in `run.py`/`build.py` ## [0.1.1] - 2026-02-11 -Изменено: -- Обновлена справка `--help` -- `main.py` теперь корректно запускается при прямом выполнении +Changed: +- Updated `--help` +- `main.py` now runs correctly when executed directly ## [0.1.0] - 2026-02-11 -Первый публичный релиз. +First public release. -Добавлено: -- Базовые команды `new`, `init`, `build`, `run`, `clean` -- Профили сборки `debug` и `release` -- Шаблоны `dcr.toml` и `src/main.c` +Added: +- Base commands `new`, `init`, `build`, `run`, `clean` +- Build profiles `debug` and `release` +- `dcr.toml` and `src/main.c` templates diff --git a/Cargo.lock b/Cargo.lock index 14c5b0a..4cd691f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,7 +62,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "dcr" -version = "0.2.2" +version = "0.2.3" dependencies = [ "reqwest", "self-replace", diff --git a/Cargo.toml b/Cargo.toml index 422496b..2aa3c33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dcr" -version = "0.2.2" +version = "0.2.3" edition = "2024" build = "build.rs" diff --git a/README.md b/README.md index 72e704c..6f502f6 100644 --- a/README.md +++ b/README.md @@ -107,15 +107,15 @@ Builds the project. If no profile is specified, `--debug` is used. Builds the project and runs the binary. If no profile is specified, `--debug` is used. Run manually: -`./target//main` +`./target//` ### `dcr clean` Removes the `target` directory in the project root. ## Build Profiles Two profiles are supported: -- `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` -- `--release` - flags: `-O3 -DNDEBUG -march=native` +- `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang) +- `--release` - flags: `-O3 -DNDEBUG -march=native`(Clang) ## Configuration The main project file is `dcr.toml`. @@ -126,7 +126,10 @@ Example `dcr.toml`: [package] name = "hello" version = "0.1.0" + +[build] language = "c" +standard = "c11" compiler = "clang" [dependencies] @@ -134,7 +137,7 @@ compiler = "clang" ## Requirements - Rust toolchain (`rustc`, `cargo`) - for building DCR from source -- C compiler (`clang`, `gcc`, or others) +- C compiler (`clang`, `gcc`, or 'cl'(msvc)) ## Releases Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed. diff --git a/docs/README_ru.md b/docs/README_ru.md index 8b15dc4..26ce904 100644 --- a/docs/README_ru.md +++ b/docs/README_ru.md @@ -107,15 +107,15 @@ hello/ Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`. Запуск вручную: -`./target//main` +`./target//` ### `dcr clean` Удаляет папку `target` в корне проекта. ## Профили сборки Поддерживаются два профиля: -- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` -- `--release` — флаги: `-O3 -DNDEBUG -march=native` +- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang) +- `--release` — флаги: `-O3 -DNDEBUG -march=native`(Clang) ## Конфигурация Основной файл проекта — `dcr.toml`. @@ -126,7 +126,10 @@ hello/ [package] name = "hello" version = "0.1.0" + +[build] language = "c" +standard = "c11" compiler = "clang" [dependencies] @@ -134,7 +137,7 @@ compiler = "clang" ## Требования - Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR -- C-компилятор (`clang`, `gcc` или другие) +- C-компилятор (`clang`, `gcc` или 'cl'(msvc)) ## Релизы Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`. diff --git a/src/cli/build.rs b/src/cli/build.rs index 0fd964c..cf7ddd3 100644 --- a/src/cli/build.rs +++ b/src/cli/build.rs @@ -1,15 +1,13 @@ use crate::config::{PROFILE, flags}; +use crate::core::builder::{BuildContext, build as build_project}; use crate::core::config::Config; use crate::utils::fs::check_dir; use crate::utils::log::{error, warn}; use crate::utils::text::{BOLD_GREEN, colored}; use std::fs; -use std::process::Command; use std::time::Instant; pub fn build(args: &[String]) -> i32 { - let mut active_profile = PROFILE.to_string(); - let items = check_dir(None).unwrap_or_default(); if !items.contains(&"dcr.toml".to_string()) { error("dcr.toml file not found"); @@ -23,73 +21,92 @@ pub fn build(args: &[String]) -> i32 { 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(""); + let active_profile = match parse_profile(args) { + Ok(profile) => profile, + Err(code) => return code, + }; + let project_name = get_config_str(&config, "package.name"); + let project_compiler = get_config_str(&config, "build.compiler"); + let build_language = get_config_str(&config, "build.language"); + let build_standard = get_config_str(&config, "build.standard"); + println!( + " Building project `{}`\n Profile: {}\n Compiler: {}\n", + colored(&project_name, BOLD_GREEN), + colored(&active_profile, BOLD_GREEN), + colored(&project_compiler, BOLD_GREEN) + ); + + ensure_target_dirs(&items, &active_profile); + + let ctx = BuildContext { + profile: &active_profile, + project_name: &project_name, + compiler: &project_compiler, + language: &build_language, + standard: &build_standard, + }; + match run_build(&ctx) { + Ok(times) => { + println!( + " {} Build completed successfully in {} seconds", + colored("✔", BOLD_GREEN), + colored(×.to_string(), BOLD_GREEN) + ); + 0 + } + Err(msg) => { + error(&msg); + 1 + } + } +} + +fn parse_profile(args: &[String]) -> Result { if let Some(first_arg) = args.first() { if first_arg.starts_with("--") { let candidate = first_arg.trim_start_matches("--"); if flags(candidate).is_some() { - active_profile = candidate.to_string(); - } else { - warn("Unknown build flag"); - return 1; + return Ok(candidate.to_string()); } - } else { - warn("Unknown argument"); - return 1; + warn("Unknown build flag"); + return Err(1); } + warn("Unknown argument"); + return Err(1); } + Ok(PROFILE.to_string()) +} - println!( - " Building project `{}`\n Profile: {}\n Compiler: {}\n", - colored(project_name, BOLD_GREEN), - colored(&active_profile, BOLD_GREEN), - colored(project_compiler, BOLD_GREEN) - ); +fn get_config_str(config: &Config, key: &str) -> String { + config + .get(key) + .and_then(|v| v.as_str()) + .unwrap_or("") + .to_string() +} +fn ensure_target_dirs(items: &[String], profile: &str) { if !items.contains(&"target".to_string()) { let _ = fs::create_dir("./target"); } let target_items = check_dir(Some("target")).unwrap_or_default(); - if !target_items.contains(&active_profile) { - let _ = fs::create_dir(format!("./target/{active_profile}")); + if !target_items.contains(&profile.to_string()) { + let _ = fs::create_dir(format!("./target/{profile}")); + } +} + +fn run_build(ctx: &BuildContext) -> Result { + let start_time = Instant::now(); + match build_project(ctx) { + Ok(times) => { + let times = if times == 0.0 { + ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0 + } else { + times + }; + Ok(times) + } + Err(_) => Err("Build failed".to_string()), } - - let src_items = check_dir(Some("src")).unwrap_or_default(); - 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); - cmd.arg("./src/main.c"); - for flag in compile_flags { - cmd.arg(flag); - } - cmd.arg("-o") - .arg(format!("./target/{active_profile}/{project_name}")); - - match cmd.status() { - Ok(status) if status.success() => { - let times = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; - println!( - " {} Build completed successfully in {} seconds", - colored("✔", BOLD_GREEN), - colored(×.to_string(), BOLD_GREEN) - ); - return 0; - } - _ => { - error("Build failed"); - return 1; - } - } - } - - 0 } diff --git a/src/core/builder/clang.rs b/src/core/builder/clang.rs new file mode 100644 index 0000000..3888b35 --- /dev/null +++ b/src/core/builder/clang.rs @@ -0,0 +1,81 @@ +use crate::core::builder::BuildContext; +use crate::platform; +use std::fs; +use std::process::Command; +use std::time::Instant; + +pub fn build(ctx: &BuildContext) -> Result { + let compiler = if ctx.compiler.is_empty() { + "cc" + } else { + ctx.compiler + }; + let start_time = Instant::now(); + let mut cmd = Command::new(compiler); + let sources = collect_sources(ctx.language)?; + for source in &sources { + cmd.arg(source); + } + if !ctx.standard.is_empty() { + cmd.arg(format!("-std={}", ctx.standard)); + } + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + cmd.arg("-o") + .arg(platform::bin_path(ctx.profile, ctx.project_name)); + + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + Ok(elapsed) + } + Ok(_) => Err("Build failed".to_string()), + Err(err) => Err(format!("Build failed: {err}")), + } +} + +fn collect_sources(language: &str) -> Result, String> { + let lang = language.to_lowercase(); + let mut sources = Vec::new(); + let entries = fs::read_dir("./src").map_err(|err| format!("src dir error: {err}"))?; + for entry in entries { + let entry = entry.map_err(|err| format!("src dir error: {err}"))?; + let path = entry.path(); + if !path.is_file() { + continue; + } + let ext = path + .extension() + .and_then(|v| v.to_str()) + .unwrap_or("") + .to_lowercase(); + let file = path.to_string_lossy().to_string(); + let allowed = (lang == "c" && ext == "c") + || ((lang == "c++" || lang == "cpp" || lang == "cxx") + && (ext == "cpp" || ext == "cxx" || ext == "cc")); + if allowed { + sources.push(file); + } + } + sources.sort(); + if sources.is_empty() { + return Err("No source files found in ./src".to_string()); + } + Ok(sources) +} + +fn default_flags(profile: &str) -> &'static [&'static str] { + match profile { + "release" => &["-O3", "-DNDEBUG", "-march=native"], + "debug" => &[ + "-O0", + "-g", + "-Wall", + "-Wextra", + "-fno-omit-frame-pointer", + "-DDEBUG", + ], + _ => &[], + } +} diff --git a/src/core/builder/gcc.rs b/src/core/builder/gcc.rs new file mode 100644 index 0000000..3888b35 --- /dev/null +++ b/src/core/builder/gcc.rs @@ -0,0 +1,81 @@ +use crate::core::builder::BuildContext; +use crate::platform; +use std::fs; +use std::process::Command; +use std::time::Instant; + +pub fn build(ctx: &BuildContext) -> Result { + let compiler = if ctx.compiler.is_empty() { + "cc" + } else { + ctx.compiler + }; + let start_time = Instant::now(); + let mut cmd = Command::new(compiler); + let sources = collect_sources(ctx.language)?; + for source in &sources { + cmd.arg(source); + } + if !ctx.standard.is_empty() { + cmd.arg(format!("-std={}", ctx.standard)); + } + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + cmd.arg("-o") + .arg(platform::bin_path(ctx.profile, ctx.project_name)); + + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + Ok(elapsed) + } + Ok(_) => Err("Build failed".to_string()), + Err(err) => Err(format!("Build failed: {err}")), + } +} + +fn collect_sources(language: &str) -> Result, String> { + let lang = language.to_lowercase(); + let mut sources = Vec::new(); + let entries = fs::read_dir("./src").map_err(|err| format!("src dir error: {err}"))?; + for entry in entries { + let entry = entry.map_err(|err| format!("src dir error: {err}"))?; + let path = entry.path(); + if !path.is_file() { + continue; + } + let ext = path + .extension() + .and_then(|v| v.to_str()) + .unwrap_or("") + .to_lowercase(); + let file = path.to_string_lossy().to_string(); + let allowed = (lang == "c" && ext == "c") + || ((lang == "c++" || lang == "cpp" || lang == "cxx") + && (ext == "cpp" || ext == "cxx" || ext == "cc")); + if allowed { + sources.push(file); + } + } + sources.sort(); + if sources.is_empty() { + return Err("No source files found in ./src".to_string()); + } + Ok(sources) +} + +fn default_flags(profile: &str) -> &'static [&'static str] { + match profile { + "release" => &["-O3", "-DNDEBUG", "-march=native"], + "debug" => &[ + "-O0", + "-g", + "-Wall", + "-Wextra", + "-fno-omit-frame-pointer", + "-DDEBUG", + ], + _ => &[], + } +} diff --git a/src/core/builder/mod.rs b/src/core/builder/mod.rs new file mode 100644 index 0000000..5d275c0 --- /dev/null +++ b/src/core/builder/mod.rs @@ -0,0 +1,28 @@ +pub mod clang; +pub mod gcc; +pub mod msvc; + +pub struct BuildContext<'a> { + pub profile: &'a str, + pub project_name: &'a str, + pub compiler: &'a str, + pub language: &'a str, + pub standard: &'a str, +} + +pub fn build(ctx: &BuildContext) -> Result { + let compiler = ctx.compiler.to_lowercase(); + if compiler.contains("clang-cl") { + return msvc::build(ctx); + } + if compiler.contains("gcc") || compiler.contains("g++") { + return gcc::build(ctx); + } + if compiler.contains("clang") || compiler.contains("clang++") { + return clang::build(ctx); + } + if compiler == "cl" || compiler.contains("msvc") { + return msvc::build(ctx); + } + clang::build(ctx) +} diff --git a/src/core/builder/msvc.rs b/src/core/builder/msvc.rs new file mode 100644 index 0000000..9162b8e --- /dev/null +++ b/src/core/builder/msvc.rs @@ -0,0 +1,116 @@ +use crate::core::builder::BuildContext; +use crate::platform; +use std::fs; +use std::process::Command; +use std::time::Instant; + +pub fn build(ctx: &BuildContext) -> Result { + let compiler = if ctx.compiler.is_empty() { + "cl" + } else { + ctx.compiler + }; + let start_time = Instant::now(); + let mut cmd = Command::new(compiler); + let sources = collect_sources(ctx.language)?; + + cmd.arg("/nologo"); + match ctx.language.to_lowercase().as_str() { + "c" => { + cmd.arg("/TC"); + } + "c++" | "cpp" | "cxx" => { + cmd.arg("/TP"); + } + _ => { + return Err("Unsupported language".to_string()); + } + } + + if !ctx.standard.is_empty() { + let std_flag = msvc_standard_flag(ctx.language, ctx.standard)?; + cmd.arg(std_flag); + } + + for flag in default_flags(ctx.profile) { + cmd.arg(flag); + } + + for source in &sources { + cmd.arg(source); + } + cmd.arg(format!( + "/Fe:{}", + platform::bin_path(ctx.profile, ctx.project_name) + )); + + match cmd.status() { + Ok(status) if status.success() => { + let elapsed = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0; + Ok(elapsed) + } + Ok(_) => Err("Build failed".to_string()), + Err(err) => Err(format!("Build failed: {err}")), + } +} + +fn collect_sources(language: &str) -> Result, String> { + let lang = language.to_lowercase(); + let mut sources = Vec::new(); + let entries = fs::read_dir("./src").map_err(|err| format!("src dir error: {err}"))?; + for entry in entries { + let entry = entry.map_err(|err| format!("src dir error: {err}"))?; + let path = entry.path(); + if !path.is_file() { + continue; + } + let ext = path + .extension() + .and_then(|v| v.to_str()) + .unwrap_or("") + .to_lowercase(); + let file = path.to_string_lossy().to_string(); + let allowed = (lang == "c" && ext == "c") + || ((lang == "c++" || lang == "cpp" || lang == "cxx") + && (ext == "cpp" || ext == "cxx" || ext == "cc")); + if allowed { + sources.push(file); + } + } + sources.sort(); + if sources.is_empty() { + return Err("No source files found in ./src".to_string()); + } + Ok(sources) +} + +fn msvc_standard_flag(language: &str, standard: &str) -> Result { + let lang = language.to_lowercase(); + let std = standard.to_lowercase(); + if lang == "c" { + return match std.as_str() { + "c11" => Ok("/std:c11".to_string()), + "c17" => Ok("/std:c17".to_string()), + _ => Err("Unsupported C standard for MSVC".to_string()), + }; + } + if lang == "c++" || lang == "cpp" || lang == "cxx" { + return match std.as_str() { + "c++11" => Ok("/std:c++11".to_string()), + "c++14" => Ok("/std:c++14".to_string()), + "c++17" => Ok("/std:c++17".to_string()), + "c++20" => Ok("/std:c++20".to_string()), + "c++23" => Ok("/std:c++latest".to_string()), + _ => Err("Unsupported C++ standard for MSVC".to_string()), + }; + } + Err("Unsupported language".to_string()) +} + +fn default_flags(profile: &str) -> &'static [&'static str] { + match profile { + "release" => &["/O2", "/DNDEBUG"], + "debug" => &["/Od", "/Zi", "/W4", "/DDEBUG", "/Oy-"], + _ => &[], + } +} diff --git a/src/core/config.rs b/src/core/config.rs index bae951f..cf732f5 100644 --- a/src/core/config.rs +++ b/src/core/config.rs @@ -5,6 +5,7 @@ use toml::map::Map; const DEFAULT_VERSION: &str = "0.1.0"; const DEFAULT_LANGUAGE: &str = "c"; +const DEFAULT_STANDARD: &str = "c11"; const DEFAULT_COMPILER: &str = "clang"; #[derive(Debug)] @@ -101,12 +102,24 @@ impl Config { .and_then(|v| v.as_table()) .ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?; - for key in ["name", "version", "language", "compiler"] { + for key in ["name", "version"] { 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"))); } } + + let build = self + .get("build") + .and_then(|v| v.as_table()) + .ok_or_else(|| ConfigError::Invalid("missing [build]".into()))?; + + for key in ["language", "standard", "compiler"] { + let value = build.get(key).and_then(|v| v.as_str()).unwrap_or(""); + if value.trim().is_empty() { + return Err(ConfigError::Invalid(format!("build.{key} is empty"))); + } + } Ok(()) } @@ -128,7 +141,7 @@ fn read_toml(path: &Path) -> Result { } fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> { - let content = toml::to_string(value)?; + let content = format_toml(value)?; fs::write(path, content)?; Ok(()) } @@ -145,22 +158,78 @@ fn default_toml() -> Result { "version".to_string(), Value::String(DEFAULT_VERSION.to_string()), ); - package.insert( + let mut build = Map::new(); + build.insert( "language".to_string(), Value::String(DEFAULT_LANGUAGE.to_string()), ); - package.insert( + build.insert( + "standard".to_string(), + Value::String(DEFAULT_STANDARD.to_string()), + ); + build.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("build".to_string(), Value::Table(build)); root.insert("dependencies".to_string(), Value::Table(Map::new())); Ok(Value::Table(root)) } +fn format_toml(value: &Value) -> Result { + let root = value + .as_table() + .ok_or_else(|| ConfigError::Invalid("root is not a table".into()))?; + + let package = root + .get("package") + .and_then(|v| v.as_table()) + .ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?; + let build = root + .get("build") + .and_then(|v| v.as_table()) + .ok_or_else(|| ConfigError::Invalid("missing [build]".into()))?; + let deps = root + .get("dependencies") + .and_then(|v| v.as_table()) + .ok_or_else(|| ConfigError::Invalid("missing [dependencies]".into()))?; + + let name = package.get("name").and_then(|v| v.as_str()).unwrap_or(""); + let version = package + .get("version") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let language = build.get("language").and_then(|v| v.as_str()).unwrap_or(""); + let standard = build.get("standard").and_then(|v| v.as_str()).unwrap_or(""); + let compiler = build.get("compiler").and_then(|v| v.as_str()).unwrap_or(""); + + let mut out = String::new(); + out.push_str("[package]\n"); + out.push_str(&format!("name = \"{name}\"\n")); + out.push_str(&format!("version = \"{version}\"\n\n")); + + out.push_str("[build]\n"); + out.push_str(&format!("language = \"{language}\"\n")); + out.push_str(&format!("standard = \"{standard}\"\n")); + out.push_str(&format!("compiler = \"{compiler}\"\n\n")); + + out.push_str("[dependencies]\n"); + if !deps.is_empty() { + let mut keys: Vec<&String> = deps.keys().collect(); + keys.sort(); + for key in keys { + let val = deps.get(key).and_then(|v| v.as_str()).unwrap_or(""); + out.push_str(&format!("{key} = \"{val}\"\n")); + } + } + Ok(out) +} + fn get_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> { let mut current = value; for key in path { diff --git a/src/core/mod.rs b/src/core/mod.rs index ef68c36..5dc0a79 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1 +1,2 @@ +pub mod builder; pub mod config; diff --git a/src/main.rs b/src/main.rs index 6ab1345..f07057f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use crate::utils::log::error; mod cli; mod config; mod core; +mod platform; mod utils; fn main() { diff --git a/src/platform/linux.rs b/src/platform/linux.rs new file mode 100644 index 0000000..3746b47 --- /dev/null +++ b/src/platform/linux.rs @@ -0,0 +1,3 @@ +pub fn bin_path(profile: &str, name: &str) -> String { + format!("./target/{profile}/{name}") +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs new file mode 100644 index 0000000..3746b47 --- /dev/null +++ b/src/platform/macos.rs @@ -0,0 +1,3 @@ +pub fn bin_path(profile: &str, name: &str) -> String { + format!("./target/{profile}/{name}") +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..839b760 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,25 @@ +#[cfg(target_os = "linux")] +pub mod linux; +#[cfg(target_os = "macos")] +pub mod macos; +#[cfg(target_os = "windows")] +pub mod windows; + +pub fn bin_path(profile: &str, name: &str) -> String { + #[cfg(target_os = "linux")] + { + linux::bin_path(profile, name) + } + #[cfg(target_os = "macos")] + { + return macos::bin_path(profile, name); + } + #[cfg(target_os = "windows")] + { + return windows::bin_path(profile, name); + } + #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] + { + format!("./target/{profile}/{name}") + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs new file mode 100644 index 0000000..c6e8eb0 --- /dev/null +++ b/src/platform/windows.rs @@ -0,0 +1,3 @@ +pub fn bin_path(profile: &str, name: &str) -> String { + format!("./target/{profile}/{name}.exe") +}