17 Commits

Author SHA1 Message Date
ff12ca2758 Update release.yml 2026-02-21 16:19:44 +08:00
468491b5b1 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
2026-02-21 16:12:32 +08:00
0f48151b68 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
2026-02-21 16:12:20 +08:00
e87e0cbc47 update aur publish 2026-02-20 18:31:30 +08:00
fff8baa3c9 update aur publish 2026-02-20 18:22:05 +08:00
ad0d4cdcb7 update aur publish 2026-02-20 18:20:50 +08:00
45806f193e update aur publish 2026-02-20 18:17:36 +08:00
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
7a0ebb43a5 remove one project deps 2026-02-18 21:08:56 +08:00
4c628ff336 update CHANGELOG.md 2026-02-18 21:05:36 +08:00
95324ce199 chore: translate CLI and installer messages to English 2026-02-18 21:04:28 +08:00
d1e23a3179 Update project docs lang to Eng
Add FAQ.md and docs/
2026-02-18 19:53:39 +08:00
37 changed files with 1781 additions and 412 deletions

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

@@ -0,0 +1,136 @@
name: AUR Publish
on:
workflow_dispatch:
inputs:
version:
description: "Tag version without v prefix (e.g. 0.2.2)"
required: true
push:
tags:
- "v*"
permissions:
contents: read
jobs:
publish-aur:
runs-on: ubuntu-latest
environment: aur
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive version and checksum
id: meta
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
PKGVER="${{ inputs.version }}"
else
PKGVER="${GITHUB_REF_NAME#v}"
fi
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:-}"
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

@@ -9,6 +9,34 @@ permissions:
contents: write
jobs:
notes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Prepare Release Notes
shell: bash
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
notes_file=".github/release-notes.md"
if grep -q "^## \\[${tag}\\]" CHANGELOG.md; then
awk -v ver="$tag" '
$0 ~ "^## \\[" ver "\\]" {found=1; next}
found && $0 ~ "^## \\[" {exit}
found {print}
' CHANGELOG.md | sed -e '${/^$/d;}' > "$notes_file"
else
echo "No CHANGELOG section for $tag." > "$notes_file"
git log -1 --pretty=format:"%s%n%n%b" >> "$notes_file"
fi
- name: Upload Notes Artifact
uses: actions/upload-artifact@v4
with:
name: release-notes
path: .github/release-notes.md
build:
strategy:
fail-fast: false
@@ -17,10 +45,10 @@ jobs:
- os: ubuntu-latest
target: 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
asset_name: dcr-x86_64-apple-darwin
- os: macos-14
- os: macos-15
target: aarch64-apple-darwin
asset_name: dcr-aarch64-apple-darwin
- os: windows-latest
@@ -28,9 +56,14 @@ jobs:
asset_name: dcr-x86_64-pc-windows-msvc.exe
runs-on: ${{ matrix.os }}
needs: notes
steps:
- uses: actions/checkout@v4
- name: Download Release Notes
uses: actions/download-artifact@v4
with:
name: release-notes
path: .github
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -56,3 +89,4 @@ jobs:
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.asset_name }}
body_path: .github/release-notes.md

View File

