- 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
This commit is contained in:
2026-02-21 16:12:20 +08:00
parent e87e0cbc47
commit 0f48151b68
17 changed files with 544 additions and 98 deletions

View File

@@ -1,5 +1,17 @@
# Changelog # 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 ## [0.2.2] - 2026-02-20
Changed: Changed:
- Reworked `dcr.toml` handling: added read, validation, and edit through `core::config` - 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 - Translated installer script messages in `install.sh` and `install.ps1` to English
## [0.2.0] - 2026-02-17 ## [0.2.0] - 2026-02-17
Изменено: Changed:
- Проект переведен с Python на Rust - Project migrated from Python to Rust
- CLI и команды (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) перенесены на Rust - CLI and commands (`new`, `init`, `build`, `run`, `clean`, `--help`, `--version`, `--update`) ported to Rust
- Обновлен флаг `--update` - добавлена поддержка GNU\Linux, Windows, MacOS - Updated `--update` flag: added support for GNU/Linux, Windows, macOS
- Обновлены `README.md`, `CONTRIBUTING.md`, `install.sh` под Rust-реализацию - Updated `README.md`, `CONTRIBUTING.md`, and `install.sh` for the Rust implementation
- Добавлен `install.ps1` для Windows - Added `install.ps1` for Windows
- Обновлен `install.sh` для GNU/Linux и MacOS - Updated `install.sh` for GNU/Linux and macOS
Добавлено: Added:
- Добавлен `install.ps1` для Windows - Added `install.ps1` for Windows
- Поддержка GNU\Linux, Windows, MacOS(x86_64/arm) - 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 ## [0.1.2] - 2026-02-12
Добавлено: Added:
- Команда обновления - Update command
- Скрипт установки `install.sh` и инструкции по установке в README - `install.sh` install script and README instructions
- Флаг `--version` для вывода версии - `--version` flag
Изменено: Changed:
- Улучшен консольный вывод команд, добавлены цвета и обновлена справка `--help` - Improved CLI output, added colors, and updated `--help`
- Обновлен запуск проекта и обработка сборки в `run.py`/`build.py` - Updated project run/build handling in `run.py`/`build.py`
## [0.1.1] - 2026-02-11 ## [0.1.1] - 2026-02-11
Изменено: Changed:
- Обновлена справка `--help` - Updated `--help`
- `main.py` теперь корректно запускается при прямом выполнении - `main.py` now runs correctly when executed directly
## [0.1.0] - 2026-02-11 ## [0.1.0] - 2026-02-11
Первый публичный релиз. First public release.
Добавлено: Added:
- Базовые команды `new`, `init`, `build`, `run`, `clean` - Base commands `new`, `init`, `build`, `run`, `clean`
- Профили сборки `debug` и `release` - Build profiles `debug` and `release`
- Шаблоны `dcr.toml` и `src/main.c` - `dcr.toml` and `src/main.c` templates

2
Cargo.lock generated
View File

