Merge pull request #2 from dexoron/rust-rewrite

Rewrite project from Python to Rust and add Windows/MacOS supports
This commit is contained in:
Dexoron
2026-02-17 18:31:34 +08:00
committed by GitHub
45 changed files with 2828 additions and 726 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__/
build/
dist/
wheels/
*.egg-info
.venv
sandbox/*
.ruff_cache/
.vscode/
target/
sandbox/**
.idea/
.vscode/
.env

View File

@@ -1 +0,0 @@
3.14.2

View File

@@ -1,5 +1,21 @@
# Changelog
## [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
Добавлено:
- Команда обновления

View File

@@ -4,41 +4,43 @@
## Требования
- Обязательно использовать форматирование `ruff`.
- Обязательно использовать аннотации типов и проверять их через `ty`.
- Использовать Rust stable.
- Перед PR код должен проходить форматирование, линт и сборку.
## Быстрый старт(GNU/Linux)
## Быстрый старт
Создайте линковку ./PATH_TO_PROJECT/dcr и ~/.local/bin/dcr
После этого, вы можете использовать команду dcr где угодно
```bash
git clone https://github.com/dexoron/dcr.git
cd dcr
cargo check
```
## Стиль кода
- Форматируйте код через `ruff`.
- Форматируйте код через `cargo fmt`.
- Проверяйте предупреждения через `cargo clippy`.
- Держите функции небольшими и сфокусированными.
- Предпочитайте понятные имена вместо "хитрых".
## Типизация
- Добавляйте аннотации типов для нового и изменяемого кода.
- Запускайте `ty` для проверки корректности типов.
## Проверки
Для запуска всех проверок используйте скрипт проекта:
Перед PR запускайте:
```bash
./check.sh
cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo check
```
> Скрипт можно запустить из любой папки, указав путь до него: `../check.sh` (или больше `../`, если нужно).
## CI/CD и релизы
## Запуск инструментов напрямую
```bash
ruff check src
uvx ty check src
```
- 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
@@ -48,7 +50,7 @@ uvx ty check src
## Вопросы
Если что-то непонятно, откройте issue или задайте вопрос в PR.
Если что-то непонятно, открой issue или задайте вопрос в PR.
Также можно задать вопрос напрямую:
- TG: @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 = "2.0.0"
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"

View File

@@ -1,8 +1,8 @@
# DCR (Dexoron Cargo Realization)
DCR — это утилита для управления проектами на C. Она задает единый формат проекта, дает команды для создания, сборки и запуска, и использует один конфигурационный файл `dcr.toml`.
DCR — это утилита для управления проектами на C/C++ в стиле Cargo.
Проект находится на стадии MVP. Текущая реализация написана на Python и в будущем будет переписана.
Текущая реализация написана на Rust.
## Зачем DCR
- Единая структура проекта без ручной настройки
@@ -15,25 +15,50 @@ DCR — это утилита для управления проектами н
- Запуск собранного бинарника
- Очистка результатов сборки
- Генерация минимального шаблона 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`
## Установка
### Автоматическая установка(Рекомендуется)
**GNU/Linux**
```sh
curl -L dcr.zov.tatar | bash
```
### Самостоятельная установка
### Из исходников
```sh
git clone https://github.com/dexoron/dcr.git "~/.local/share/dcr"
chmod +x "~/.local/share/dcr/dcr"
ln -sf ~/.local/share/dcr/dcr ~/.local/bin
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://raw.githubusercontent.com/dexoron/dcr/master/install.sh | bash
```
### Через install.ps1 (Windows)
```powershell
irm https://raw.githubusercontent.com/dexoron/dcr/master/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`
@@ -41,7 +66,8 @@ ln -sf ~/.local/share/dcr/dcr ~/.local/bin
`dcr init`
Структура проекта:
```
```txt
hello/
- src/
- - main.c
@@ -49,8 +75,7 @@ hello/
```
Сборка и запуск проекта:
`dcr run` или `dcr run --release` - для сбрки с оптимизацией
`dcr run` или `dcr run --release`
## Команды
@@ -78,10 +103,11 @@ hello/
- `--release` — флаги: `-O3 -DNDEBUG -march=native`
## Конфигурация
Основной файл проекта — `dcr.toml`. Сейчас он создается автоматически и служит точкой входа для будущих настроек.
Основной файл проекта — `dcr.toml`.
Пример `dcr.toml`:
```
```toml
[package]
name = "hello"
version = "0.1.0"
@@ -91,34 +117,12 @@ compiler = "clang"
[dependencies]
```
## Установка в PATH
Чтобы вызывать `dcr` из любой директории, можно сделать symlink в `~/.local/bin`:
```sh
ln -s "$PWD/dcr" ~/.local/bin/dcr
```
Убедитесь, что `~/.local/bin` находится в `PATH`.
## Пример проекта
`src/main.c` содержит минимальный `Hello, World!`:
```c
#include <stdio.h>
int main(void) {
puts("Hello, World!");
return 0;
}
```
## Требования
- C-компилятор (например, `gcc` или `clang`)
- Python 3.14.2 (для текущей версии DCR)
- uv (менеджер проекта для Python)
- Rust toolchain (`rustc`, `cargo`) - Для ручной сборки DCR
- C-компилятор (`clang`, `gcc` или другие)
## Статус и планы
Проект в стадии MVP. Планируется переписывание на более подходящий язык и расширение функциональности:
- более гибкая конфигурация через `dcr.toml`
- расширяемые профили сборки
- менеджер пакетов
## Релизы
Релизы собираются автоматически через GitHub Actions (`.github/workflows/release.yml`) при пуше тега формата `v*`.
## Лицензия
См. файл `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}");
}

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="src"
echo "#============#"
echo "# Ruff #"
echo "#============#"
(cd "$SCRIPT_DIR" && ruff check "$BASE_DIR")
echo
echo
echo "#============#"
echo "# Ty #"
echo "#============#"
(cd "$SCRIPT_DIR" && uvx ty check "$BASE_DIR")

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 "$@"

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 "Не найдено: $name"
}
}
function Select-Mode {
Write-Host "Выбери способ установки:"
Write-Host " 1) Скачать готовый бинарник из GitHub Release (рекомендуется)"
Write-Host " 2) Собрать из git"
$choice = Read-Host "Введите 1 или 2 [1]"
if ([string]::IsNullOrWhiteSpace($choice)) { $choice = '1' }
switch ($choice) {
'1' { return 'release' }
'2' { return 'build' }
default { Fail 'Неизвестный вариант' }
}
}
function Get-Target {
if ($env:PROCESSOR_ARCHITECTURE -ne 'AMD64') {
Fail "Поддерживается только x86_64 Windows, текущая архитектура: $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 "Получение последнего релиза..."
$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)"
}
Info "Скачивание $assetName..."
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $BinaryPath
Ok "Скачан бинарник $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 'Клонирование репозитория...'
git clone --depth 1 $RepoUrl $tmp | Out-Null
Info 'Сборка release-бинарника...'
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 'Установлен бинарник из исходников'
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 "Добавлен путь в PATH: $BinPath"
}
Ok "Команда dcr доступна через $linkPath"
}
Info 'Запуск установки DCR'
$mode = Select-Mode
$target = Get-Target
if ($mode -eq 'build') {
Install-FromSource -target $target
} else {
Install-FromRelease -target $target
}
Setup-Path
Ok 'Установка завершена успешно'

View File

@@ -1,14 +1,11 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# ===== CONFIG =====
TMPDIR="/tmp/dcr-temp"
TMPDIR="/tmp/dcr-install"
INSTALL_PATH="$HOME/.local/share/dcr"
PYTHON_DIR="$INSTALL_PATH/Python"
BINPATH="$HOME/.local/bin"
LOGFILE="$HOME/.cache/dcr-install.log"
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"
REPO_URL="https://github.com/dexoron/dcr"
GITHUB_API="https://api.github.com/repos/dexoron/dcr/releases/latest"
RED='\033[0;31m'
@@ -27,118 +24,89 @@ error() { echo -e "${RED}✖ $1${NC}"; }
trap 'error "Ошибка на строке $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() {
if [[ "$(uname -s)" != "Linux" ]]; then
error "Поддерживается только Linux"
exit 1
fi
case "$(uname -s)" in
Linux|Darwin) ;;
*) error "Поддерживаются только Linux и macOS"; exit 1 ;;
esac
}
get_latest_version() {
curl -fsSL "$GITHUB_API" | sed -n 's/.*"tag_name": "\(.*\)".*/\1/p'
detect_target() {
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 "Неподдерживаемая платформа: $os/$arch"; exit 1 ;;
esac
ASSET_NAME="dcr-${TARGET_TRIPLE}"
}
get_installed_version() {
if [[ -f "$INSTALL_PATH/VERSION" ]]; then
cat "$INSTALL_PATH/VERSION"
else
echo ""
fi
check_common_dependencies() {
command -v curl >/dev/null 2>&1 || { error "curl не установлен"; exit 1; }
}
install_python() {
log "Python не найден, установка встроенного Python..."
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 установлен"
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; }
}
install_app() {
prepare_sources() {
log "Получение исходников..."
rm -rf "$TMPDIR"
git clone --depth 1 "$REPO_URL" "$TMPDIR"
success "Исходники получены"
}
build_binary() {
log "Сборка release-бинарника..."
(cd "$TMPDIR" && cargo build --release)
success "Сборка завершена"
}
fetch_latest_release_json() {
curl -fsSL "$GITHUB_API"
}
download_binary() {
log "Получение последнего релиза..."
TAG=$(get_latest_version)
local release_json download_url tag
release_json="$(fetch_latest_release_json)"
if [[ -z "$TAG" ]]; then
tag="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name": "\([^"]*\)".*/\1/p' | head -n1)"
if [[ -z "$tag" ]]; then
error "Не удалось определить версию релиза"
exit 1
fi
INSTALLED_VERSION=$(get_installed_version)
if [[ -n "$INSTALLED_VERSION" ]]; then
log "Установленная версия: $INSTALLED_VERSION"
log "Последняя версия: $TAG"
if [[ "$INSTALLED_VERSION" == "$TAG" ]]; then
success "Установлена последняя версия ($TAG), обновление не требуется"
exit 0
else
warn "Найдена устаревшая версия, выполняется обновление"
fi
else
log "DCR не установлен, выполняется установка"
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}"
exit 1
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"
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)"
curl -fL "$download_url" -o "$INSTALL_PATH/dcr"
chmod +x "$INSTALL_PATH/dcr"
success "Скачан бинарник ${ASSET_NAME} (${tag})"
}
install_wrapper() {
log "Создание launcher..."
install_built_binary() {
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
cp "$TMPDIR/target/release/dcr" "$INSTALL_PATH/dcr"
chmod +x "$INSTALL_PATH/dcr"
success "Launcher создан"
success "Установлен бинарник из исходников"
}
install_link() {
log "Создание симлинка..."
mkdir -p "$BINPATH"
ln -sf "$INSTALL_PATH/dcr" "$BINPATH/dcr"
success "Команда 'dcr' добавлена в $BINPATH"
}
@@ -154,23 +122,37 @@ cleanup() {
rm -rf "$TMPDIR" 2>/dev/null || true
}
select_install_mode() {
echo "Выбери способ установки:"
echo " 1) Скачать готовый бинарник из GitHub Release (рекомендуется)"
echo " 2) Собрать из git"
read -r -p "Введите 1 или 2 [1]: " choice
case "${choice:-1}" in
1) INSTALL_MODE="release" ;;
2) INSTALL_MODE="build" ;;
*) error "Неизвестный вариант"; exit 1 ;;
esac
}
main() {
log "Запуск установки DCR"
check_os
check_dependencies
detect_target
check_common_dependencies
select_install_mode
cleanup
install_app
if ! command -v python3 >/dev/null 2>&1; then
install_python
if [[ "$INSTALL_MODE" == "build" ]]; then
check_build_dependencies
prepare_sources
build_binary
install_built_binary
else
success "Найден системный Python"
download_binary
fi
install_wrapper
install_link
check_path
cleanup

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

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

@@ -0,0 +1,82 @@
use crate::config::{PROFILE, PROJECT_COMPILER, flags};
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, 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));
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 {
println!("{}: неизвестный флаг сборки", colored("error", &red_bold));
return 1;
}
} else {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
return 1;
}
}
println!(
" Сборка проекта `{}`\n Профиль: {}\n Компилятор: {}\n",
colored(&project_name, &green_bold),
colored(&active_profile, &green_bold),
colored(PROJECT_COMPILER, &green_bold)
);
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!(
" {} Сборка завершена успешно, за {} секунд",
colored("✔", &green_bold),
colored(&times.to_string(), &green_bold)
);
return 0;
}
_ => {
println!("{}: сборка не удалась", colored("error", &red_bold));
return 1;
}
}
}
0
}

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