@@ -1,40 +1,65 @@
# 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`
- `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
Changed:
- Translated user-facing CLI messages to English
- Unified error and warning output via `utils::log::{error, warn}`
- Updated `--help` output: translated headers and examples to English, used `printc`, and applied `BOLD_*` styles
- 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

57
Cargo.lock generated
View File

@@ -62,11 +62,12 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "dcr"
version = "0.2.0"
version = "0.2.3"
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"

View File

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

102
FAQ.md Normal file
View File

@@ -0,0 +1,102 @@
# FAQ
## What is this project about?
`dcr` is a tool for managing C/C++ projects and their dependencies.
It is inspired by Cargo, but it is not a direct equivalent of Makefile, CMake, or other classic build systems.
The goal is to provide a simpler and easier-to-configure development workflow.
## How is the project developed?
The project is written in Rust and is primarily developed by one maintainer, Dexoron.
AI tools are used during development, but they do not write the project end-to-end. Their role is limited to supporting specific tasks (for example, suggestions, wording checks, and translations).
## Which tools are used for development?
Main development stack:
- `RustRover` 2025 - development IDE.
- `Codex` (`GPT-5.3-Codex (medium)`) - primary AI tool, integrated via JetBrains AI Chat.
- `Rust` - main project language.
- `Cargo` - tool for building, testing, and dependency management in Rust projects.
## Is the project open source?
Yes, the project is open source.
You can propose ideas, report issues, and contribute code changes.
The project is distributed under the MIT license (see `LICENSE`).
## How can I contribute?
Thank you for contributing.
You can:
- help with code;
- help with translations;
- help with documentation;
- suggest an idea or report a bug.
### If you want to submit changes (code, translation, documentation)
1. Fork the required repository: `dexoron/dcr` (main project) or `dexoron/dcr-site` (project website).
2. Make your changes following the rules in `CONTRIBUTING.md`.
3. Push changes to your fork.
4. Open a Pull Request.
5. Wait for review.
### If you want to report a problem or suggest an idea
1. Open the relevant repository: `dexoron/dcr` or `dexoron/dcr-site`.
2. Go to the `Issues` tab.
3. Create an Issue and specify its type (bug, suggestion, question/help).
4. Describe the problem or idea clearly and concisely.
5. Wait for feedback.
## How do I install `dcr`?
Available installation methods may vary by project version.
Recommended path:
- check instructions in `README.md`;
- if there is no prebuilt package for your platform, build from source using `cargo build --release`.
## How can I get started quickly?
Minimal workflow:
1. Install `dcr`.
2. Create a project (or open an existing C/C++ project).
3. Initialize `dcr` configuration.
4. Add project dependencies.
5. Build using `dcr`.
For detailed commands and examples, see `README.md` and the project documentation.
## What is the project status?
`dcr` is under active development.
This means some interfaces and behaviors may change between versions.
Before upgrading, check the changelog and release notes.
## Which systems does `dcr` support?
`dcr` targets modern C/C++ development environments.
Actual compatibility depends on the `dcr` version, operating system, and toolchain.
Before starting, check documentation for:
- supported operating systems;
- supported compilers and linkers;
- version-specific limitations.
## How is `dcr` different from Make/CMake?
`dcr` does not aim to be a direct replacement for Make/CMake.
Its main focus is simplifying project and dependency management with a Cargo-like experience.
If you need low-level, full control over build steps, classic tools may be a better fit.
If you want a simpler and more uniform configuration model, `dcr` may be more convenient.
## Where can I find documentation and examples?
Primary sources:
- `README.md` in the repository;
- project documentation (see the project website);
- `Issues` for known problems and discussions.
## How do I report a bug properly?
To make a bug report easy to reproduce, include:
1. `dcr` version.
2. OS and compiler/toolchain version.
3. Steps to reproduce.
4. Expected behavior.
5. Actual behavior and error output.
6. Minimal project example (if possible).
Submit bugs and issues through `Issues` in the relevant repository.

122
README.md
View File

@@ -1,31 +1,38 @@
# DCR (Dexoron Cargo Realization)
DCR — это утилита для управления проектами на C/C++ в стиле Cargo.
DCR is a utility for managing C/C++ projects in a Cargo-like style.
Текущая реализация написана на Rust.
The current implementation is written in Rust.
## Зачем DCR
- Единая структура проекта без ручной настройки
- Простые команды для типовых задач
- Прозрачная компиляция и предсказуемые профили сборки
## Why DCR
- Unified project structure without manual setup
- Simple commands for common tasks
- Transparent compilation and predictable build profiles
## Возможности
- Создание нового проекта или инициализация текущей директории
- Сборка проекта в профилях `debug` и `release`
- Запуск собранного бинарника
- Очистка результатов сборки
- Генерация минимального шаблона C-проекта
- Обновление бинарника через `dcr --update` (GitHub Releases)
## Features
- Create a new project or initialize the current directory
- Build a project with `debug` and `release` profiles
- Run the compiled binary
- Clean build artifacts
- Generate a minimal C project template
- Update the binary via `dcr --update` (GitHub Releases, not for pacman/AUR installs)
## Поддерживаемые платформы
## Supported Platforms
- Linux: `x86_64-unknown-linux-gnu`
- macOS Intel: `x86_64-apple-darwin`
- macOS Apple Silicon: `aarch64-apple-darwin`
- Windows: `x86_64-pc-windows-msvc`
## Установка
## Installation
### Из исходников
### Package Manager
**Arch Linux**
```sh
yay -S dcr # or paru and other AUR package managers
```
### From Source
```sh
git clone https://github.com/dexoron/dcr.git
@@ -35,37 +42,45 @@ mkdir -p ~/.local/bin
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
```
### Через install.sh (Linux/macOS)
### Via `install.sh` (Linux/macOS)
```sh
curl -fsSL https://raw.githubusercontent.com/dexoron/dcr/master/install.sh | bash
curl -fsSL https://dcr.dexoron.su/install.sh | bash
```
### Через install.ps1 (Windows)
### Via `install.ps1` (Windows)
```powershell
irm https://raw.githubusercontent.com/dexoron/dcr/master/install.ps1 | iex
irm https://dcr.dexoron.su/install.ps1 | iex
```
Оба скрипта при запуске спрашивают:
- скачать готовый бинарник из GitHub Release
- или собрать проект из `git`
When executed, both scripts ask whether to:
- download a prebuilt binary from GitHub Releases
- or build the project from `git`
Релизные ассеты:
Release assets:
- `dcr-x86_64-unknown-linux-gnu`
- `dcr-x86_64-apple-darwin`
- `dcr-aarch64-apple-darwin`
- `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
Create a new project:
`dcr new hello`
Или инициализировать текущую директорию (директория должна быть пустой):
Or initialize the current directory (the directory must be empty):
`dcr init`
Структура проекта:
Project structure:
```txt
hello/
@@ -74,55 +89,58 @@ hello/
- dcr.toml
```
Сборка и запуск проекта:
`dcr run` или `dcr run --release`
Build and run the project:
`dcr run` or `dcr run --release`
## Команды
## Commands
### `dcr new <name>`
Создает проект в текущей директории с указанным именем.
Creates a project with the specified name in the current directory.
### `dcr init`
Создает проект в текущей директории. Имя проекта будет равно имени директории. Директория должна быть пустой.
Creates a project in the current directory. The project name is taken from the directory name. The directory must be empty.
### `dcr build [profile]`
Собирает проект. Если профиль не указан, используется `--debug`.
Builds the project. If no profile is specified, `--debug` is used.
### `dcr run [profile]`
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`.
Builds the project and runs the binary. If no profile is specified, `--debug` is used.
Запуск вручную:
`./target/<profile>/main`
Run manually:
`./target/<profile>/<name>`
### `dcr clean`
Удаляет папку `target` в корне проекта.
Removes the `target` directory in the project root.
## Профили сборки
Поддерживаются два профиля:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
- `--release` — флаги: `-O3 -DNDEBUG -march=native`
## Build Profiles
Two profiles are supported:
- `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang)
- `--release` - flags: `-O3 -DNDEBUG -march=native`(Clang)
## Конфигурация
Основной файл проекта — `dcr.toml`.
## Configuration
The main project file is `dcr.toml`.
Пример `dcr.toml`:
Example `dcr.toml`:
```toml
[package]
name = "hello"
version = "0.1.0"
[build]
language = "c"
standard = "c11"
compiler = "clang"
[dependencies]
```
## Требования
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
- C-компилятор (`clang`, `gcc` или другие)
## Requirements
- Rust toolchain (`rustc`, `cargo`) - for building DCR from source
- C compiler (`clang`, `gcc`, or 'cl'(msvc))
## Релизы
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.
## Releases
Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
## Лицензия
См. файл `LICENSE`.
## License
See `LICENSE`.

57
docs/CONTRIBUTING_ru.md Normal file
View File

@@ -0,0 +1,57 @@
# Вклад в проект
Спасибо за интерес к проекту.
## Требования
- Использовать Rust stable.
- Перед PR код должен проходить форматирование, линт и сборку.
## Быстрый старт
```bash
git clone https://github.com/dexoron/dcr.git
cd dcr
cargo check
```
## Стиль кода
- Форматируйте код через `cargo fmt`.
- Проверяйте предупреждения через `cargo clippy`.
- Держите функции небольшими и сфокусированными.
- Предпочитайте понятные имена вместо "хитрых".
## Проверки
Перед PR запускайте:
```bash
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo check
```
## CI/CD и релизы
- CI: `.github/workflows/ci.yml`
- Release: `.github/workflows/release.yml`
- Релиз запускается по тегу `v*` и публикует бинарники для:
- `x86_64-unknown-linux-gnu`
- `x86_64-apple-darwin`
- `aarch64-apple-darwin`
- `x86_64-pc-windows-msvc`
## Коммиты и PR
- Делайте коммиты понятными.
- В PR указывайте краткое описание и мотивацию изменений.
- Обновляйте документацию при изменении поведения.
## Вопросы
Если что-то непонятно, открой issue или задайте вопрос в PR.
Также можно задать вопрос напрямую:
- TG: @dexoron
- DS: dexoron

