10 Commits

48 changed files with 3259 additions and 736 deletions

28
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: CI
on:
push:
branches:
- master
- main
pull_request:
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Format
run: cargo fmt --all --check
- name: Lint
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Check
run: cargo check --all-targets --all-features

58
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Release
on:
push:
tags:
- "v*"
permissions:
contents: write
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
asset_name: dcr-x86_64-unknown-linux-gnu
- os: macos-13
target: x86_64-apple-darwin
asset_name: dcr-x86_64-apple-darwin
- os: macos-14
target: aarch64-apple-darwin
asset_name: dcr-aarch64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
asset_name: dcr-x86_64-pc-windows-msvc.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Build
run: cargo build --release --target ${{ matrix.target }}
- name: Prepare Artifact (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
cp target/${{ matrix.target }}/release/dcr ${{ matrix.asset_name }}
- name: Prepare Artifact (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Copy-Item "target/${{ matrix.target }}/release/dcr.exe" "${{ matrix.asset_name }}"
- name: Upload Release Asset
uses: softprops/action-gh-release@v2
with:
files: ${{ matrix.asset_name }}

14
.gitignore vendored
View File

@@ -1,9 +1,5 @@
__pycache__/ target/
build/ sandbox/**
dist/ .idea/
wheels/ .vscode/
*.egg-info .env
.venv
sandbox/*
.ruff_cache/
.vscode/

View File

@@ -1 +0,0 @@
3.14.2

View File

@@ -1,5 +1,28 @@
# Changelog # Changelog
## [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
Добавлено:
- Добавлен `install.ps1` для Windows
- Поддержка GNU\Linux, Windows, MacOS(x86_64/arm)
ВАЖНО:
- Код перенесен с помощью нейронных сетей, в будущих версиях будет баг-фикс и изменение логики
## [0.1.2] - 2026-02-12 ## [0.1.2] - 2026-02-12
Добавлено: Добавлено:
- Команда обновления - Команда обновления

57
CONTRIBUTING.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

1670
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "dcr"
version = "0.2.1"
edition = "2024"
build = "build.rs"
[dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
serde = { version = "1", features = ["derive"] }
self-replace = "1"

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.

154
README.md
View File

@@ -1,87 +1,113 @@
# DCR (Dexoron Cargo Realization) # DCR (Dexoron Cargo Realization)
DCR — это утилита для управления проектами на C. Она задает единый формат проекта, дает команды для создания, сборки и запуска, и использует один конфигурационный файл `dcr.toml`. DCR is a utility for managing C/C++ projects in a Cargo-like style.
Проект находится на стадии MVP. Текущая реализация написана на Python и в будущем будет переписана. 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
## Возможности ## Features
- Создание нового проекта или инициализация текущей директории - Create a new project or initialize the current directory
- Сборка проекта в профилях `debug` и `release` - Build a project with `debug` and `release` profiles
- Запуск собранного бинарника - Run the compiled binary
- Очистка результатов сборки - Clean build artifacts
- Генерация минимального шаблона C-проекта - Generate a minimal C project template
- Update the binary via `dcr --update` (GitHub Releases)
## Установка ## 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
**GNU/Linux** ### From Source
```sh
curl -L dcr.zov.tatar | bash
```
### Самостоятельная установка
```sh ```sh
git clone https://github.com/dexoron/dcr.git "~/.local/share/dcr" git clone https://github.com/dexoron/dcr.git
chmod +x "~/.local/share/dcr/dcr" cd dcr
ln -sf ~/.local/share/dcr/dcr ~/.local/bin cargo build --release
mkdir -p ~/.local/bin
ln -sf "$PWD/target/release/dcr" ~/.local/bin/dcr
``` ```
## Быстрый старт ### Via `install.sh` (Linux/macOS)
Создать новый проект:
```sh
curl -fsSL https://dcr.dexoron.su/install.sh | bash
```
### Via `install.ps1` (Windows)
```powershell
irm https://dcr.dexoron.su/install.ps1 | iex
```
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`
## Quick Start
Create a new project:
`dcr new hello` `dcr new hello`
Или инициализировать текущую директорию (директория должна быть пустой): Or initialize the current directory (the directory must be empty):
`dcr init` `dcr init`
Структура проекта: Project structure:
```
```txt
hello/ hello/
- src/ - src/
- - main.c - - main.c
- dcr.toml - dcr.toml
``` ```
Сборка и запуск проекта: Build and run the project:
`dcr run` или `dcr run --release` - для сбрки с оптимизацией `dcr run` or `dcr run --release`
## Commands
## Команды
### `dcr new <name>` ### `dcr new <name>`
Создает проект в текущей директории с указанным именем. Creates a project with the specified name in the current directory.
### `dcr init` ### `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]` ### `dcr build [profile]`
Собирает проект. Если профиль не указан, используется `--debug`. Builds the project. If no profile is specified, `--debug` is used.
### `dcr run [profile]` ### `dcr run [profile]`
Собирает проект и запускает бинарник. Если профиль не указан, используется `--debug`. Builds the project and runs the binary. If no profile is specified, `--debug` is used.
Запуск вручную: Run manually:
`./target/<profile>/main` `./target/<profile>/main`
### `dcr clean` ### `dcr clean`
Удаляет папку `target` в корне проекта. Removes the `target` directory in the project root.
## Профили сборки ## Build Profiles
Поддерживаются два профиля: Two profiles are supported:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG` - `--debug` (default) - flags: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
- `--release` — флаги: `-O3 -DNDEBUG -march=native` - `--release` - flags: `-O3 -DNDEBUG -march=native`
## Конфигурация ## Configuration
Основной файл проекта — `dcr.toml`. Сейчас он создается автоматически и служит точкой входа для будущих настроек. The main project file is `dcr.toml`.
Пример `dcr.toml`: Example `dcr.toml`:
```
```toml
[package] [package]
name = "hello" name = "hello"
version = "0.1.0" version = "0.1.0"
@@ -91,34 +117,12 @@ compiler = "clang"
[dependencies] [dependencies]
``` ```
## Установка в PATH ## Requirements
Чтобы вызывать `dcr` из любой директории, можно сделать symlink в `~/.local/bin`: - Rust toolchain (`rustc`, `cargo`) - for building DCR from source
```sh - C compiler (`clang`, `gcc`, or others)
ln -s "$PWD/dcr" ~/.local/bin/dcr
```
Убедитесь, что `~/.local/bin` находится в `PATH`.
## Пример проекта ## Releases
`src/main.c` содержит минимальный `Hello, World!`: Releases are built automatically via GitHub Actions (`.github/workflows/release.yml`) when a tag matching `v*` is pushed.
```c
#include <stdio.h>
int main(void) { ## License
puts("Hello, World!"); See `LICENSE`.
return 0;
}
```
## Требования
- C-компилятор (например, `gcc` или `clang`)
- Python 3.14.2 (для текущей версии DCR)
- uv (менеджер проекта для Python)
## Статус и планы
Проект в стадии MVP. Планируется переписывание на более подходящий язык и расширение функциональности:
- более гибкая конфигурация через `dcr.toml`
- расширяемые профили сборки
- менеджер пакетов
## Лицензия
См. файл `LICENSE`.

4
build.rs Normal file
View File

@@ -0,0 +1,4 @@
fn main() {
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown-target".to_string());
println!("cargo:rustc-env=DCR_TARGET={target}");
}

10
dcr
View File

@@ -1,10 +0,0 @@
#!/usr/bin/env bash
BASE_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
if command -v python3 >/dev/null 2>&1; then
PYTHON_BIN="python3"
else
PYTHON_BIN="$BASE_DIR/Python/bin/python3"
fi
PYTHONPATH="$BASE_DIR/src" exec "$PYTHON_BIN" -m dcr.main "$@"

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` в соответствующем репозитории.

128
docs/README_ru.md Normal file
View File

@@ -0,0 +1,128 @@
# DCR (Dexoron Cargo Realization)
DCR — это утилита для управления проектами на C/C++ в стиле Cargo.
Текущая реализация написана на Rust.
## Зачем DCR
- Единая структура проекта без ручной настройки
- Простые команды для типовых задач
- Прозрачная компиляция и предсказуемые профили сборки
## Возможности
- Создание нового проекта или инициализация текущей директории
- Сборка проекта в профилях `debug` и `release`
- Запуск собранного бинарника
- Очистка результатов сборки
- Генерация минимального шаблона C-проекта
- Обновление бинарника через `dcr --update` (GitHub Releases)
## Поддерживаемые платформы
- Linux: `x86_64-unknown-linux-gnu`
- macOS Intel: `x86_64-apple-darwin`
- macOS Apple Silicon: `aarch64-apple-darwin`
- Windows: `x86_64-pc-windows-msvc`
## Установка
### Из исходников
```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 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>/main`
### `dcr clean`
Удаляет папку `target` в корне проекта.
## Профили сборки
Поддерживаются два профиля:
- `--debug` (по умолчанию) — флаги: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDEBUG`
- `--release` — флаги: `-O3 -DNDEBUG -march=native`
## Конфигурация
Основной файл проекта — `dcr.toml`.
Пример `dcr.toml`:
```toml
[package]
name = "hello"
version = "0.1.0"
language = "c"
compiler = "clang"
[dependencies]
```
## Требования
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
- C-компилятор (`clang`, `gcc` или другие)
## Релизы
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.
## Лицензия
См. файл `LICENSE`.

113
install.ps1 Normal file
View File

@@ -0,0 +1,113 @@
$ErrorActionPreference = 'Stop'
$RepoUrl = 'https://github.com/dexoron/dcr'
$ApiUrl = 'https://api.github.com/repos/dexoron/dcr/releases/latest'
$InstallPath = Join-Path $env:LOCALAPPDATA 'dcr'
$BinPath = Join-Path $env:USERPROFILE '.local\bin'
$BinaryPath = Join-Path $InstallPath 'dcr.exe'
function Info($msg) { Write-Host "[INFO] $msg" -ForegroundColor Cyan }
function Ok($msg) { Write-Host "[OK] $msg" -ForegroundColor Green }
function Warn($msg) { Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Fail($msg) { Write-Host "[ERROR] $msg" -ForegroundColor Red; exit 1 }
function Require-Cmd($name) {
if (-not (Get-Command $name -ErrorAction SilentlyContinue)) {
Fail "Not found: $name"
}
}
function Select-Mode {
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 'Unknown option' }
}
}
function Get-Target {
if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') {
Fail "Only x86_64 Windows is supported, current architecture: $env:PROCESSOR_ARCHITECTURE"
}
return 'x86_64-pc-windows-msvc'
}
function Install-FromRelease {
param([string]$target)
New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
$assetName = "dcr-$target.exe"
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 "Asset $assetName not found in release $($release.tag_name)"
}
Info "Downloading $assetName..."
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $BinaryPath
Ok "Binary downloaded: $assetName"
}
function Install-FromSource {
param([string]$target)
Require-Cmd 'git'
Require-Cmd 'cargo'
$tmp = Join-Path $env:TEMP 'dcr-install'
if (Test-Path $tmp) { Remove-Item -Recurse -Force $tmp }
Info 'Cloning repository...'
git clone --depth 1 $RepoUrl $tmp | Out-Null
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 'Binary installed from source'
Remove-Item -Recurse -Force $tmp
}
function Setup-Path {
New-Item -ItemType Directory -Path $BinPath -Force | Out-Null
$linkPath = Join-Path $BinPath 'dcr.exe'
Copy-Item $BinaryPath $linkPath -Force
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not $userPath) { $userPath = '' }
if (-not ($userPath -split ';' | Where-Object { $_ -eq $BinPath })) {
$newPath = if ($userPath) { "$userPath;$BinPath" } else { $BinPath }
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
$env:Path = "$env:Path;$BinPath"
Warn "Added path to PATH: $BinPath"
}
Ok "dcr command is available via $linkPath"
}
Info 'Starting DCR installation'
$mode = Select-Mode
$target = Get-Target
if ($mode -eq 'build') {
Install-FromSource -target $target
} else {
Install-FromRelease -target $target
}
Setup-Path
Ok 'Installation completed successfully'

View File

@@ -1,14 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
# ===== CONFIG ===== TMPDIR="/tmp/dcr-install"
TMPDIR="/tmp/dcr-temp"
INSTALL_PATH="$HOME/.local/share/dcr" INSTALL_PATH="$HOME/.local/share/dcr"
PYTHON_DIR="$INSTALL_PATH/Python"
BINPATH="$HOME/.local/bin" BINPATH="$HOME/.local/bin"
LOGFILE="$HOME/.cache/dcr-install.log" LOGFILE="$HOME/.cache/dcr-install.log"
REPO_URL="https://github.com/dexoron/dcr"
PYTHON_DOWNLOAD_URL="https://github.com/astral-sh/python-build-standalone/releases/download/20260203/cpython-3.10.19+20260203-x86_64-unknown-linux-gnu-install_only.tar.gz"
GITHUB_API="https://api.github.com/repos/dexoron/dcr/releases/latest" GITHUB_API="https://api.github.com/repos/dexoron/dcr/releases/latest"
RED='\033[0;31m' RED='\033[0;31m'
@@ -25,127 +22,98 @@ success() { echo -e "${GREEN}✔ $1${NC}"; }
warn() { echo -e "${YELLOW}⚠ $1${NC}"; } warn() { echo -e "${YELLOW}⚠ $1${NC}"; }
error() { echo -e "${RED}✖ $1${NC}"; } error() { echo -e "${RED}✖ $1${NC}"; }
trap 'error "Ошибка на строке $LINENO"; exit 1' ERR trap 'error "Error on line $LINENO"; exit 1' ERR
check_dependencies() {
log "Проверка зависимостей..."
command -v curl >/dev/null 2>&1 || { error "curl не установлен"; exit 1; }
command -v tar >/dev/null 2>&1 || { error "tar не установлен"; exit 1; }
success "Зависимости в порядке"
}
check_os() { check_os() {
if [[ "$(uname -s)" != "Linux" ]]; then case "$(uname -s)" in
error "Поддерживается только Linux" Linux|Darwin) ;;
exit 1 *) error "Only Linux and macOS are supported"; exit 1 ;;
fi esac
} }
get_latest_version() { detect_target() {
curl -fsSL "$GITHUB_API" | sed -n 's/.*"tag_name": "\(.*\)".*/\1/p' local os arch
os="$(uname -s)"
arch="$(uname -m)"
case "$os:$arch" in
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 "Unsupported platform: $os/$arch"; exit 1 ;;
esac
ASSET_NAME="dcr-${TARGET_TRIPLE}"
} }
get_installed_version() { check_common_dependencies() {
if [[ -f "$INSTALL_PATH/VERSION" ]]; then command -v curl >/dev/null 2>&1 || { error "curl is not installed"; exit 1; }
cat "$INSTALL_PATH/VERSION"
else
echo ""
fi
} }
install_python() { check_build_dependencies() {
log "Python не найден, установка встроенного Python..." 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; }
mkdir -p "$PYTHON_DIR"
mkdir -p "$TMPDIR"
curl -fsSL "$PYTHON_DOWNLOAD_URL" -o "$TMPDIR/python.tar.gz"
tar -xf "$TMPDIR/python.tar.gz" -C "$PYTHON_DIR" --strip-components=1
rm -f "$TMPDIR/python.tar.gz"
success "Встроенный Python установлен"
} }
install_app() { prepare_sources() {
log "Получение последнего релиза..." log "Fetching sources..."
rm -rf "$TMPDIR"
git clone --depth 1 "$REPO_URL" "$TMPDIR"
success "Sources fetched"
}
TAG=$(get_latest_version) build_binary() {
log "Building release binary..."
(cd "$TMPDIR" && cargo build --release)
success "Build completed"
}
if [[ -z "$TAG" ]]; then fetch_latest_release_json() {
error "Не удалось определить версию релиза" curl -fsSL "$GITHUB_API"
}
download_binary() {
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 "Failed to determine release version"
exit 1 exit 1
fi fi
INSTALLED_VERSION=$(get_installed_version) download_url="$(printf '%s\n' "$release_json" | sed -n "s#.*\"browser_download_url\": \"\([^\"]*${ASSET_NAME}\)\".*#\1#p" | head -n1)"
if [[ -z "$download_url" ]]; then
if [[ -n "$INSTALLED_VERSION" ]]; then error "Asset ${ASSET_NAME} not found in release ${tag}"
log "Установленная версия: $INSTALLED_VERSION" exit 1
log "Последняя версия: $TAG"
if [[ "$INSTALLED_VERSION" == "$TAG" ]]; then
success "Установлена последняя версия ($TAG), обновление не требуется"
exit 0
else
warn "Найдена устаревшая версия, выполняется обновление"
fi
else
log "DCR не установлен, выполняется установка"
fi fi
mkdir -p "$TMPDIR"
curl -fsSL -o "$TMPDIR/sources.tar.gz" \
"https://github.com/dexoron/dcr/archive/refs/tags/$TAG.tar.gz"
rm -rf "$INSTALL_PATH"
mkdir -p "$INSTALL_PATH" mkdir -p "$INSTALL_PATH"
curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
tar -xf "$TMPDIR/sources.tar.gz" -C "$INSTALL_PATH" --strip-components=1
rm -f "$TMPDIR/sources.tar.gz"
echo "$TAG" > "$INSTALL_PATH/VERSION"
success "Приложение установлено (версия $TAG)"
}
install_wrapper() {
log "Создание launcher..."
mkdir -p "$INSTALL_PATH"
cat > "$INSTALL_PATH/dcr" <<'EOF'
#!/usr/bin/env bash
BASE_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
if command -v python3 >/dev/null 2>&1; then
PYTHON_BIN="python3"
else
PYTHON_BIN="$BASE_DIR/Python/bin/python3"
fi
PYTHONPATH="$BASE_DIR/src" exec "$PYTHON_BIN" -m dcr.main "$@"
EOF
chmod +x "$INSTALL_PATH/dcr" chmod +x "$INSTALL_PATH/dcr"
success "Binary ${ASSET_NAME} downloaded (${tag})"
}
success "Launcher создан" install_built_binary() {
mkdir -p "$INSTALL_PATH"
cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
chmod +x "$INSTALL_PATH/dcr"
success "Binary installed from source"
} }
install_link() { install_link() {
log "Создание симлинка..." log "Creating symlink..."
mkdir -p "$BINPATH" mkdir -p "$BINPATH"
ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr" ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
success "Command 'dcr' added to $BINPATH"
success "Команда 'dcr' добавлена в $BINPATH"
} }
check_path() { check_path() {
if ! echo "$PATH" | grep -q "$BINPATH"; then if ! echo "$PATH" | grep -q "$BINPATH"; then
warn "Каталог $BINPATH не найден в PATH" warn "Directory $BINPATH not found in PATH"
echo "Добавь в ~/.bashrc или ~/.zshrc:" echo "Add this to ~/.bashrc or ~/.zshrc:"
echo "export PATH=\"$BINPATH:\$PATH\"" echo "export PATH=\"$BINPATH:\$PATH\""
fi fi
} }
@@ -154,29 +122,43 @@ cleanup() {
rm -rf "$TMPDIR" 2>/dev/null || true rm -rf "$TMPDIR" 2>/dev/null || true
} }
select_install_mode() {
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 "Unknown option"; exit 1 ;;
esac
}
main() { main() {
log "Запуск установки DCR" log "Starting DCR installation"
check_os check_os
check_dependencies detect_target
check_common_dependencies
select_install_mode
cleanup cleanup
install_app if [[ "$INSTALL_MODE" == "build" ]]; then
check_build_dependencies
if ! command -v python3 >/dev/null 2>&1; then prepare_sources
install_python build_binary
install_built_binary
else else
success "Найден системный Python" download_binary
fi fi
install_wrapper
install_link install_link
check_path check_path
cleanup cleanup
success "Установка завершена успешно" success "Installation completed successfully"
log "Лог сохранён в $LOGFILE" log "Log saved to $LOGFILE"
} }
main "$@" main "$@"

