6 Commits

Author SHA1 Message Date
9ae4bb5ab1 update release.yml and CHANGELOG.md 2026-02-20 18:09:16 +08:00
5a2025fb80 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
2026-02-20 17:54:01 +08:00
06e7af6ab6 update README.md, --update and aur 2026-02-19 14:50:16 +08:00
8495441b1a Update pkgrel 1 to 2 2026-02-19 13:14:03 +08:00
b56f502a30 fix aur 2026-02-19 13:02:01 +08:00
115692253e add AUR publish support 2026-02-18 23:16:02 +08:00
19 changed files with 581 additions and 62 deletions

125
.github/workflows/aur.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: AUR Publish
on:
push:
tags:
- "v*"
permissions:
contents: read
jobs:
publish-aur:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive version and checksum
id: meta
shell: bash
run: |
set -euo pipefail
PKGVER="${GITHUB_REF_NAME#v}"
SRC_URL="https://github.com/dexoron/dcr/archive/refs/tags/v${PKGVER}.tar.gz"
SHA256="$(curl -fsSL "$SRC_URL" | sha256sum | awk '{print $1}')"
echo "pkgver=$PKGVER" >> "$GITHUB_OUTPUT"
echo "source_url=$SRC_URL" >> "$GITHUB_OUTPUT"
echo "sha256=$SHA256" >> "$GITHUB_OUTPUT"
- name: Render AUR metadata
shell: bash
run: |
set -euo pipefail
PKGVER='${{ steps.meta.outputs.pkgver }}'
SRC_URL='${{ steps.meta.outputs.source_url }}'
SHA256='${{ steps.meta.outputs.sha256 }}'
cat > packaging/aur/dcr/PKGBUILD <<PKG
pkgname=dcr
pkgver=${PKGVER}
pkgrel=1
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64' 'aarch64')
url="https://github.com/dexoron/dcr"
license=('MIT')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
optdepends=(
'gcc: build C/C++ projects with GCC'
'clang: build C/C++ projects with Clang'
)
source=("\$pkgname-\$pkgver.tar.gz::\$url/archive/refs/tags/v\$pkgver.tar.gz")
sha256sums=('${SHA256}')
build() {
cd "\$srcdir/\$pkgname-\$pkgver"
export CARGO_PROFILE_RELEASE_LTO=false
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
cargo build --release --locked
}
package() {
cd "\$srcdir/\$pkgname-\$pkgver"
install -Dm755 "target/release/dcr" "\$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "\$pkgdir/usr/share/licenses/\$pkgname/LICENSE"
}
PKG
cat > packaging/aur/dcr/.SRCINFO <<SRC
pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = ${PKGVER}
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
arch = aarch64
license = MIT
makedepends = cargo
depends = gcc-libs
optdepends = gcc: build C/C++ projects with GCC
optdepends = clang: build C/C++ projects with Clang
options = !lto
source = dcr-${PKGVER}.tar.gz::${SRC_URL}
sha256sums = ${SHA256}
pkgname = dcr
SRC
- name: Push to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
AUR_PACKAGE: dcr
shell: bash
run: |
set -euo pipefail
if [[ -z "${AUR_SSH_PRIVATE_KEY:-}" ]]; then
echo "AUR_SSH_PRIVATE_KEY secret is missing"
exit 1
fi
install -d -m 700 ~/.ssh
printf '%s\n' "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
export GIT_SSH_COMMAND='ssh -i ~/.ssh/aur -o IdentitiesOnly=yes'
git clone "ssh://aur@aur.archlinux.org/${AUR_PACKAGE}.git" aur-repo
cp packaging/aur/dcr/PKGBUILD aur-repo/PKGBUILD
cp packaging/aur/dcr/.SRCINFO aur-repo/.SRCINFO
cd aur-repo
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet; then
echo "No AUR changes"
exit 0
fi
git add PKGBUILD .SRCINFO
git commit -m "dcr ${{ steps.meta.outputs.pkgver }}"
git push

View File