@@ -0,0 +1,72 @@
use crate::config::flags;
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, BRIGHT_YELLOW, 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()))
.unwrap_or_else(|| "project".to_string());
let items = check_dir(None).unwrap_or_default();
if args.len() > 1 {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
return 1;
}
if !items.contains(&"dcr.toml".to_string()) {
println!("{}: не найден файл dcr.toml", colored("error", &red_bold));
return 1;
}
println!(
" Очистка проекта `{}`",
colored(&project_name, &green_bold)
);
if !items.contains(&"target".to_string()) {
println!(
"{}: директория target не найдена",
colored("warn", &yellow_bold)
);
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() {
println!("{}: неизвестный профиль", colored("error", &red_bold));
return 1;
}
let target_items = check_dir(Some("target")).unwrap_or_default();
if !target_items.contains(&profile) {
println!(
"{}: директория target/{profile} не найдена",
colored("warn", &yellow_bold)
);
return 1;
}
println!(" Профиль: {}", colored(&profile, &green_bold));
let _ = fs::remove_dir_all(format!("target/{profile}"));
println!(
"{} Удалена директория target/{profile}",
colored("\n ✔", &green_bold)
);
return 0;
}
let _ = fs::remove_dir_all("target");
println!(
"{} Удалена директория target",
colored("\n ✔", &green_bold)
);
0
}

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