View File

@@ -1,11 +0,0 @@
[project]
name = "dcr"
version = "0.1.0"
description = "Утилита для управления проектами для C/C++"
readme = "README.md"
license = "LICENSE"
authors = [
{ name = "Dexoron" },
{ name = "F1F1FISHKA" }
]
requires-python = ">=3.10"

View File

81
src/cli/build.rs Normal file
View File

@@ -0,0 +1,81 @@
use crate::config::{PROFILE, PROJECT_COMPILER, flags};
use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use std::fs;
use std::process::Command;
use std::time::Instant;
pub fn build(args: &[String]) -> i32 {
let mut active_profile = PROFILE.to_string();
let 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()) {
error("dcr.toml file not found");
return 1;
}
if let Some(first_arg) = args.first() {
if first_arg.starts_with("--") {
let candidate = first_arg.trim_start_matches("--");
if flags(candidate).is_some() {
active_profile = candidate.to_string();
} else {
warn("Unknown build flag");
return 1;
}
} else {
warn("Unknown argument");
return 1;
}
}
println!(
" Building project `{}`\n Profile: {}\n Compiler: {}\n",
colored(&project_name, BOLD_GREEN),
colored(&active_profile, BOLD_GREEN),
colored(PROJECT_COMPILER, BOLD_GREEN)
);
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}"));
}
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!(
" {} Build completed successfully in {} seconds",
colored("✔", BOLD_GREEN),
colored(&times.to_string(), BOLD_GREEN)
);
return 0;
}
_ => {
error("Build failed");
return 1;
}
}
}
0
}