102
docs/FAQ_ru.md Normal file
View File

@@ -0,0 +1,102 @@
# FAQ
## В чем суть проекта?
`dcr` - это инструмент для управления C/C++ проектами и их зависимостями.
Он вдохновлен Cargo, но не является аналогом Makefile, CMake или других классических систем сборки.
Цель проекта - дать более простую и удобную в настройке среду для разработки.
## Как разрабатывается проект?
Проект написан на Rust и в основном развивается одним разработчиком - Dexoron.
В работе используются ИИ-инструменты, но они не пишут проект целиком: их роль - помощь с отдельными задачами (например, советы, проверка формулировок, переводы и т.д.).
## Какие инструменты используются для разработки?
Основной стек разработки:
- `RustRover` 2025 - IDE для разработки.
- `Codex` (`GPT-5.3-Codex (medium)`) - основной ИИ-инструмент, интегрированный через JetBrains AI Chat.
- `Rust` - основной язык проекта.
- `Cargo` - инструмент сборки, тестов и управления зависимостями Rust-проектов.
## Проект открытый?
Да, проект открыт (Open Source).
Вы можете предлагать идеи, находить ошибки и вносить изменения в код.
Проект распространяется по лицензии MIT (см. `LICENSE`).
## Как внести вклад?
Спасибо за желание помочь проекту.
Вы можете:
- помочь с кодом;
- помочь с переводом;
- помочь с документацией;
- предложить идею или сообщить о баге.
### Если вы хотите внести изменения (код, перевод, документация)
1. Сделайте форк нужного репозитория: `dexoron/dcr` (основной проект) или `dexoron/dcr-site` (сайт проекта).
2. Внесите изменения с учетом правил из `CONTRIBUTING.md`.
3. Отправьте изменения в свой форк.
4. Создайте Pull Request.
5. Дождитесь ревью.
### Если вы хотите сообщить о проблеме или предложить идею
1. Откройте нужный репозиторий: `dexoron/dcr` или `dexoron/dcr-site`.
2. Перейдите во вкладку `Issues`.
3. Создайте Issue и укажите тип (баг, предложение, вопрос/помощь).
4. Опишите проблему или идею кратко и понятно.
5. Дождитесь обратной связи.
## Как установить `dcr`?
Актуальные способы установки зависят от текущей версии проекта.
Рекомендуемый путь:
- проверьте инструкции в `README.md`;
- если готового пакета для вашей платформы нет, соберите проект из исходников через `cargo build --release`.
## Как быстро начать работу?
Минимальный сценарий:
1. Установите `dcr`.
2. Создайте проект (или перейдите в существующий C/C++ проект).
3. Инициализируйте конфигурацию `dcr`.
4. Добавьте зависимости проекта.
5. Запустите сборку через `dcr`.
Подробные команды и примеры смотрите в `README.md` и документации проекта.
## На какой стадии проект?
`dcr` активно развивается.
Это означает, что интерфейсы и поведение некоторых функций могут меняться между версиями.
Перед обновлением рекомендуется читать changelog и проверять заметки к релизу.
## С какими системами работает `dcr`?
`dcr` ориентирован на современные среды разработки C/C++.
Фактическая совместимость зависит от версии `dcr`, операционной системы и используемого toolchain.
Перед началом работы проверьте в документации:
- поддерживаемые ОС;
- поддерживаемые компиляторы и линкеры;
- ограничения конкретной версии.
## Чем `dcr` отличается от Make/CMake?
`dcr` не пытается быть прямой заменой Make/CMake.
Главный фокус `dcr` - упростить управление проектом и зависимостями в стиле, близком к опыту Cargo.
Если вам нужен низкоуровневый полный контроль над процессом сборки, классические инструменты могут быть привычнее.
Если нужна более простая и единообразная модель конфигурации - `dcr` может быть удобнее.
## Где смотреть документацию и примеры?
Основные источники:
- `README.md` в репозитории;
- документация проекта (смотреть сайт проекта);
- `Issues` для известных проблем и обсуждений;
## Как правильно сообщить о баге?
Чтобы баг-репорт можно было быстро воспроизвести, укажите:
1. Версию `dcr`.
2. ОС и версию компилятора/toolchain.
3. Шаги воспроизведения.
4. Ожидаемое поведение.
5. Фактическое поведение и текст ошибки.
6. Минимальный пример проекта (по возможности).
Баги и проблемы отправляйте через `Issues` в соответствующем репозитории.

146
docs/README_ru.md Normal file
View File