@@ -0,0 +1,161 @@
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() {
println!("Ошибка: команда не поддежривает аргументы");
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(_) => {
println!("Ошибка: не удалось инициализировать HTTP клиент");
return 1;
}
};
let release = match fetch_latest_release(&client) {
Ok(release) => release,
Err(err) => {
println!("Ошибка: не удалось проверить обновления: {err}");
return 1;
}
};
let latest_version = release.tag_name.trim_start_matches('v');
if latest_version == current_version {
println!("Установлена последняя версия: {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 {
println!("Ошибка: не найден бинарник для таргета {target}");
return 1;
};
let bytes = match download_asset(&client, &asset.browser_download_url) {
Ok(bytes) => bytes,
Err(err) => {
println!("Ошибка: не удалось скачать обновление: {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!("Ошибка: не удалось записать временный бинарник");
return 1;
}
set_executable_permissions(&temp_path);
if self_replace::self_replace(&temp_path).is_err() {
let _ = fs::remove_file(&temp_path);
println!("Ошибка: не удалось заменить текущий бинарник");
return 1;
}
let _ = fs::remove_file(&temp_path);
println!("Обновление завершено: {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 завершился ошибкой".to_string())?;
if !response.status().is_success() {
return Err(format!("GitHub API вернул статус {}", response.status()));
}
response
.json::<Release>()
.map_err(|_| "ответ GitHub API имеет неожиданный формат".to_string())
}
fn download_asset(client: &Client, url: &str) -> Result<Vec<u8>, String> {
let response = client
.get(url)
.send()
.map_err(|_| "запрос на скачивание завершился ошибкой".to_string())?;
if !response.status().is_success() {
return Err(format!("скачивание вернуло статус {}", response.status()));
}
response
.bytes()
.map(|bytes| bytes.to_vec())
.map_err(|_| "не удалось прочитать скачанные данные".to_string())
}
fn asset_candidates(target: &str) -> Vec<String> {
let mut names = vec![format!("dcr-{target}")];
match 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);
}
}
}

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