63
src/cli/clean.rs Normal file
View File

@@ -0,0 +1,63 @@
use crate::config::flags;
use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use std::fs;
pub fn clean(args: &[String]) -> i32 {
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 args.len() > 1 {
error("Unknown argument");
return 1;
}
if !items.contains(&"dcr.toml".to_string()) {
error("dcr.toml file not found");
return 1;
}
println!(
" Cleaning project `{}`",
colored(&project_name, BOLD_GREEN)
);
if !items.contains(&"target".to_string()) {
warn("Directory target not found");
return 1;
}
if let Some(arg) = args.first() {
let mut profile = arg.clone();
if profile.starts_with("--") {
profile = profile.trim_start_matches("--").to_string();
}
if flags(&profile).is_none() {
error("Unknown profile");
return 1;
}
let target_items = check_dir(Some("target")).unwrap_or_default();
if !target_items.contains(&profile) {
warn(&format!("Directory target/{profile} not found"));
return 1;
}
println!(" Profile: {}", colored(&profile, BOLD_GREEN));
let _ = fs::remove_dir_all(format!("target/{profile}"));
println!(
"{} Removed directory target/{profile}",
colored("\n ✔", BOLD_GREEN)
);
return 0;
}
let _ = fs::remove_dir_all("target");
println!(
"{} Removed directory target",
colored("\n ✔", BOLD_GREEN)
);
0
}