@@ -0,0 +1,146 @@
# DCR (Dexoron Cargo Realization)
DCR — это утилита для управления проектами на C/C++ в стиле Cargo.
Текущая реализация написана на Rust.
## Зачем DCR
- Единая структура проекта без ручной настройки
- Простые команды для типовых задач
- Прозрачная компиляция и предсказуемые профили сборки
## Возможности
- Создание нового проекта или инициализация текущей директории
- Сборка проекта в профилях `debug` и `release`
- Запуск собранного бинарника
- Очистка результатов сборки
- Генерация минимального шаблона C-проекта
- Обновление бинарника через `dcr --update` (GitHub Releases, не для pacman-установок)
## Поддерживаемые платформы
- Linux: `x86_64-unknown-linux-gnu`
- macOS Intel: `x86_64-apple-darwin`
- macOS Apple Silicon: `aarch64-apple-darwin`
- Windows: `x86_64-pc-windows-msvc`
## Установка
### Пакетный менеджер
**ArchLinux**
```sh
yay -S dcr # или paru и остальные менеджеры пакетов AUR
```
### Из исходников
```sh
git clone https://github.com/dexoron/dcr.git
cd dcr
cargo build --release
mkdir -p ~/.local/bin
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
```
### Через install.sh (Linux/macOS)
```sh
curl -fsSL https://dcr.dexoron.su/install.sh | bash
```
### Через install.ps1 (Windows)
```powershell
irm https://dcr.dexoron.su/install.ps1 | iex
```
Оба скрипта при запуске спрашивают:
- скачать готовый бинарник из GitHub Release
- или собрать проект из `git`
Релизные ассеты:
- `dcr-x86_64-unknown-linux-gnu`
- `dcr-x86_64-apple-darwin`
- `dcr-aarch64-apple-darwin`
- `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` определяет пакетную установку и подсказывает обновляться через пакетный менеджер
## Быстрый старт
Создать новый проект:
`dcr new hello`
Или инициализировать текущую директорию (директория должна быть пустой):
`dcr init`
Структура проекта:
```txt
hello/
- src/
- - main.c
- dcr.toml
```
Сборка и запуск проекта:
`dcr run` или `dcr run --release`
## Команды
### `dcr new <name>`
Создает проект в текущей директории с указанным именем.
### `dcr init`
Создает проект в текущей директории. Имя проекта будет равно имени директории. Директория должна быть пустой.
### `dcr build [profile]`
Собирает проект. Если профиль не указан, используется `--debug`.
### `dcr run [profile]`
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`.
Запуск вручную:
`./target/<profile>/<name>`
### `dcr clean`
Удаляет папку `target` в корне проекта.
## Профили сборки
Поддерживаются два профиля:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`(Clang)
- `--release` — флаги: `-O3 -DNDEBUG -march=native`(Clang)
## Конфигурация
Основной файл проекта — `dcr.toml`.
Пример `dcr.toml`:
```toml
[package]
name = "hello"
version = "0.1.0"
[build]
language = "c"
standard = "c11"
compiler = "clang"
[dependencies]
```
## Требования
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
- C-компилятор (`clang`, `gcc` или 'cl'(msvc))
## Релизы
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.
## Лицензия
См. файл `LICENSE`.

View File

@@ -13,27 +13,27 @@ function Fail($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 }
function Require-Cmd($name) {
if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
Fail "Не найдено: $name"
Fail "Not found: $name"
}
}
function Select-Mode {
Write-Host "Выбери способ установки:"
Write-Host " 1) Скачать готовый бинарник из GitHub Release (рекомендуется)"
Write-Host " 2) Собрать из git"
$choice = Read-Host "Введите 1 или 2 [1]"
Write-Host "Choose installation mode:"
Write-Host " 1) Download prebuilt binary from GitHub Release (recommended)"
Write-Host " 2) Build from git"
$choice = Read-Host "Enter 1 or 2 [1]"
if ([string]::IsNullOrWhiteSpace($choice)) { $choice = '1' }
switch ($choice) {
'1' { return 'release' }
'2' { return 'build' }
default { Fail 'Неизвестный вариант' }
default { Fail 'Unknown option' }
}
}
function Get-Target {
if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') {
Fail "Поддерживается только x86_64 Windows, текущая архитектура: $env:PROCESSOR_ARCHITECTURE"
Fail "Only x86_64 Windows is supported, current architecture: $env:PROCESSOR_ARCHITECTURE"
}
return 'x86_64-pc-windows-msvc'
}
@@ -44,17 +44,17 @@ function Install-FromRelease {
New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
$assetName = "dcr-$target.exe"
Info "Получение последнего релиза..."
Info "Fetching latest release..."
$release = Invoke-RestMethod -Method Get -Uri $ApiUrl
$asset = $release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1
if (-not $asset) {
Fail "Не найден ассет $assetName в релизе $($release.tag_name)"
Fail "Asset $assetName not found in release $($release.tag_name)"
}
Info "Скачивание $assetName..."
Info "Downloading $assetName..."
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $BinaryPath
Ok "Скачан бинарник $assetName"
Ok "Binary downloaded: $assetName"
}
function Install-FromSource {
@@ -66,17 +66,17 @@ function Install-FromSource {
$tmp = Join-Path $env:TEMP 'dcr-install'
if (Test-Path $tmp) { Remove-Item -Recurse -Force $tmp }
Info 'Клонирование репозитория...'
Info 'Cloning repository...'
git clone --depth 1 $RepoUrl $tmp | Out-Null
Info 'Сборка release-бинарника...'
Info 'Building release binary...'
Push-Location $tmp
cargo build --release --target $target
Pop-Location
New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
Copy-Item (Join-Path $tmp "target\$target\release\dcr.exe") $BinaryPath -Force
Ok 'Установлен бинарник из исходников'
Ok 'Binary installed from source'
Remove-Item -Recurse -Force $tmp
}
@@ -93,13 +93,13 @@ function Setup-Path {
$newPath = if ($userPath) { "$userPath;$BinPath" } else { $BinPath }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
$env:Path = "$env:Path;$BinPath"
Warn "Добавлен путь в PATH: $BinPath"
Warn "Added path to PATH: $BinPath"
}
Ok "Команда dcr доступна через $linkPath"
Ok "dcr command is available via $linkPath"
}
Info 'Запуск установки DCR'
Info 'Starting DCR installation'
$mode = Select-Mode
$target = Get-Target
@@ -110,4 +110,4 @@ if ($mode -eq 'build') {
}
Setup-Path
Ok 'Установка завершена успешно'
Ok 'Installation completed successfully'

View File