@@ -62,7 +62,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "dcr" name = "dcr"
version = "0.2.2" version = "0.2.3"
dependencies = [ dependencies = [
"reqwest", "reqwest",
"self-replace", "self-replace",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dcr" name = "dcr"
version = "0.2.2" version = "0.2.3"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View File

@@ -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. Builds the project and runs the binary. If no profile is specified, `--debug` is used.
Run manually: Run manually:
`./target/<profile>/main` `./target/<profile>/<name>`
### `dcr clean` ### `dcr clean`
Removes the `target` directory in the project root. Removes the `target` directory in the project root.
## Build Profiles ## Build Profiles
Two profiles are supported: Two profiles are supported:
- `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` - `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang)
- `--release` - flags: `-O3 -DNDEBUG -march=native` - `--release` - flags: `-O3 -DNDEBUG -march=native`(Clang)
## Configuration ## Configuration
The main project file is `dcr.toml`. The main project file is `dcr.toml`.
@@ -126,7 +126,10 @@ Example `dcr.toml`:
[package] [package]
name = "hello" name = "hello"
version = "0.1.0" version = "0.1.0"
[build]
language = "c" language = "c"
standard = "c11"
compiler = "clang" compiler = "clang"
[dependencies] [dependencies]
@@ -134,7 +137,7 @@ compiler = "clang"
## Requirements ## Requirements
- Rust toolchain (`rustc`, `cargo`) - for building DCR from source - Rust toolchain (`rustc`, `cargo`) - for building DCR from source
- C compiler (`clang`, `gcc`, or others) - C compiler (`clang`, `gcc`, or 'cl'(msvc))
## Releases ## Releases
Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed. Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.

View File

@@ -107,15 +107,15 @@ hello/
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`. Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`.
Запуск вручную: Запуск вручную:
`./target/<profile>/main` `./target/<profile>/<name>`
### `dcr clean` ### `dcr clean`
Удаляет папку `target` в корне проекта. Удаляет папку `target` в корне проекта.
## Профили сборки ## Профили сборки
Поддерживаются два профиля: Поддерживаются два профиля:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` - `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang)
- `--release` — флаги: `-O3 -DNDEBUG -march=native` - `--release` — флаги: `-O3 -DNDEBUG -march=native`(Clang)
## Конфигурация ## Конфигурация
Основной файл проекта — `dcr.toml`. Основной файл проекта — `dcr.toml`.
@@ -126,7 +126,10 @@ hello/
[package] [package]
name = "hello" name = "hello"
version = "0.1.0" version = "0.1.0"
[build]
language = "c" language = "c"
standard = "c11"
compiler = "clang" compiler = "clang"
[dependencies] [dependencies]
@@ -134,7 +137,7 @@ compiler = "clang"
## Требования ## Требования
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR - Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
- C-компилятор (`clang`, `gcc` или другие) - C-компилятор (`clang`, `gcc` или 'cl'(msvc))
## Релизы ## Релизы
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`. Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.

View File

@@ -1,15 +1,13 @@
use crate::config::{PROFILE, flags}; use crate::config::{PROFILE, flags};
use crate::core::builder::{BuildContext, build as build_project};
use crate::core::config::Config; use crate::core::config::Config;
use crate::utils::fs::check_dir; use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored}; use crate::utils::text::{BOLD_GREEN, colored};
use std::fs; use std::fs;
use std::process::Command;
use std::time::Instant; use std::time::Instant;
pub fn build(args: &[String]) -> i32 { pub fn build(args: &[String]) -> i32 {
let mut active_profile = PROFILE.to_string();
let items = check_dir(None).unwrap_or_default(); let items = check_dir(None).unwrap_or_default();
if !items.contains(&"dcr.toml".to_string()) { if !items.contains(&"dcr.toml".to_string()) {
error("dcr.toml file not found"); error("dcr.toml file not found");
@@ -23,73 +21,92 @@ pub fn build(args: &[String]) -> i32 {
return 1; return 1;
} }
}; };
let project_compiler: &str = config let active_profile = match parse_profile(args) {
.get("package.compiler") Ok(profile) => profile,
.and_then(|v| v.as_str()) Err(code) => return code,
.unwrap_or(""); };
let project_name: &str = config let project_name = get_config_str(&config, "package.name");
.get("package.name") let project_compiler = get_config_str(&config, "build.compiler");
.and_then(|v| v.as_str()) let build_language = get_config_str(&config, "build.language");
.unwrap_or(""); let build_standard = get_config_str(&config, "build.standard");
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;
}
} else {
warn("Unknown argument");
return 1;
}
}
println!( println!(
" Building project `{}`\n Profile: {}\n Compiler: {}\n", " Building project `{}`\n Profile: {}\n Compiler: {}\n",
colored(project_name, BOLD_GREEN), colored(&project_name, BOLD_GREEN),
colored(&active_profile, BOLD_GREEN), colored(&active_profile, BOLD_GREEN),
colored(project_compiler, BOLD_GREEN) colored(&project_compiler, BOLD_GREEN)
); );
if !items.contains(&"target".to_string()) { ensure_target_dirs(&items, &active_profile);
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}"));
}
let src_items = check_dir(Some("src")).unwrap_or_default(); let ctx = BuildContext {
if src_items.contains(&"main.c".to_string()) { profile: &active_profile,
let compile_flags = flags(&active_profile).unwrap_or(&[]); project_name: &project_name,
let start_time = Instant::now(); compiler: &project_compiler,
let mut cmd = Command::new(project_compiler); language: &build_language,
cmd.arg("./src/main.c"); standard: &build_standard,
for flag in compile_flags { };
cmd.arg(flag); match run_build(&ctx) {
} Ok(times) => {
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!( println!(
" {} Build completed successfully in {} seconds", " {} Build completed successfully in {} seconds",
colored("✔", BOLD_GREEN), colored("✔", BOLD_GREEN),
colored(&times.to_string(), BOLD_GREEN) colored(&times.to_string(), BOLD_GREEN)
); );
return 0; 0
} }
_ => { Err(msg) => {
error("Build failed"); error(&msg);
return 1; 1
} }
} }
} }
0 fn parse_profile(args: &[String]) -> Result<String, i32> {
if let Some(first_arg) = args.first() {
if first_arg.starts_with("--") {
let candidate = first_arg.trim_start_matches("--");
if flags(candidate).is_some() {
return Ok(candidate.to_string());
}
warn("Unknown build flag");
return Err(1);
}
warn("Unknown argument");
return Err(1);
}
Ok(PROFILE.to_string())
}
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(&profile.to_string()) {
let _ = fs::create_dir(format!("./target/{profile}"));
}
}
fn run_build(ctx: &BuildContext) -> Result<f64, String> {
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()),
}
} }