159
src/cli/flag_update.rs Normal file
View File

@@ -0,0 +1,159 @@
use crate::utils::log::{error, warn};
use reqwest::blocking::Client;
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/dexoron/dcr/releases/latest";
#[derive(Debug, Deserialize)]
struct Release {
tag_name: String,
assets: Vec<ReleaseAsset>,
}
#[derive(Debug, Deserialize)]
struct ReleaseAsset {
name: String,
browser_download_url: String,
}
pub fn flag_update(args: &[String]) -> i32 {
if !args.is_empty() {
warn("Command does not support additional arguments");
return 1;
}
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(_) => {
error("Failed to initialize HTTP client");
return 1;
}
};
let release = match fetch_latest_release(&client) {
Ok(release) => release,
Err(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!("Latest version is already installed: {current_version}");
return 0;
}
let candidate_names = asset_candidates(target);
let Some(asset) = release
.assets
.iter()
.find(|asset| candidate_names.iter().any(|name| name == &asset.name))
else {
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) => {
error(&format!("Failed to download update: {err}"));
return 1;
}
};
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);
if fs::write(&temp_path, &bytes).is_err() {
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);
error("Failed to replace current binary");
return 1;
}
let _ = fs::remove_file(&temp_path);
println!("Update completed: {current_version} -> {latest_version}");
0
}
fn fetch_latest_release(client: &Client) -> Result<Release, String> {
let response = client
.get(LATEST_RELEASE_URL)
.send()
.map_err(|_| "GitHub API request failed".to_string())?;
if !response.status().is_success() {
return Err(format!("GitHub API returned status {}", response.status()));
}
response
.json::<Release>()
.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(|_| "Download request failed".to_string())?;
if !response.status().is_success() {
return Err(format!("Download returned status {}", response.status()));
}
response
.bytes()
.map(|bytes| bytes.to_vec())
.map_err(|_| "Failed to read downloaded data".to_string())
}
fn asset_candidates(target: &str) -> Vec<String> {
let mut names = vec![format!("dcr-{target}")];
if target == "x86_64-pc-windows-msvc" {
names.push("dcr-x86_64-pc-windows-msvc.exe".to_string());
}
names
}
fn temp_binary_path(current_exe: &Path) -> PathBuf {
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|value| value.as_millis())
.unwrap_or(0);
let mut extension = format!("new-{stamp}");
if cfg!(windows) {
extension.push_str(".exe");
}
current_exe.with_extension(extension)
}
fn set_executable_permissions(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Ok(meta) = fs::metadata(path) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = fs::set_permissions(path, perms);
}
}
}