@@ -0,0 +1,36 @@
use crate::utils::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, colored};
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!();
println!("{}", colored("ИСПОЛЬЗОВАНИЕ:", &green_bold));
println!("{}", colored(" dcr <команда> [опции]", &cyan_bold));
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");
println!();
println!("{}", colored("ОПЦИИ:", &green_bold));
println!(" --debug Сборка с профилем debug");
println!(" --release Сборка с профилем release");
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));
println!();
println!("{}", colored("ПОДСКАЗКА:", &green_bold));
println!(" Запусти 'dcr <команда> --help' для справки по команде.");
0
}

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

@@ -0,0 +1,96 @@
use crate::config::{FILE_MAIN_C, file_dcr_toml};
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, BRIGHT_RED, colored, printc};
use std::fs;
use std::io::Write;
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)
);
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() {
println!("{}: директория не пуста", colored("error", &red_bold));
return 1;
}
let cwd = std::env::current_dir()
.map(|v| v.to_string_lossy().to_string())
.unwrap_or_else(|_| ".".to_string());
println!("Инициализация проекта в {cwd}");
let mut dcr_toml = match fs::File::create("./dcr.toml") {
Ok(file) => file,
Err(_) => {
println!(
"{}: не удалось создать dcr.toml",
colored("error", &red_bold)
);
return 1;
}
};
if dcr_toml
.write_all(file_dcr_toml(&project_name).as_bytes())
.is_err()
{
println!(
"{}: не удалось записать dcr.toml",
colored("error", &red_bold)
);
return 1;
}
println!(
" {} Создан файл {}",
colored("✔", &green_bold),
colored("dcr.toml", &cyan_bold)
);
if fs::create_dir("src").is_err() {
println!("{}: не удалось создать src", colored("error", &red_bold));
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)
);
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
println!(
"{}: не удалось записать src/main.c",
colored("error", &red_bold)
);
return 1;
}
println!(
" {} Создан файл {}\n",
colored("✔", &green_bold),
colored("src/main.c", &cyan_bold)
);
println!(
"Проект `{}` успешно создан\n",
colored(&project_name, &green_bold)
);
printc("Следуюший шаг:", &green_bold);
printc(" dcr run", &cyan_bold);
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;

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