@@ -22,12 +22,12 @@ success() { echo -e "${GREEN}✔ $1${NC}"; }
warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
error() { echo -e "${RED}✖ $1${NC}"; }
trap 'error "Ошибка на строке $LINENO"; exit 1' ERR
trap 'error "Error on line $LINENO"; exit 1' ERR
check_os() {
case "$(uname -s)" in
Linux|Darwin) ;;
*) error "Поддерживаются только Linux и macOS"; exit 1 ;;
*) error "Only Linux and macOS are supported"; exit 1 ;;
esac
}
@@ -40,32 +40,32 @@ detect_target() {
Linux:x86_64) TARGET_TRIPLE="x86_64-unknown-linux-gnu" ;;
Darwin:x86_64) TARGET_TRIPLE="x86_64-apple-darwin" ;;
Darwin:arm64|Darwin:aarch64) TARGET_TRIPLE="aarch64-apple-darwin" ;;
*) error "Неподдерживаемая платформа: $os/$arch"; exit 1 ;;
*) error "Unsupported platform: $os/$arch"; exit 1 ;;
esac
ASSET_NAME="dcr-${TARGET_TRIPLE}"
}
check_common_dependencies() {
command -v curl >/dev/null 2>&1 || { error "curl не установлен"; exit 1; }
command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
}
check_build_dependencies() {
command -v git >/dev/null 2>&1 || { error "git не установлен"; exit 1; }
command -v cargo >/dev/null 2>&1 || { error "cargo не установлен"; exit 1; }
command -v git >/dev/null 2>&1 || { error "git is not installed"; exit 1; }
command -v cargo >/dev/null 2>&1 || { error "cargo is not installed"; exit 1; }
}
prepare_sources() {
log "Получение исходников..."
log "Fetching sources..."
rm -rf "$TMPDIR"
git clone --depth 1 "$REPO_URL" "$TMPDIR"
success "Исходники получены"
success "Sources fetched"
}
build_binary() {
log "Сборка release-бинарника..."
log "Building release binary..."
(cd "$TMPDIR" && cargo build --release)
success "Сборка завершена"
success "Build completed"
}
fetch_latest_release_json() {
@@ -73,47 +73,47 @@ fetch_latest_release_json() {
}
download_binary() {
log "Получение последнего релиза..."
log "Fetching latest release..."
local release_json download_url tag
release_json="$(fetch_latest_release_json)"
tag="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p' | head -n1)"
if [[ -z "$tag" ]]; then
error "Не удалось определить версию релиза"
error "Failed to determine release version"
exit 1
fi
download_url="$(printf '%s\n' "$release_json" | sed -n "s#.*\"browser_download_url\": \"\([^\"]*${ASSET_NAME}\)\".*#\1#p" | head -n1)"
if [[ -z "$download_url" ]]; then
error "Не найден ассет ${ASSET_NAME} в релизе ${tag}"
error "Asset ${ASSET_NAME} not found in release ${tag}"
exit 1
fi
mkdir -p "$INSTALL_PATH"
curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
chmod +x "$INSTALL_PATH/dcr"
success "Скачан бинарник ${ASSET_NAME} (${tag})"
success "Binary ${ASSET_NAME} downloaded (${tag})"
}
install_built_binary() {
mkdir -p "$INSTALL_PATH"
cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
chmod +x "$INSTALL_PATH/dcr"
success "Установлен бинарник из исходников"
success "Binary installed from source"
}
install_link() {
log "Создание симлинка..."
log "Creating symlink..."
mkdir -p "$BINPATH"
ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
success "Команда 'dcr' добавлена в $BINPATH"
success "Command 'dcr' added to $BINPATH"
}
check_path() {
if ! echo "$PATH" | grep -q "$BINPATH"; then
warn "Каталог $BINPATH не найден в PATH"
echo "Добавь в ~/.bashrc или ~/.zshrc:"
warn "Directory $BINPATH not found in PATH"
echo "Add this to ~/.bashrc or ~/.zshrc:"
echo "export PATH=\"$BINPATH:\$PATH\""
fi
}
@@ -123,20 +123,20 @@ cleanup() {
}
select_install_mode() {
echo "Выбери способ установки:"
echo " 1) Скачать готовый бинарник из GitHub Release (рекомендуется)"
echo " 2) Собрать из git"
read -r -p "Введите 1 или 2 [1]: " choice
echo "Choose installation mode:"
echo " 1) Download prebuilt binary from GitHub Release (recommended)"
echo " 2) Build from git"
read -r -p "Enter 1 or 2 [1]: " choice
case "${choice:-1}" in
1) INSTALL_MODE="release" ;;
2) INSTALL_MODE="build" ;;
*) error "Неизвестный вариант"; exit 1 ;;
*) error "Unknown option"; exit 1 ;;
esac
}
main() {
log "Запуск установки DCR"
log "Starting DCR installation"
check_os
detect_target
@@ -157,8 +157,8 @@ main() {
check_path
cleanup
success "Установка завершена успешно"
log "Лог сохранён в $LOGFILE"
success "Installation completed successfully"
log "Log saved to $LOGFILE"
}
main "$@"

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,30 @@
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:-}"
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,82 +1,112 @@
use crate::config::{PROFILE, PROJECT_COMPILER, flags};
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::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, colored};
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 red_bold = BRIGHT_RED.to_owned() + BOLD;
let green_bold = BRIGHT_GREEN.to_owned() + BOLD;
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()) {
println!("{}: не найден файл dcr.toml", colored("error", &red_bold));
error("dcr.toml file not found");
return 1;
}
let config = match Config::open("./dcr.toml") {
Ok(cfg) => cfg,
Err(_) => {
error("dcr.toml file not found");
return 1;
}
};
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(&times.to_string(), BOLD_GREEN)
);
0
}
Err(msg) => {
error(&msg);
1
}
}
}
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() {
active_profile = candidate.to_string();
} else {
println!("{}: неизвестный флаг сборки", colored("error", &red_bold));
return 1;
return Ok(candidate.to_string());
}
} else {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
return 1;
warn("Unknown build flag");
return Err(1);
}
warn("Unknown argument");
return Err(1);
}
Ok(PROFILE.to_string())
}
println!(
" Сборка проекта `{}`\n Профиль: {}\n Компилятор: {}\n",
colored(&project_name, &green_bold),
colored(&active_profile, &green_bold),
colored(PROJECT_COMPILER, &green_bold)
);
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<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()),
}
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}/main"));
match cmd.status() {
Ok(status) if status.success() => {
let times = ((start_time.elapsed().as_secs_f64() * 100.0).trunc()) / 100.0;
println!(
" {} Сборка завершена успешно, за {} секунд",
colored("✔", &green_bold),
colored(&times.to_string(), &green_bold)
);
return 0;
}
_ => {
println!("{}: сборка не удалась", colored("error", &red_bold));
return 1;
}
}
}
0
}

View File