33
src/cli/help.rs Normal file
View File

@@ -0,0 +1,33 @@
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, printc};
pub fn help() -> i32 {
println!("DCR (Dexoron Cargo Realization)");
println!("C project manager inspired by Cargo.");
println!();
printc("USAGE:", BOLD_GREEN);
printc(" dcr <command> [options]", BOLD_CYAN);
println!();
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!();
printc("OPTIONS:", BOLD_GREEN);
println!(" --debug Build with debug profile");
println!(" --release Build with release profile");
println!();
printc("EXAMPLES:", BOLD_GREEN);
printc(" dcr new hello", BOLD_CYAN);
printc(" dcr build --release", BOLD_CYAN);
printc(" dcr run --debug", BOLD_CYAN);
println!();
printc("TIP:", BOLD_GREEN);
println!(" Run 'dcr <command> --help' for command-specific help.");
0
}

78
src/cli/init.rs Normal file
View File

@@ -0,0 +1,78 @@
use crate::config::{FILE_MAIN_C, file_dcr_toml};
use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs;
use std::io::Write;
pub fn init(args: &[String]) -> i32 {
if !args.is_empty() {
warn("Command does not support additional arguments");
return 1;
}
let items = check_dir(None).unwrap_or_default();
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());
if !items.is_empty() {
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!("Initializing the project in {cwd}");
let mut dcr_toml = match fs::File::create("./dcr.toml") {
Ok(file) => file,
Err(_) => {
error("Failed to create dcr.toml");
return 1;
}
};
if dcr_toml
.write_all(file_dcr_toml(&project_name).as_bytes())
.is_err()
{
error("Failed to write dcr.toml");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_CYAN),
colored("dcr.toml", BOLD_CYAN)
);
if fs::create_dir("src").is_err() {
error("Failed to create src/");
return 1;
}
let mut main_c = match fs::File::create("./src/main.c") {
Ok(file) => file,
Err(_) => {
error("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
error("Failed to write src/main.c");
return 1;
}
println!(
" {} Created file {}\n",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
println!(
"Project `{}` successfully created\n",
colored(project_name.as_str(), BOLD_GREEN)
);
printc("Next step:", BOLD_GREEN);
printc(" cd {}\n dcr run", project_name.as_str());
0
}

7
src/cli/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod build;
pub mod clean;
pub mod flag_update;
pub mod help;
pub mod init;
pub mod new;
pub mod run;

99
src/cli/new.rs Normal file
View File

@@ -0,0 +1,99 @@
use crate::config::{FILE_MAIN_C, file_dcr_toml};
use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
use std::fs;
use std::io::Write;
pub fn new(args: &[String]) -> i32 {
let items = check_dir(None).unwrap_or_default();
if args.is_empty() {
error("Project name not specified");
return 1;
}
if args.len() > 1 {
warn("Command does not support additional arguments");
return 1;
}
let project_name = &args[0];
println!(
"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!(
" 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() {
error("Failed to create directory");
return 1;
}
println!(
" {} Directory created {}",
colored("✔", BOLD_CYAN),
project_name
);
let toml_path = format!("./{project_name}/dcr.toml");
let mut dcr_toml = match fs::File::create(&toml_path) {
Ok(file) => file,
Err(_) => {
error("Failed to create dcr.toml");
return 1;
}
};
if dcr_toml
.write_all(file_dcr_toml(project_name).as_bytes())
.is_err()
{
error("Failed to write dcr.toml");
return 1;
}
println!(
" {} Created file {}",
colored("✔", BOLD_CYAN),
colored("dcr.toml", BOLD_CYAN)
);
if fs::create_dir_all(format!("./{project_name}/src")).is_err() {
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(_) => {
error("Failed to create src/main.c");
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
error("Failed to write src/main.c");
return 1;
}
println!(
" {} Created file {}\n",
colored("✔", BOLD_GREEN),
colored("src/main.c", BOLD_CYAN)
);
println!(
"Project `{}` successfully created\n",
colored(project_name, BOLD_GREEN)
);
printc("Next step:", BOLD_GREEN);
printc(" cd {}\n dcr run", project_name);
0
}

58
src/cli/run.rs Normal file
View File

@@ -0,0 +1,58 @@
use crate::cli::build::build;
use crate::config::{PROFILE, flags};
use crate::utils::fs::check_dir;
use crate::utils::log::{error, warn};
use crate::utils::text::{BOLD_GREEN, colored};
use std::process::Command;
pub fn run(args: &[String]) -> i32 {
let mut active_profile = PROFILE.to_string();
let items = check_dir(None).unwrap_or_default();
if !items.contains(&"dcr.toml".to_string()) {
error("dcr.toml file not found");
return 1;
}
if let Some(first_arg) = args.first() {
if first_arg.starts_with("--") {
let candidate = first_arg.trim_start_matches("--");
if flags(candidate).is_some() {
active_profile = candidate.to_string();
} else {
warn("Unknown build flag");
return 1;
}
} else {
warn("Unknown argument");
return 1;
}
}
let build_status = build(args);
if build_status == 0 {
println!(
"\n {} target/{active_profile}/main",
colored("Running", BOLD_GREEN)
);
println!("--------------------------------");
return Command::new(format!("./target/{active_profile}/main"))
.status()
.map(|status| status.code().unwrap_or(1))
.unwrap_or(1);
}
if check_dir(Some(&format!("target/{active_profile}")))
.unwrap_or_default()
.contains(&active_profile)
{
warn("Launch of the latest release");
return Command::new(format!("./target/{active_profile}/main"))
.status()
.map(|status| status.code().unwrap_or(1))
.unwrap_or(1);
}
error("Fix errors in the code to run the project");
0
}

43
src/config.rs Normal file
View File

@@ -0,0 +1,43 @@
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) {
printf("Hello World!\n");
return 0;
}
"#;
pub fn flags(profile: &str) -> Option<&'static [&'static str]> {
match profile {
"release" => Some(&["-O3", "-DNDEBUG", "-march=native"]),
"debug" => Some(&[
"-O0",
"-g",
"-Wall",
"-Wextra",
"-fno-omit-frame-pointer",
"-DDEBUG",
]),
_ => None,
}
}

View File

View File

@@ -1,18 +0,0 @@
from .help import help
from .init import init
from .new import new
from .run import run
from .build import build
from .clean import clean
from .flag_update import flag_update
__all__ = [
"help",
"init",
"new",
"run",
"build",
"clean",
"flag_update",
]

View File

@@ -1,70 +0,0 @@
import time
import os
import subprocess
from ..config import flags, profile, c_comp
from ..utils.fs import check_dir
from ..utils.text import colored, BRIGHT_GREEN, BOLD, BRIGHT_RED
def build(args: list[str] | None = None) -> int:
active_profile: str = profile
project_name = os.path.basename(os.getcwd())
if "dcr.toml" not in check_dir():
print(colored("error", BRIGHT_RED + BOLD) + ": не найден файл dcr.toml")
return 1
if args:
if len(args) >= 1 and args[0].startswith("--"):
candidate: str = args[0][2:]
if candidate in flags:
active_profile: str = candidate
else:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный флаг сборки")
return 1
else:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный аргумент")
return 1
print(
" Сборка проекта `"
+ colored(project_name, BRIGHT_GREEN + BOLD)
+ "`\n Профиль: "
+ colored(active_profile, BRIGHT_GREEN + BOLD)
+ "\n Компилятор: "
+ colored(c_comp, BRIGHT_GREEN + BOLD)
+ "\n"
)
if "target" not in check_dir():
os.mkdir("./target")
if active_profile not in check_dir("target"):
os.mkdir(f"./target/{active_profile}")
if "main.c" in check_dir("src"):
compile_flags = flags[active_profile]
start_time: float = time.time()
try:
subprocess.run(
[
c_comp,
"./src/main.c",
*compile_flags,
"-o",
f"./target/{active_profile}/main",
],
check=True,
)
end_time: float = time.time()
times: float = end_time - start_time
times: float = int(times * 100) / 100
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Сборка завершена успешно, за "
+ colored(str(times), BRIGHT_GREEN + BOLD)
+ " секунд"
)
return 0
except:
print(colored("error", BRIGHT_RED + BOLD) + ": сборка не удалась")
return 1
return 0