@@ -17,10 +17,10 @@ jobs:
- os: ubuntu-latest - os: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
asset_name: dcr-x86_64-unknown-linux-gnu asset_name: dcr-x86_64-unknown-linux-gnu
- os: macos-13 - os: macos-15-intel
target: x86_64-apple-darwin target: x86_64-apple-darwin
asset_name: dcr-x86_64-apple-darwin asset_name: dcr-x86_64-apple-darwin
- os: macos-14 - os: macos-15
target: aarch64-apple-darwin target: aarch64-apple-darwin
asset_name: dcr-aarch64-apple-darwin asset_name: dcr-aarch64-apple-darwin
- os: windows-latest - os: windows-latest
@@ -28,7 +28,6 @@ jobs:
asset_name: dcr-x86_64-pc-windows-msvc.exe asset_name: dcr-x86_64-pc-windows-msvc.exe
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## [0.2.2] - 2026-02-20
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
## [0.2.1] - 2026-02-18 ## [0.2.1] - 2026-02-18
Changed: Changed:
- Translated user-facing CLI messages to English - Translated user-facing CLI messages to English

57
Cargo.lock generated
View File

@@ -62,11 +62,12 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "dcr" name = "dcr"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"reqwest", "reqwest",
"self-replace", "self-replace",
"serde", "serde",
"toml",
] ]
[[package]] [[package]]
@@ -878,6 +879,15 @@ dependencies = [
"zmij", "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]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -1043,6 +1053,45 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tower" name = "tower"
version = "0.5.3" version = "0.5.3"
@@ -1466,6 +1515,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.51.0" version = "0.51.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dcr" name = "dcr"
version = "0.2.1" version = "0.2.2"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"
@@ -8,3 +8,4 @@ build = "build.rs"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
self-replace = "1" self-replace = "1"
toml = "1.0.3"

View File

@@ -15,7 +15,7 @@ The current implementation is written in Rust.
- Run the compiled binary - Run the compiled binary
- Clean build artifacts - Clean build artifacts
- Generate a minimal C project template - Generate a minimal C project template
- Update the binary via `dcr --update` (GitHub Releases) - Update the binary via `dcr --update` (GitHub Releases, not for pacman/AUR installs)
## Supported Platforms ## Supported Platforms
- Linux: `x86_64-unknown-linux-gnu` - Linux: `x86_64-unknown-linux-gnu`
@@ -25,6 +25,13 @@ The current implementation is written in Rust.
## Installation ## Installation
### Package Manager
**Arch Linux**
```sh
yay -S dcr # or paru and other AUR package managers
```
### From Source ### From Source
```sh ```sh
@@ -57,6 +64,14 @@ Release assets:
- `dcr-aarch64-apple-darwin` - `dcr-aarch64-apple-darwin`
- `dcr-x86_64-pc-windows-msvc.exe` - `dcr-x86_64-pc-windows-msvc.exe`
## Update
- If DCR was installed from GitHub release assets, `install.sh`, `install.ps1`, or built manually:
- use `dcr --update`
- If DCR is installed via `pacman/AUR`:
- update with your package manager: `paru/yay -Syu dcr` or `sudo pacman -Syu dcr`
- `dcr --update` detects package-managed installs and asks you to update via package manager
## Quick Start ## Quick Start
Create a new project: Create a new project:

View File

@@ -15,7 +15,7 @@ DCR — это утилита для управления проектами н
- Запуск собранного бинарника - Запуск собранного бинарника
- Очистка результатов сборки - Очистка результатов сборки
- Генерация минимального шаблона C-проекта - Генерация минимального шаблона C-проекта
- Обновление бинарника через `dcr --update` (GitHub Releases) - Обновление бинарника через `dcr --update` (GitHub Releases, не для pacman-установок)
## Поддерживаемые платформы ## Поддерживаемые платформы
- Linux: `x86_64-unknown-linux-gnu` - Linux: `x86_64-unknown-linux-gnu`
@@ -25,6 +25,13 @@ DCR — это утилита для управления проектами н
## Установка ## Установка
### Пакетный менеджер
**ArchLinux**
```sh
yay -S dcr # или paru и остальные менеджеры пакетов AUR
```
### Из исходников ### Из исходников
```sh ```sh
@@ -57,6 +64,14 @@ irm https://dcr.dexoron.su/install.ps1 | iex
- `dcr-aarch64-apple-darwin` - `dcr-aarch64-apple-darwin`
- `dcr-x86_64-pc-windows-msvc.exe` - `dcr-x86_64-pc-windows-msvc.exe`
## Обновление
- Если DCR установлен из GitHub Releases, через `install.sh`, `install.ps1` или собран вручную:
- используйте `dcr --update`
- Если DCR установлен через `pacman/AUR`:
- обновляйте через пакетный менеджер: `paru/yay -Syu dcr` или `sudo pacman -Syu dcr`
- `dcr --update` определяет пакетную установку и подсказывает обновляться через пакетный менеджер
## Быстрый старт ## Быстрый старт
Создать новый проект: Создать новый проект:

21
packaging/aur/README.md Normal file
View File

@@ -0,0 +1,21 @@
# AUR publishing
This repository includes an automated AUR publish workflow:
- package files: `packaging/aur/dcr/PKGBUILD`, `packaging/aur/dcr/.SRCINFO`
- workflow: `.github/workflows/aur.yml`
## One-time setup
1. Create the `dcr` package on AUR and initialize its git repository.
2. Create an SSH key pair for CI.
3. Add the public key to your AUR account.
4. Add the private key to GitHub Actions secret `AUR_SSH_PRIVATE_KEY`.
## Release flow
1. Bump project version in `Cargo.toml`.
2. Push tag `v<version>` (example: `v0.2.2`).
3. GitHub Actions will:
- compute source tarball SHA256
- render `PKGBUILD` and `.SRCINFO`
- push updates to `ssh://aur@aur.archlinux.org/dcr.git`

View File

@@ -0,0 +1,17 @@
pkgbase = dcr
pkgdesc = Cargo-like utility to manage C/C++ projects
pkgver = 0.2.1
pkgrel = 1
url = https://github.com/dexoron/dcr
arch = x86_64
arch = aarch64
license = MIT
makedepends = cargo
depends = gcc-libs
optdepends = gcc: build C/C++ projects with GCC
optdepends = clang: build C/C++ projects with Clang
options = !lto
source = dcr-0.2.1.tar.gz::https://github.com/dexoron/dcr/archive/refs/tags/v0.2.1.tar.gz
sha256sums = 30e334fa98cd51073c0fa84c80a982584b00156b57084311ad53419441ce6235
pkgname = dcr

View File

@@ -0,0 +1,29 @@
pkgname=dcr
pkgver=0.2.1
pkgrel=3
pkgdesc="Cargo-like utility to manage C/C++ projects"
arch=('x86_64' 'aarch64')
url="https://github.com/dexoron/dcr"
license=('MIT')
depends=('gcc-libs')
makedepends=('cargo')
options=('!lto')
optdepends=(
'gcc: build C/C++ projects with GCC'
'clang: build C/C++ projects with Clang'
)
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('30e334fa98cd51073c0fa84c80a982584b00156b57084311ad53419441ce6235')
build() {
cd "$srcdir/$pkgname-$pkgver"
export CARGO_PROFILE_RELEASE_LTO=false
export RUSTFLAGS="${RUSTFLAGS/-C link-arg=-fuse-ld=lld/}"
cargo build --release --locked
}
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm755 "target/release/dcr" "$pkgdir/usr/bin/dcr"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}