@@ -1,13 +1,10 @@
use crate::config::flags;
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, BRIGHT_YELLOW, colored};
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use std::fs;
pub fn clean(args: &[String]) -> i32 {
let red_bold = BRIGHT_RED.to_owned() + BOLD;
let green_bold = BRIGHT_GREEN.to_owned() + BOLD;
let yellow_bold = BRIGHT_YELLOW.to_owned() + BOLD;
let project_name = std::env::current_dir()
.ok()
.and_then(|p| p.file_name().map(|v| v.to_string_lossy().to_string()))
@@ -15,23 +12,20 @@ pub fn clean(args: &[String]) -> i32 {
let items = check_dir(None).unwrap_or_default();
if args.len() > 1 {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
error("Unknown argument");
return 1;
}
if !items.contains(&"dcr.toml".to_string()) {
println!("{}: не найден файл dcr.toml", colored("error", &red_bold));
error("dcr.toml file not found");
return 1;
}
println!(
" Очистка проекта `{}`",
colored(&project_name, &green_bold)
" Cleaning project `{}`",
colored(&project_name, BOLD_GREEN)
);
if !items.contains(&"target".to_string()) {
println!(
"{}: директория target не найдена",
colored("warn", &yellow_bold)
);
warn("Directory target not found");
return 1;
}
@@ -41,32 +35,29 @@ pub fn clean(args: &[String]) -> i32 {
profile = profile.trim_start_matches("--").to_string();
}
if flags(&profile).is_none() {
println!("{}: неизвестный профиль", colored("error", &red_bold));
error("Unknown profile");
return 1;
}
let target_items = check_dir(Some("target")).unwrap_or_default();
if !target_items.contains(&profile) {
println!(
"{}: директория target/{profile} не найдена",
colored("warn", &yellow_bold)
);
warn(&format!("Directory target/{profile} not found"));
return 1;
}
println!(" Профиль: {}", colored(&profile, &green_bold));
println!(" Profile: {}", colored(&profile, BOLD_GREEN));
let _ = fs::remove_dir_all(format!("target/{profile}"));
println!(
"{} Удалена директория target/{profile}",
colored("\n ✔", &green_bold)
"{} Removed directory target/{profile}",
colored("\n ✔", BOLD_GREEN)
);
return 0;
}
let _ = fs::remove_dir_all("target");
println!(
"{} Удалена директория target",
colored("\n ✔", &green_bold)
"{} Removed directory target",
colored("\n ✔", BOLD_GREEN)
);
0
}

View File