View File

@@ -1,53 +0,0 @@
import os
import shutil
from ..utils.fs import check_dir
from ..utils.text import colored, BRIGHT_RED, BOLD, BRIGHT_GREEN, BRIGHT_YELLOW
from ..config import flags
def clean(args: list[str] | None = None) -> int:
project_name = os.path.basename(os.getcwd())
items: list[str] = check_dir()
if args and len(args) > 1:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный аргумент")
return 1
if "dcr.toml" not in items:
print(colored("error", BRIGHT_RED + BOLD) + ": не найден файл dcr.toml")
return 1
print(" Очистка проекта `" + colored(project_name, BRIGHT_GREEN + BOLD) + "`")
if "target" not in items:
print(colored("warn", BRIGHT_YELLOW + BOLD) + ": директория target не найдена")
return 1
if args:
profile = args[0]
if profile.startswith("--"):
profile = profile[2:]
if profile not in flags:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный профиль")
return 1
if profile not in check_dir("target"):
print(
colored("warn", BRIGHT_YELLOW + BOLD)
+ f": директория target/{profile} не найдена"
)
return 1
print(" Профиль: " + colored(profile, BRIGHT_GREEN + BOLD))
shutil.rmtree(os.path.join("target", profile))
print(
colored("\n ✔", BRIGHT_GREEN + BOLD)
+ f" Удалена директория target/{profile}"
)
return 0
shutil.rmtree("target")
print(colored("\n ✔", BRIGHT_GREEN + BOLD) + " Удалена директория target")
return 0

View File