View File

@@ -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::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};
@@ -8,10 +9,6 @@ use std::time::Instant;
pub fn build(args: &[String]) -> i32 { pub fn build(args: &[String]) -> i32 {
let mut active_profile = PROFILE.to_string(); 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(); let items = check_dir(None).unwrap_or_default();
if !items.contains(&"dcr.toml".to_string()) { if !items.contains(&"dcr.toml".to_string()) {
@@ -19,6 +16,22 @@ pub fn build(args: &[String]) -> i32 {
return 1; 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 let Some(first_arg) = args.first() {
if first_arg.starts_with("--") { if first_arg.starts_with("--") {
let candidate = first_arg.trim_start_matches("--"); let candidate = first_arg.trim_start_matches("--");
@@ -36,9 +49,9 @@ pub fn build(args: &[String]) -> i32 {
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()) { if !items.contains(&"target".to_string()) {
@@ -53,12 +66,13 @@ pub fn build(args: &[String]) -> i32 {
if src_items.contains(&"main.c".to_string()) { if src_items.contains(&"main.c".to_string()) {
let compile_flags = flags(&active_profile).unwrap_or(&[]); let compile_flags = flags(&active_profile).unwrap_or(&[]);
let start_time = Instant::now(); let start_time = Instant::now();
let mut cmd = Command::new(PROJECT_COMPILER); let mut cmd = Command::new(project_compiler);
cmd.arg("./src/main.c"); cmd.arg("./src/main.c");
for flag in compile_flags { for flag in compile_flags {
cmd.arg(flag); 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() { match cmd.status() {
Ok(status) if status.success() => { Ok(status) if status.success() => {

View File

@@ -3,6 +3,7 @@ use reqwest::blocking::Client;
use serde::Deserialize; use serde::Deserialize;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest"; const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest";
@@ -25,6 +26,22 @@ pub fn flag_update(args: &[String]) -> i32 {
return 1; return 1;
} }
let current_exe = match std::env::current_exe() {
Ok(path) => path,
Err(_) => {
error("Failed to resolve current binary path");
return 1;
}
};
if let Some(package_name) = pacman_owned_package(&current_exe) {
warn("This dcr binary is managed by pacman/AUR");
println!(
"Update via package manager: yay/paru -Syu {package_name} or sudo pacman -Syu {package_name}"
);
return 0;
}
let current_version = env!("CARGO_PKG_VERSION"); let current_version = env!("CARGO_PKG_VERSION");
let target = env!("DCR_TARGET"); let target = env!("DCR_TARGET");
let client = match Client::builder().user_agent("dcr-updater").build() { let client = match Client::builder().user_agent("dcr-updater").build() {
@@ -67,13 +84,6 @@ pub fn flag_update(args: &[String]) -> i32 {
} }
}; };
let current_exe = match std::env::current_exe() {
Ok(path) => path,
Err(_) => {
error("Failed to resolve current binary path");
return 1;
}
};
let temp_path = temp_binary_path(&current_exe); let temp_path = temp_binary_path(&current_exe);
if fs::write(&temp_path, &bytes).is_err() { if fs::write(&temp_path, &bytes).is_err() {
@@ -157,3 +167,23 @@ fn set_executable_permissions(path: &Path) {
} }
} }
} }
#[cfg(target_os = "linux")]
fn pacman_owned_package(path: &Path) -> Option<String> {
let output = Command::new("pacman").arg("-Qoq").arg(path).output().ok()?;
if !output.status.success() {
return None;
}
let package_name = String::from_utf8_lossy(&output.stdout).trim().to_string();
if package_name.is_empty() {
return None;
}
Some(package_name)
}
#[cfg(not(target_os = "linux"))]
fn pacman_owned_package(_path: &Path) -> Option<String> {
None
}

View File

@@ -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::fs::check_dir;
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use toml::Value;
pub fn init(args: &[String]) -> i32 { pub fn init(args: &[String]) -> i32 {
if !args.is_empty() { if !args.is_empty() {
@@ -27,15 +29,15 @@ pub fn init(args: &[String]) -> i32 {
.unwrap_or_else(|_| ".".to_string()); .unwrap_or_else(|_| ".".to_string());
println!("Initializing the project in {cwd}"); println!("Initializing the project in {cwd}");
let mut dcr_toml = match fs::File::create("./dcr.toml") { let mut config = match Config::new("./dcr.toml") {
Ok(file) => file, Ok(cfg) => cfg,
Err(_) => { Err(_) => {
error("Failed to create dcr.toml"); error("Failed to create dcr.toml");
return 1; return 1;
} }
}; };
if dcr_toml if config
.write_all(file_dcr_toml(&project_name).as_bytes()) .edit("package.name", Value::String(project_name.clone()))
.is_err() .is_err()
{ {
error("Failed to write dcr.toml"); error("Failed to write dcr.toml");
@@ -43,7 +45,7 @@ pub fn init(args: &[String]) -> i32 {
} }
println!( println!(
" {} Created file {}", " {} Created file {}",
colored("✔", BOLD_CYAN), colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN) colored("dcr.toml", BOLD_CYAN)
); );
@@ -73,6 +75,6 @@ pub fn init(args: &[String]) -> i32 {
colored(project_name.as_str(), BOLD_GREEN) colored(project_name.as_str(), BOLD_GREEN)
); );
printc("Next step:", BOLD_GREEN); printc("Next step:", BOLD_GREEN);
printc(" cd {}\n dcr run", project_name.as_str()); printc(" dcr run", BOLD_CYAN);
0 0
} }

View File

@@ -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::fs::check_dir;
use crate::utils::log::{error, warn}; use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc}; use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use toml::Value;
pub fn new(args: &[String]) -> i32 { pub fn new(args: &[String]) -> i32 {
let items = check_dir(None).unwrap_or_default(); let items = check_dir(None).unwrap_or_default();
@@ -42,20 +44,20 @@ pub fn new(args: &[String]) -> i32 {
} }
println!( println!(
" {} Directory created {}", " {} Directory created {}",
colored("✔", BOLD_CYAN), colored("✔", BOLD_GREEN),
project_name project_name
); );
let toml_path = format!("./{project_name}/dcr.toml"); let toml_path = format!("./{project_name}/dcr.toml");
let mut dcr_toml = match fs::File::create(&toml_path) { let mut config = match Config::new(&toml_path) {
Ok(file) => file, Ok(cfg) => cfg,
Err(_) => { Err(_) => {
error("Failed to create dcr.toml"); error("Failed to create dcr.toml");
return 1; return 1;
} }
}; };
if dcr_toml if config
.write_all(file_dcr_toml(project_name).as_bytes()) .edit("package.name", Value::String(project_name.to_string()))
.is_err() .is_err()
{ {
error("Failed to write dcr.toml"); error("Failed to write dcr.toml");
@@ -63,7 +65,7 @@ pub fn new(args: &[String]) -> i32 {
} }
println!( println!(
" {} Created file {}", " {} Created file {}",
colored("✔", BOLD_CYAN), colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN) colored("dcr.toml", BOLD_CYAN)
); );
@@ -94,6 +96,6 @@ pub fn new(args: &[String]) -> i32 {
colored(project_name, BOLD_GREEN) colored(project_name, BOLD_GREEN)
); );
printc("Next step:", 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 0
} }

View File

@@ -1,5 +1,6 @@
use crate::cli::build::build; use crate::cli::build::build;
use crate::config::{PROFILE, flags}; use crate::config::{PROFILE, flags};
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};
@@ -14,6 +15,18 @@ pub fn run(args: &[String]) -> i32 {
return 1; 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 let Some(first_arg) = args.first() {
if first_arg.starts_with("--") { if first_arg.starts_with("--") {
let candidate = first_arg.trim_start_matches("--"); let candidate = first_arg.trim_start_matches("--");
@@ -32,11 +45,11 @@ pub fn run(args: &[String]) -> i32 {
let build_status = build(args); let build_status = build(args);
if build_status == 0 { if build_status == 0 {
println!( println!(
"\n {} target/{active_profile}/main", "\n {} target/{active_profile}/{project_name}",
colored("Running", BOLD_GREEN) colored("Running", BOLD_GREEN)
); );
println!("--------------------------------"); println!("--------------------------------");
return Command::new(format!("./target/{active_profile}/main")) return Command::new(format!("./target/{active_profile}/{project_name}"))
.status() .status()
.map(|status| status.code().unwrap_or(1)) .map(|status| status.code().unwrap_or(1))
.unwrap_or(1); .unwrap_or(1);
@@ -47,7 +60,7 @@ pub fn run(args: &[String]) -> i32 {
.contains(&active_profile) .contains(&active_profile)
{ {
warn("Launch of the latest release"); warn("Launch of the latest release");
return Command::new(format!("./target/{active_profile}/main")) return Command::new(format!("./target/{active_profile}/{project_name}"))
.status() .status()
.map(|status| status.code().unwrap_or(1)) .map(|status| status.code().unwrap_or(1))
.unwrap_or(1); .unwrap_or(1);

View File

@@ -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 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 <stdio.h> pub const FILE_MAIN_C: &str = r#"#include <stdio.h>
int main(void) { int main(void) {

193
src/core/config.rs Normal file
View File

@@ -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<std::io::Error> for ConfigError {
fn from(err: std::io::Error) -> Self {
ConfigError::Io(err)
}
}
impl From<toml::de::Error> for ConfigError {
fn from(err: toml::de::Error) -> Self {
ConfigError::TomlDe(err)
}
}
impl From<toml::ser::Error> 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<Self, ConfigError> {
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<Self, ConfigError> {
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<Value, ConfigError> {
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<Value, ConfigError> {
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()))
}
}

1
src/core/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod config;

View File

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