@@ -1,7 +1,9 @@
use crate::utils::log::{error, warn};
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest";
@@ -20,16 +22,32 @@ struct ReleaseAsset {
pub fn flag_update(args: &[String]) -> i32 {
if !args.is_empty() {
println!("Ошибка: команда не поддежривает аргументы");
warn("Command does not support additional arguments");
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 target = env!("DCR_TARGET");
let client = match Client::builder().user_agent("dcr-updater").build() {
Ok(client) => client,
Err(_) => {
println!("Ошибка: не удалось инициализировать HTTP клиент");
error("Failed to initialize HTTP client");
return 1;
}
};
@@ -37,14 +55,14 @@ pub fn flag_update(args: &[String]) -> i32 {
let release = match fetch_latest_release(&client) {
Ok(release) => release,
Err(err) => {
println!("Ошибка: не удалось проверить обновления: {err}");
error(&format!("Failed to check for updates: {err}"));
return 1;
}
};
let latest_version = release.tag_name.trim_start_matches('v');
if latest_version == current_version {
println!("Установлена последняя версия: {current_version}");
println!("Latest version is already installed: {current_version}");
return 0;
}
@@ -54,41 +72,34 @@ pub fn flag_update(args: &[String]) -> i32 {
.iter()
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
else {
println!("Ошибка: не найден бинарник для таргета {target}");
error(&format!("Binary for target {target} not found"));
return 1;
};
let bytes = match download_asset(&client, &asset.browser_download_url) {
Ok(bytes) => bytes,
Err(err) => {
println!("Ошибка: не удалось скачать обновление: {err}");
error(&format!("Failed to download update: {err}"));
return 1;
}
};
let current_exe = match std::env::current_exe() {
Ok(path) => path,
Err(_) => {
println!("Ошибка: не удалось определить путь текущего бинарника");
return 1;
}
};
let temp_path = temp_binary_path(&current_exe);
if fs::write(&temp_path, &bytes).is_err() {
println!("Ошибка: не удалось записать временный бинарник");
error("Failed to write temporary binary");
return 1;
}
set_executable_permissions(&temp_path);
if self_replace::self_replace(&temp_path).is_err() {
let _ = fs::remove_file(&temp_path);
println!("Ошибка: не удалось заменить текущий бинарник");
error("Failed to replace current binary");
return 1;
}
let _ = fs::remove_file(&temp_path);
println!("Обновление завершено: {current_version} -> {latest_version}");
println!("Update completed: {current_version} -> {latest_version}");
0
}
@@ -96,31 +107,31 @@ fn fetch_latest_release(client: &Client) -> Result<Release, String> {
let response = client
.get(LATEST_RELEASE_URL)
.send()
.map_err(|_| "запрос к GitHub API завершился ошибкой".to_string())?;
.map_err(|_| "GitHub API request failed".to_string())?;
if !response.status().is_success() {
return Err(format!("GitHub API вернул статус {}", response.status()));
return Err(format!("GitHub API returned status {}", response.status()));
}
response
.json::<Release>()
.map_err(|_| "ответ GitHub API имеет неожиданный формат".to_string())
.map_err(|_| "GitHub API response has an unexpected format".to_string())
}
fn download_asset(client: &Client, url: &str) -> Result<Vec<u8>, String> {
let response = client
.get(url)
.send()
.map_err(|_| "запрос на скачивание завершился ошибкой".to_string())?;
.map_err(|_| "Download request failed".to_string())?;
if !response.status().is_success() {
return Err(format!("скачивание вернуло статус {}", response.status()));
return Err(format!("Download returned status {}", response.status()));
}
response
.bytes()
.map(|bytes| bytes.to_vec())
.map_err(|_| "не удалось прочитать скачанные данные".to_string())
.map_err(|_| "Failed to read downloaded data".to_string())
}
fn asset_candidates(target: &str) -> Vec<String> {
@@ -156,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,36 +1,33 @@
use crate::utils::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, colored};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
pub fn help() -> i32 {
let green_bold = BRIGHT_GREEN.to_owned() + BOLD;
let cyan_bold = BRIGHT_CYAN.to_owned() + BOLD;
println!("DCR (Dexoron Cargo Realization)");
println!("Менеджер C-проектов, вдохновленный Cargo.");
println!("C project manager inspired by Cargo.");
println!();
println!("{}", colored("ИСПОЛЬЗОВАНИЕ:", &green_bold));
println!("{}", colored(" dcr <команда> [опции]", &cyan_bold));
printc("USAGE:", BOLD_GREEN);
printc(" dcr <command> [options]", BOLD_CYAN);
println!();
println!("{}", colored("КОМАНДЫ:", &green_bold));
println!(" new <name> Создать новый проект");
println!(" init Инициализировать текущую директорию как проект");
println!(" build [--profile] Собрать проект (по умолчанию: --debug)");
println!(" run [--profile] Собрать и запустить (по умолчанию: --debug)");
println!(" clean Удалить директорию target");
println!("{}", colored("ФЛАГИ:", &green_bold));
println!(" --help Показать справку по команде");
println!(" --update Обновить dcr до актуальной версии");
println!(" --version Показать версию dcr");
printc("COMMANDS:", BOLD_GREEN);
println!(" new <name> Create a new project");
println!(" init Initialize the current directory as a project");
println!(" build [--profile] Build the project (default: --debug)");
println!(" run [--profile] Build and run the project (default: --debug)");
println!(" clean Remove the target directory");
printc("FLAGS:", BOLD_GREEN);
println!(" --help Show command help");
println!(" --update Update dcr to the latest version");
println!(" --version Show dcr version");
println!();
println!("{}", colored("ОПЦИИ:", &green_bold));
println!(" --debug Сборка с профилем debug");
println!(" --release Сборка с профилем release");
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Build with debug profile");
println!(" --release Build with release profile");
println!();
println!("{}", colored("ПРИМЕРЫ:", &green_bold));
println!("{}", colored(" dcr new hello", &cyan_bold));
println!("{}", colored(" dcr build --release", &cyan_bold));
println!("{}", colored(" dcr run --debug", &cyan_bold));
printc("EXAMPLES:", BOLD_GREEN);
printc(" dcr new hello", BOLD_CYAN);
printc(" dcr build --release", BOLD_CYAN);
printc(" dcr run --debug", BOLD_CYAN);
println!();
println!("{}", colored("ПОДСКАЗКА:", &green_bold));
println!(" Запусти 'dcr <команда> --help' для справки по команде.");
printc("TIP:", BOLD_GREEN);
println!(" Run 'dcr <command> --help' for command-specific help.");
0
}

View File

@@ -1,19 +1,15 @@
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::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, BRIGHT_RED, colored, printc};
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 {
let red_bold = BRIGHT_RED.to_owned() + BOLD;
let green_bold = BRIGHT_GREEN.to_owned() + BOLD;
let cyan_bold = BRIGHT_CYAN.to_owned() + BOLD;
if !args.is_empty() {
println!(
"{}: команда не поддерживает доп. аргументы",
colored("error", &red_bold)
);
warn("Command does not support additional arguments");
return 1;
}
@@ -24,73 +20,61 @@ pub fn init(args: &[String]) -> i32 {
.unwrap_or_else(|| "project".to_string());
if !items.is_empty() {
println!("{}: директория не пуста", colored("error", &red_bold));
error("Directory not empty");
return 1;
}
let cwd = std::env::current_dir()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string());
println!("Инициализация проекта в {cwd}");
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(_) => {
println!(
"{}: не удалось создать dcr.toml",
colored("error", &red_bold)
);
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()
{
println!(
"{}: не удалось записать dcr.toml",
colored("error", &red_bold)
);
error("Failed to write dcr.toml");
return 1;
}
println!(
" {} Создан файл {}",
colored("✔", &green_bold),
colored("dcr.toml", &cyan_bold)
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN)
);
if fs::create_dir("src").is_err() {
println!("{}: не удалось создать src", colored("error", &red_bold));
error("Failed to create src/");
return 1;
}
let mut main_c = match fs::File::create("./src/main.c") {
Ok(file) => file,
Err(_) => {
println!(
"{}: не удалось создать src/main.c",
colored("error", &red_bold)
);
error("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
println!(
"{}: не удалось записать src/main.c",
colored("error", &red_bold)
);
error("Failed to write src/main.c");
return 1;
}
println!(
" {} Создан файл {}\n",
colored("✔", &green_bold),
colored("src/main.c", &cyan_bold)
" {} Created file {}\n",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
println!(
"Проект `{}` успешно создан\n",
colored(&project_name, &green_bold)
"Project `{}` successfully created\n",
colored(project_name.as_str(), BOLD_GREEN)
);
printc("Следуюший шаг:", &green_bold);
printc(" dcr run", &cyan_bold);
printc("Next step:", BOLD_GREEN);
printc(" dcr run", BOLD_CYAN);
0
}

View File

@@ -1,123 +1,101 @@
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::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, BRIGHT_RED, colored, printc};
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();
let red_bold = BRIGHT_RED.to_owned() + BOLD;
let green_bold = BRIGHT_GREEN.to_owned() + BOLD;
let cyan_bold = BRIGHT_CYAN.to_owned() + BOLD;
if args.is_empty() {
println!(
"{}: не указанно название проекта",
colored("error", &red_bold)
);
error("Project name not specified");
return 1;
}
if args.len() > 1 {
println!(
"{}: команда не поддерживает доп. аргументы",
colored("error", &red_bold)
);
warn("Command does not support additional arguments");
return 1;
}
let project_name = &args[0];
println!(
"Создание проекта `{}`...",
colored(project_name, &green_bold)
"Creating a Project `{}`...",
colored(project_name, BOLD_CYAN)
);
if items.contains(project_name) {
error(&format!(
"Directory `{}` already exists\n",
colored(project_name, BOLD_CYAN)
));
printc("Подсказка:", BOLD_CYAN);
println!(
"{}: директория `{}` уже существует\n",
colored("error", &red_bold),
colored(project_name, &green_bold)
);
printc("Подсказка:", &green_bold);
println!(
" Используй `{}` для инициализации существующего проекта\n или задай другое имя проекта",
colored("dcr init", &cyan_bold)
" Use `{}` to initialize an existing project\n or specify a different project name",
colored("dcr init", BOLD_CYAN)
);
return 1;
}
if fs::create_dir(project_name).is_err() {
println!(
"{}: не удалось создать директорию",
colored("error", &red_bold)
);
error("Failed to create directory");
return 1;
}
println!(
" {} Создана директория {}",
colored("✔", &green_bold),
" {} Directory created {}",
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(_) => {
println!(
"{}: не удалось создать dcr.toml",
colored("error", &red_bold)
);
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()
{
println!(
"{}: не удалось записать dcr.toml",
colored("error", &red_bold)
);
error("Failed to write dcr.toml");
return 1;
}
println!(
" {} Создан файл {}",
colored("✔", &green_bold),
colored("dcr.toml", &cyan_bold)
" {} Created file {}",
colored("✔", BOLD_GREEN),
colored("dcr.toml", BOLD_CYAN)
);
if fs::create_dir_all(format!("./{project_name}/src")).is_err() {
println!("{}: не удалось создать src", colored("error", &red_bold));
error("Failed to create src/");
return 1;
}
let main_c_path = format!("./{project_name}/src/main.c");
let mut main_c = match fs::File::create(&main_c_path) {
Ok(file) => file,
Err(_) => {
println!(
"{}: не удалось создать src/main.c",
colored("error", &red_bold)
);
error("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
println!(
"{}: не удалось записать src/main.c",
colored("error", &red_bold)
);
error("Failed to write src/main.c");
return 1;
}
println!(
" {} Создан файл {}\n",
colored("✔", &green_bold),
colored("src/main.c", &cyan_bold)
" {} Created file {}\n",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
println!(
"Проект `{}` успешно создан\n",
colored(project_name, &green_bold)
"Project `{}` successfully created\n",
colored(project_name, BOLD_GREEN)
);
printc("Следуюший шаг:", &green_bold);
printc(&format!(" cd {project_name}\n dcr run"), &cyan_bold);
printc("Next step:", BOLD_GREEN);
printc(&format!(" cd {}\n dcr run", project_name), BOLD_CYAN);
0
}

View File

@@ -1,39 +1,55 @@
use crate::cli::build::build;
use crate::config::{PROFILE, flags};
use crate::core::config::Config;
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_RED, colored};
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use std::process::Command;
pub fn run(args: &[String]) -> i32 {
let red_bold = BRIGHT_RED.to_owned() + BOLD;
let mut active_profile = PROFILE.to_string();
let items = check_dir(None).unwrap_or_default();
if !items.contains(&"dcr.toml".to_string()) {
println!("{}: не найден файл dcr.toml", colored("error", &red_bold));
error("dcr.toml file not found");
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("--");
if flags(candidate).is_some() {
active_profile = candidate.to_string();
} else {
println!("{}: неизвестный флаг сборки", colored("error", &red_bold));
warn("Unknown build flag");
return 1;
}
} else {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
warn("Unknown argument");
return 1;
}
}
let build_status = build(args);
if build_status == 0 {
println!("\n Запуск target/{active_profile}/main");
println!(
"\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);
@@ -43,13 +59,13 @@ pub fn run(args: &[String]) -> i32 {
.unwrap_or_default()
.contains(&active_profile)
{
println!("Запуск последнего релиза");
return Command::new(format!("./target/{active_profile}/main"))
warn("Launch of the latest release");
return Command::new(format!("./target/{active_profile}/{project_name}"))
.status()
.map(|status| status.code().unwrap_or(1))
.unwrap_or(1);
}
println!("Исправьте ошибки в коде для запуска проекта");
error("Fix errors in the code to run the project");
0
}

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 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>
int main(void) {

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-"],
_ => &[],
}
}

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

@@ -0,0 +1,262 @@
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_STANDARD: &str = "c11";
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"] {
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(())
}
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 = format_toml(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()),
);
let mut build = Map::new();
build.insert(
"language".to_string(),
Value::String(DEFAULT_LANGUAGE.to_string()),
);
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<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> {
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()))
}
}

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

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

View File

@@ -1,5 +1,8 @@
use crate::utils::log::error;
mod cli;
mod config;
mod core;
mod platform;
mod utils;
fn main() {
@@ -25,7 +28,7 @@ fn main() {
"--help" => cli::help::help(),
"--update" => cli::flag_update::flag_update(rest),
_ => {
println!("Неизвестная команда или аргумент");
error("Unknown command or argument");
0
}
};

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

View File

@@ -1,6 +1,7 @@
use std::io;
use std::path::PathBuf;
#[allow(dead_code)]
pub fn check_dir(dir: Option<&str>) -> io::Result<Vec<String>> {
let path: PathBuf = match dir {
None | Some(".") | Some("./") => std::env::current_dir()?,

View File

@@ -1,30 +1,24 @@
use crate::utils::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, BRIGHT_YELLOW, colored};
use crate::utils::text::{BOLD_CYAN, colored};
#[allow(dead_code)]
pub fn error(msg: &str) {
println!(
"{}: {msg}",
colored("error", &(BRIGHT_RED.to_owned() + BOLD))
);
println!("{}: {msg}", colored("error", BOLD_CYAN),);
}
#[allow(dead_code)]
pub fn warn(msg: &str) {
println!(
"{}: {msg}",
colored("warn", &(BRIGHT_YELLOW.to_owned() + BOLD))
);
println!("{}: {msg}", colored("warn", BOLD_CYAN),);
}
#[allow(dead_code)]
pub fn info(msg: &str) {
println!("{}: {msg}", colored("info", BOLD));
}
#[allow(dead_code)]
pub fn ok(msg: &str) {
println!(
"{}: {msg}",
colored("ok", &(BRIGHT_GREEN.to_owned() + BOLD))
);
}
// #[allow(dead_code)]
// pub fn info(msg: &str) {
// println!("{}: {msg}", colored("info", BOLD));
// }
//
// #[allow(dead_code)]
// pub fn ok(msg: &str) {
// println!(
// "{}: {msg}",
// colored("ok", &(BRIGHT_GREEN.to_owned() + BOLD))
// );
// }

View File

@@ -1,16 +1,32 @@
#[allow(dead_code)]
pub const RESET: &str = "\x1b[0m";
#[allow(dead_code)]
pub const BOLD: &str = "\x1b[1m";
#[allow(dead_code)]
pub const BRIGHT_RED: &str = "\x1b[91m";
#[allow(dead_code)]
pub const BRIGHT_GREEN: &str = "\x1b[92m";
#[allow(dead_code)]
pub const BRIGHT_YELLOW: &str = "\x1b[93m";
#[allow(dead_code)]
pub const BRIGHT_CYAN: &str = "\x1b[96m";
#[allow(dead_code)]
pub const BOLD_RED: &str = "\x1b[1m\x1b[91m";
#[allow(dead_code)]
pub const BOLD_GREEN: &str = "\x1b[1m\x1b[92m";
#[allow(dead_code)]
pub const BOLD_YELLOW: &str = "\x1b[1m\x1b[93m";
#[allow(dead_code)]
pub const BOLD_CYAN: &str = "\x1b[1m\x1b[96m";
#[allow(dead_code)]
pub fn colored(msg: &str, style: &str) -> String {
format!("{style}{msg}{RESET}")
}
#[allow(dead_code)]
pub fn printc(msg: &str, style: &str) {
println!("{style}{msg}{RESET}");
}