@@ -1,23 +0,0 @@
import platform
import shutil
import subprocess
def flag_update(args: list[str] | None = None) -> int:
if args and len(args) > 0:
print("Ошибка: команда не поддежривает аргументы")
return 1
if platform.system() != "Linux":
print("Ошибка: обновление поддерживается только на Linux")
return 1
if shutil.which("curl") is None:
print("Ошибка: не найден curl")
return 1
try:
subprocess.run(
["bash", "-c", "curl -fsSL dcr.zov.tatar | bash"], check=True
)
return 0
except subprocess.CalledProcessError:
print("Ошибка: не удалось выполнить обновление")
return 1

View File

@@ -1,39 +0,0 @@
from ..utils.text import colored, BRIGHT_GREEN, BRIGHT_CYAN, BOLD
def help() -> int:
print(
"\n".join(
[
"DCR (Dexoron Cargo Realization)",
"Менеджер C-проектов, вдохновленный Cargo.",
"",
colored("ИСПОЛЬЗОВАНИЕ:", BRIGHT_GREEN + BOLD),
colored(" dcr <команда> [опции]", BRIGHT_CYAN + BOLD),
"",
colored("КОМАНДЫ:", BRIGHT_GREEN + BOLD),
" new <name> Создать новый проект",
" init Инициализировать текущую директорию как проект",
" build [--profile] Собрать проект (по умолчанию: --debug)",
" run [--profile] Собрать и запустить (по умолчанию: --debug)",
" clean Удалить директорию target",
colored("ФЛАГИ:", BRIGHT_GREEN + BOLD),
" --help Показать справку по команде",
" --update Обновить dcr до актуальной версии",
" --version Показать версию dcr",
"",
colored("ОПЦИИ:", BRIGHT_GREEN + BOLD),
" --debug Сборка с профилем debug",
" --release Сборка с профилем release",
"",
colored("ПРИМЕРЫ:", BRIGHT_GREEN + BOLD),
colored(" dcr new hello", BRIGHT_CYAN + BOLD),
colored(" dcr build --release", BRIGHT_CYAN + BOLD),
colored(" dcr run --debug", BRIGHT_CYAN + BOLD),
"",
colored("ПОДСКАЗКА:", BRIGHT_GREEN + BOLD),
" Запусти 'dcr <команда> --help' для справки по команде.",
]
)
)
return 0

View File

@@ -1,61 +0,0 @@
from ..utils.text import colored, BRIGHT_RED, BOLD, printc, BRIGHT_GREEN, BRIGHT_CYAN
from ..config import (
file_dcr_toml,
file_main_c,
project_version,
project_language,
project_compiler,
)
import os
from ..utils.fs import check_dir
def init(args: list[str] | None = None) -> int:
if args:
print(
colored("error", BRIGHT_RED + BOLD)
+ ": команда не поддерживает доп. аргументы"
)
return 1
items: list[str] = check_dir()
project_name = os.path.basename(os.getcwd())
if items:
print(colored("error", BRIGHT_RED + BOLD) + ": директория не пуста")
return 1
print("Инициализация проекта в " + os.getcwd())
with open(f"./dcr.toml", "w") as file:
file.write(
file_dcr_toml.format(
project_name=project_name,
project_version=project_version,
project_language=project_language,
project_compiler=project_compiler,
)
)
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Создан файл "
+ colored("dcr.toml", BRIGHT_CYAN + BOLD)
)
os.mkdir("src")
with open("./src/main.c", "w") as file:
file.write(file_main_c)
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Создан файл "
+ colored("src/main.c\n", BRIGHT_CYAN + BOLD)
)
print(
"Проект `" + colored(project_name, BRIGHT_GREEN + BOLD) + "` успешно создан\n"
)
printc("Следуюший шаг:", BRIGHT_GREEN + BOLD)
printc(" dcr run", BRIGHT_CYAN + BOLD)
return 0

View File

@@ -1,89 +0,0 @@
from ..utils.text import (
colored,
printc,
BRIGHT_GREEN,
BOLD,
BRIGHT_CYAN,
BRIGHT_RED,
)
from ..config import (
file_dcr_toml,
file_main_c,
project_version,
project_language,
project_compiler,
)
import os
from ..utils.fs import check_dir
def new(args: list[str]) -> int:
items: list[str] = check_dir()
if len(args) == 0:
print(colored("error", BRIGHT_RED + BOLD) + ": не указанно название проекта")
return 1
if len(args) > 1:
print(
colored("error", BRIGHT_RED + BOLD)
+ ": команда не поддерживает доп. аргументы"
)
return 1
project_name: str = args[0]
print("Создание проекта `" + colored(project_name, BRIGHT_GREEN + BOLD) + "`...")
if project_name in items:
print(
colored("error", BRIGHT_RED + BOLD)
+ ": директория `"
+ colored(project_name, BRIGHT_GREEN + BOLD)
+ "` уже существует\n"
)
printc("Подсказка:", BRIGHT_GREEN + BOLD)
print(
" Используй `"
+ colored("dcr init", BRIGHT_CYAN + BOLD)
+ "` для инициализации существующего проекта\n или задай другое имя проекта"
)
return 1
os.mkdir(project_name)
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Создана директория "
+ project_name
)
with open(f"./{project_name}/dcr.toml", "w") as file:
file.write(
file_dcr_toml.format(
project_name=project_name,
project_version=project_version,
project_language=project_language,
project_compiler=project_compiler,
)
)
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Создан файл "
+ colored("dcr.toml", BRIGHT_CYAN + BOLD)
)
os.makedirs(f"./{project_name}/src")
with open(f"./{project_name}/src/main.c", "w") as file:
file.write(file_main_c)
print(
" "
+ colored("✔", BRIGHT_GREEN + BOLD)
+ " Создан файл "
+ colored("src/main.c\n", BRIGHT_CYAN + BOLD)
)
print(
"Проект `" + colored(project_name, BRIGHT_GREEN + BOLD) + "` успешно создан\n"
)
printc("Следуюший шаг:", BRIGHT_GREEN + BOLD)
printc(" cd " + project_name + "\n dcr run", BRIGHT_CYAN + BOLD)
return 0

View File