@@ -0,0 +1,123 @@
use crate::config::{FILE_MAIN_C, file_dcr_toml};
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_CYAN, BRIGHT_GREEN, BRIGHT_RED, colored, printc};
use std::fs;
use std::io::Write;
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)
);
return 1;
}
if args.len() > 1 {
println!(
"{}: команда не поддерживает доп. аргументы",
colored("error", &red_bold)
);
return 1;
}
let project_name = &args[0];
println!(
"Создание проекта `{}`...",
colored(project_name, &green_bold)
);
if items.contains(project_name) {
println!(
"{}: директория `{}` уже существует\n",
colored("error", &red_bold),
colored(project_name, &green_bold)
);
printc("Подсказка:", &green_bold);
println!(
" Используй `{}` для инициализации существующего проекта\n или задай другое имя проекта",
colored("dcr init", &cyan_bold)
);
return 1;
}
if fs::create_dir(project_name).is_err() {
println!(
"{}: не удалось создать директорию",
colored("error", &red_bold)
);
return 1;
}
println!(
" {} Создана директория {}",
colored("✔", &green_bold),
project_name
);
let toml_path = format!("./{project_name}/dcr.toml");
let mut dcr_toml = match fs::File::create(&toml_path) {
Ok(file) => file,
Err(_) => {
println!(
"{}: не удалось создать dcr.toml",
colored("error", &red_bold)
);
return 1;
}
};
if dcr_toml
.write_all(file_dcr_toml(project_name).as_bytes())
.is_err()
{
println!(
"{}: не удалось записать dcr.toml",
colored("error", &red_bold)
);
return 1;
}
println!(
" {} Создан файл {}",
colored("✔", &green_bold),
colored("dcr.toml", &cyan_bold)
);
if fs::create_dir_all(format!("./{project_name}/src")).is_err() {
println!("{}: не удалось создать src", colored("error", &red_bold));
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)
);
return 1;
}
};
if main_c.write_all(FILE_MAIN_C.as_bytes()).is_err() {
println!(
"{}: не удалось записать src/main.c",
colored("error", &red_bold)
);
return 1;
}
println!(
" {} Создан файл {}\n",
colored("✔", &green_bold),
colored("src/main.c", &cyan_bold)
);
println!(
"Проект `{}` успешно создан\n",
colored(project_name, &green_bold)
);
printc("Следуюший шаг:", &green_bold);
printc(&format!(" cd {project_name}\n dcr run"), &cyan_bold);
0
}

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

@@ -0,0 +1,55 @@
use crate::cli::build::build;
use crate::config::{PROFILE, flags};
use crate::utils::fs::check_dir;
use crate::utils::text::{BOLD, BRIGHT_RED, 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));
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 {
println!("{}: неизвестный флаг сборки", colored("error", &red_bold));
return 1;
}
} else {
println!("{}: неизвестный аргумент", colored("error", &red_bold));
return 1;
}
}
let build_status = build(args);
if build_status == 0 {
println!("\n Запуск target/{active_profile}/main");
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)
{
println!("Запуск последнего релиза");
return Command::new(format!("./target/{active_profile}/main"))
.status()
.map(|status| status.code().unwrap_or(1))
.unwrap_or(1);
}
println!("Исправьте ошибки в коде для запуска проекта");
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, project_compiler
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(project_compiler, 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(
[
project_compiler,
"./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: # noqa: E722
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("./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,37 +0,0 @@
import subprocess
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:
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,43 +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"
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 # ty:ignore[unresolved-import]
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)

34
src/main.rs Normal file
View File

@@ -0,0 +1,34 @@
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),
_ => {
println!("Неизвестная команда или аргумент");
0
}
};
std::process::exit(code);
}

View File

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

@@ -0,0 +1,17 @@
use std::io;
use std::path::PathBuf;
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)
}

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

@@ -0,0 +1,30 @@
use crate::utils::text::{BOLD, BRIGHT_GREEN, BRIGHT_RED, BRIGHT_YELLOW, colored};
#[allow(dead_code)]
pub fn error(msg: &str) {
println!(
"{}: {msg}",
colored("error", &(BRIGHT_RED.to_owned() + BOLD))
);
}
#[allow(dead_code)]
pub fn warn(msg: &str) {
println!(
"{}: {msg}",
colored("warn", &(BRIGHT_YELLOW.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))
);
}

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

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

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

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