81
src/core/builder/clang.rs Normal file
View File

@@ -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<f64, String> {
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<Vec<String>, 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",
],
_ => &[],
}
}

81
src/core/builder/gcc.rs Normal file
View File

@@ -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<f64, String> {
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<Vec<String>, 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",
],
_ => &[],
}
}

28
src/core/builder/mod.rs Normal file
View File

@@ -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<f64, String> {
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)
}

116
src/core/builder/msvc.rs Normal file
View File

@@ -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<f64, String> {
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<Vec<String>, 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<String, String> {
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-"],
_ => &[],
}
}

View File

@@ -5,6 +5,7 @@ use toml::map::Map;
const DEFAULT_VERSION: &str = "0.1.0"; const DEFAULT_VERSION: &str = "0.1.0";
const DEFAULT_LANGUAGE: &str = "c"; const DEFAULT_LANGUAGE: &str = "c";
const DEFAULT_STANDARD: &str = "c11";
const DEFAULT_COMPILER: &str = "clang"; const DEFAULT_COMPILER: &str = "clang";
#[derive(Debug)] #[derive(Debug)]
@@ -101,12 +102,24 @@ impl Config {
.and_then(|v| v.as_table()) .and_then(|v| v.as_table())
.ok_or_else(|| ConfigError::Invalid("missing [package]".into()))?; .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(""); let value = package.get(key).and_then(|v| v.as_str()).unwrap_or("");
if value.trim().is_empty() { if value.trim().is_empty() {
return Err(ConfigError::Invalid(format!("package.{key} 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(()) Ok(())
} }
@@ -128,7 +141,7 @@ fn read_toml(path: &Path) -> Result<Value, ConfigError> {
} }
fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> { fn write_toml(path: &Path, value: &Value) -> Result<(), ConfigError> {
let content = toml::to_string(value)?; let content = format_toml(value)?;
fs::write(path, content)?; fs::write(path, content)?;
Ok(()) Ok(())
} }
@@ -145,22 +158,78 @@ fn default_toml() -> Result<Value, ConfigError> {
"version".to_string(), "version".to_string(),
Value::String(DEFAULT_VERSION.to_string()), Value::String(DEFAULT_VERSION.to_string()),
); );
package.insert( let mut build = Map::new();
build.insert(
"language".to_string(), "language".to_string(),
Value::String(DEFAULT_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(), "compiler".to_string(),
Value::String(DEFAULT_COMPILER.to_string()), Value::String(DEFAULT_COMPILER.to_string()),
); );
let mut root = Map::new(); let mut root = Map::new();
root.insert("package".to_string(), Value::Table(package)); 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())); root.insert("dependencies".to_string(), Value::Table(Map::new()));
Ok(Value::Table(root)) Ok(Value::Table(root))
} }
fn format_toml(value: &Value) -> Result<String, ConfigError> {
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> { fn get_path<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> {
let mut current = value; let mut current = value;
for key in path { for key in path {

View File

@@ -1 +1,2 @@
pub mod builder;
pub mod config; pub mod config;

View File

@@ -2,6 +2,7 @@ use crate::utils::log::error;
mod cli; mod cli;
mod config; mod config;
mod core; mod core;
mod platform;
mod utils; mod utils;
fn main() { fn main() {

3
src/platform/linux.rs Normal file
View File

@@ -0,0 +1,3 @@
pub fn bin_path(profile: &str, name: &str) -> String {
format!("./target/{profile}/{name}")
}

3
src/platform/macos.rs Normal file
View File

@@ -0,0 +1,3 @@
pub fn bin_path(profile: &str, name: &str) -> String {
format!("./target/{profile}/{name}")
}

25
src/platform/mod.rs Normal file
View File

@@ -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}")
}
}

3
src/platform/windows.rs Normal file
View File

@@ -0,0 +1,3 @@
pub fn bin_path(profile: &str, name: &str) -> String {
format!("./target/{profile}/{name}.exe")
}