@@ -1,39 +0,0 @@
import subprocess
import os
from ..utils.fs import check_dir
from ..utils.text import colored, BRIGHT_RED, BOLD
from ..config import flags, profile
from .build import build
def run(args: list[str] | None = None) -> int:
project_name = os.path.basename(os.getcwd())
active_profile: str = profile
if "dcr.toml" not in check_dir():
print(colored("error", BRIGHT_RED + BOLD) + ": не найден файл dcr.toml")
return 1
if args:
if len(args) >= 1 and args[0].startswith("--"):
candidate: str = args[0][2:]
if candidate in flags:
active_profile: str = candidate
else:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный флаг сборки")
return 1
else:
print(colored("error", BRIGHT_RED + BOLD) + ": неизвестный аргумент")
return 1
build_status: int = build(args)
if build_status == 0:
print("\n Запуск " + "target/" + active_profile + "/main")
print("--------------------------------")
return subprocess.run([f"./target/{active_profile}/main"]).returncode
else:
if active_profile in check_dir("target/" + active_profile):
print("Запуск последнего релиза")
return subprocess.run([f"./target/{active_profile}/main"]).returncode
else:
print("Исправьте ошибки в коде для запуска проекта")
return 0

View File

@@ -1,44 +0,0 @@
file_dcr_toml: str = """[package]
name = "{project_name}"
version = "{project_version}"
language = "{project_language}"
compiler = "{project_compiler}"
[dependencies]
"""
file_main_c: str = """#include <stdio.h>
int main(void) {
printf("Hello World!\\n");
return 0;
}
"""
file_dcr_lock: str = """# This file is automatically @generated by dcr.
# It is not intended for manual editing.
version = 1
[[package]]
name = "{project_name}"
version = "{project_version}"
"""
"""[[package]]
name = "{package_name}"
version = "{package_version}"
source = "{{package_source}"
checksum = "{package_checksum}"
dependencies = [{package_dependencies}]"""
project_version: str = "0.1.0"
project_language: str = "c"
project_compiler: str = "clang"
profile: str = "debug"
c_comp: str = "clang"
flags: dict[str, list[str]] = {
"release": ["-O3", "-DNDEBUG", "-march=native"],
"debug": ["-O0", "-g", "-Wall", "-Wextra", "-fno-omit-frame-pointer", "-DDEBUG"],
}
VERSION = "v0.1.1"

View File

@@ -1,38 +0,0 @@
from .config import VERSION
import dcr.cli as cli
import sys
def main() -> int:
if len(sys.argv) < 2:
cli.help()
return 0
cmd: str = sys.argv[1]
args: str | list[str] = sys.argv[2:]
match cmd:
case "new":
cli.new(args)
case "init":
cli.init(args)
case "build":
cli.build(args)
case "run":
cli.run(args)
case "clean":
cli.clean(args)
case "--version":
print(f"dcr {VERSION} (GNU/Linux)")
case "--help":
cli.help()
case "--update":
cli.flag_update(args)
case _:
print("Неизвестная команда или аргумент")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -1,8 +0,0 @@
import os
def check_dir(dir: str | None = None) -> list[str]:
if dir in (None, ".", "./"):
return os.listdir(os.getcwd())
else:
return os.listdir(os.getcwd() + "/" + dir)

View File

@@ -1,40 +0,0 @@
# RESET
RESET = "\033[0m"
# TEXT STYLES
BOLD = "\033[1m"
DIM = "\033[2m"
ITALIC = "\033[3m"
UNDERLINE = "\033[4m"
BLINK = "\033[5m"
REVERSE = "\033[7m"
HIDDEN = "\033[8m"
STRIKETHROUGH = "\033[9m"
# STANDARD COLORS (Foreground)
BLACK = "\033[30m"
RED = "\033[31m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
BLUE = "\033[34m"
MAGENTA = "\033[35m"
CYAN = "\033[36m"
WHITE = "\033[37m"
# BRIGHT COLORS
BRIGHT_BLACK = "\033[90m"
BRIGHT_RED = "\033[91m"
BRIGHT_GREEN = "\033[92m"
BRIGHT_YELLOW = "\033[93m"
BRIGHT_BLUE = "\033[94m"
BRIGHT_MAGENTA = "\033[95m"
BRIGHT_CYAN = "\033[96m"
BRIGHT_WHITE = "\033[97m"
def colored(msg: str, style: str) -> str:
return style + msg + RESET
def printc(msg: str, style: str) -> None:
print(style + msg + RESET)

35
src/main.rs Normal file
View File

@@ -0,0 +1,35 @@
use crate::utils::log::error;
mod cli;
mod config;
mod utils;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
std::process::exit(cli::help::help());
}
let cmd = args[1].as_str();
let rest = &args[2..];
let code = match cmd {
"new" => cli::r#new::new(rest),
"init" => cli::init::init(rest),
"build" => cli::build::build(rest),
"run" => cli::run::run(rest),
"clean" => cli::clean::clean(rest),
"--version" => {
println!("dcr {} ({})", env!("CARGO_PKG_VERSION"), env!("DCR_TARGET"));
0
}
"--help" => cli::help::help(),
"--update" => cli::flag_update::flag_update(rest),
_ => {
error("Unknown command or argument");
0
}
};
std::process::exit(code);
}

View File

18
src/utils/fs.rs Normal file
View File

@@ -0,0 +1,18 @@
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()?,
Some(value) => std::env::current_dir()?.join(value),
};
let mut items = Vec::new();
for entry in std::fs::read_dir(path)? {
let entry = entry?;
items.push(entry.file_name().to_string_lossy().to_string());
}
Ok(items)
}

24
src/utils/log.rs Normal file
View File

@@ -0,0 +1,24 @@
use crate::utils::text::{BOLD_CYAN, colored};
#[allow(dead_code)]
pub fn error(msg: &str) {
println!("{}: {msg}", colored("error", BOLD_CYAN),);
}
#[allow(dead_code)]
pub fn warn(msg: &str) {
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))
// );
// }

3
src/utils/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod fs;
pub mod log;
pub mod text;

32
src/utils/text.rs Normal file
View File

@@ -0,0 +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}");
}