Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[package]
name = "cargo-bless"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
rust-version = "1.80"
default-run = "cargo-bless"
description = "Modernize your Rust dependencies with blessed.rs + live intel"
license = "MIT"
repository = "https://github.com/Ruffian-L/cargo-bless"
Expand Down
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A Cargo subcommand that checks your dependencies against [blessed.rs](https://bl
- Scans your `Cargo.toml` dependency tree (direct + transitive, with features)
- Matches against a built-in rule database sourced from blessed.rs
- Detects single-crate replacements _and_ combo optimizations (e.g. dropping `serde_json` when `reqwest` has the `json` feature)
- Runs a built-in bullshit detector code audit for suspicious Rust complexity patterns
- Optionally runs a built-in bullshit detector code audit for suspicious Rust complexity patterns
- Fetches live metadata from crates.io (latest version, downloads) and GitHub (last push, archived status)
- Optionally applies safe fixes to your `Cargo.toml` with `--fix` (preview first with `--dry-run`)

Expand Down Expand Up @@ -47,7 +47,7 @@ cargo bless --fix # apply changes (creates .bak backup)
cargo bless --update-rules # fetch latest rules from blessed.rs
cargo bless --json # output suggestions as JSON
cargo bless --offline # skip network calls, use local cache only
cargo bless --no-audit-code # skip the default code audit
cargo bless --audit-code # include code audit in the main report
```

### CLI Flags
Expand All @@ -56,8 +56,7 @@ cargo bless --no-audit-code # skip the default code audit
|------|-------------|
| `--fix` | Apply auto-fixable suggestions to Cargo.toml |
| `--dry-run` | Preview changes without writing (use with `--fix`) |
| `--audit-code` | Explicitly run the bullshit detector code audit |
| `--no-audit-code` | Skip the default bullshit detector code audit |
| `--audit-code` | Include the bullshit detector code audit in the main report |
| `--diff` | With `cargo bless bs`, audit only changed lines from `git diff HEAD` |
| `--verbose` | Show every code-audit finding instead of the top findings summary |
| `--json` | Output suggestions as JSON array (for CI/pipelines) |
Expand Down Expand Up @@ -94,9 +93,9 @@ Or pass a custom path: `cargo bless --policy=custom-bless.toml`
## Example

```
$ cargo bless
$ cargo bless --audit-code

🔥 cargo-bless v0.1.3
🔥 cargo-bless v0.1.4

📋 Scanning dependencies...

Expand Down Expand Up @@ -184,7 +183,7 @@ Before any write, `--fix` creates a `Cargo.toml.bak` backup and runs `cargo upda
2. Rules from `data/suggestions.json` are matched against direct deps (single-crate and combo patterns)
3. `crates_io_api::SyncClient` fetches live metadata (cached to `~/.cache/cargo-bless/` with 1-hour TTL)
4. `reqwest` checks GitHub for `pushed_at`, `archived`, and star count
5. The bullshit detector scans Rust files under `src`, `tests`, `examples`, and `benches` for static complexity patterns
5. With `--audit-code` or `cargo bless bs`, the bullshit detector scans Rust files under `src`, `tests`, `examples`, and `benches` for static complexity patterns
6. `toml_edit` applies fixes while preserving comments and formatting

Network calls are non-fatal — if you're offline, the rule-based report still works.
Expand Down
25 changes: 20 additions & 5 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use clap::{Parser, Subcommand};
use clap::{Args, Parser, Subcommand};

/// Cargo subcommand wrapper — invoked as `cargo bless`.
#[derive(Parser, Debug)]
Expand All @@ -11,12 +11,27 @@ pub struct Cli {
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Bless your dependencies — modernize, optimize, and stay current.
Bless(BlessOpts),
Bless(BlessCommand),
/// Run only the bullshit detector code audit.
Bs(CodeAuditOpts),
}

#[derive(clap::Args, Debug)]
#[derive(Args, Debug)]
pub struct BlessCommand {
#[command(flatten)]
pub opts: BlessOpts,

#[command(subcommand)]
pub command: Option<BlessSubcommand>,
}

#[derive(Subcommand, Debug)]
pub enum BlessSubcommand {
/// Run only the bullshit detector code audit.
Bs(CodeAuditOpts),
}

#[derive(Args, Debug)]
pub struct BlessOpts {
/// Apply suggested changes to Cargo.toml (creates .bak backup first).
#[arg(long)]
Expand All @@ -43,7 +58,7 @@ pub struct BlessOpts {
pub audit_code: bool,

/// Skip the default bullshit detector code audit.
#[arg(long)]
#[arg(long, hide = true)]
pub no_audit_code: bool,

/// Output suggestions in JSON format.
Expand Down Expand Up @@ -80,7 +95,7 @@ pub struct BlessOpts {
pub verbose: bool,
}

#[derive(clap::Args, Debug)]
#[derive(Args, Debug)]
pub struct CodeAuditOpts {
/// Path to the Cargo.toml whose source tree should be audited.
#[arg(long, value_name = "PATH")]
Expand Down
120 changes: 62 additions & 58 deletions src/fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

use std::fs;
use std::path::Path;
use std::process::Command;
use std::process::{Command, Output};

use anyhow::{Context, Result};
use colored::*;
Expand Down Expand Up @@ -111,64 +111,21 @@ pub fn apply(suggestions: &[Suggestion], manifest_path: &Path, dry_run: bool) ->
fs::write(manifest_path, &edited)
.with_context(|| format!("failed to write {}", manifest_path.display()))?;

// Run cargo update
println!("{}", "📦 Running cargo update...".dimmed());
let status = Command::new("cargo")
.arg("update")
.arg("--manifest-path")
.arg(manifest_path)
.status();

match status {
Ok(s) if s.success() => {
println!("{}", "✅ cargo update completed successfully.".green());
}
Ok(s) => {
println!(
"{}",
format!("⚠️ cargo update exited with: {}", s).yellow()
);
}
Err(e) => {
println!(
"{}",
format!("⚠️ Failed to run cargo update: {}", e).yellow()
);
}
}
run_cargo_validation(
"cargo update",
"📦 Running cargo update...",
"✅ cargo update completed successfully.",
&["update", "--manifest-path"],
manifest_path,
);

// Run cargo check to validate the fix didn't break compilation
println!("{}", "🔍 Running cargo check...".dimmed());
let check_status = Command::new("cargo")
.arg("check")
.arg("--manifest-path")
.arg(manifest_path)
.status();

match check_status {
Ok(s) if s.success() => {
println!(
"{}",
"✅ cargo check passed — project still compiles.".green()
);
}
Ok(s) => {
println!(
"{}",
format!(
"⚠️ cargo check failed (exit: {}). You may need to update source code.",
s
)
.yellow()
);
}
Err(e) => {
println!(
"{}",
format!("⚠️ Failed to run cargo check: {}", e).yellow()
);
}
}
run_cargo_validation(
"cargo check",
"🔍 Running cargo check...",
"✅ cargo check passed — project still compiles.",
&["check", "--manifest-path"],
manifest_path,
);

println!();
if !applied.is_empty() {
Expand All @@ -190,6 +147,53 @@ pub fn apply(suggestions: &[Suggestion], manifest_path: &Path, dry_run: bool) ->
Ok(FixResult { applied, skipped })
}

fn run_cargo_validation(
command_name: &str,
start_message: &str,
success_message: &str,
args: &[&str],
manifest_path: &Path,
) {
println!("{}", start_message.dimmed());
let output = Command::new("cargo").args(args).arg(manifest_path).output();

match output {
Ok(output) if output.status.success() => {
println!("{}", success_message.green());
}
Ok(output) => {
println!(
"{}",
format!(
"⚠️ {command_name} exited with {}. Run `{command_name} --manifest-path {}` for details.",
output.status,
manifest_path.display()
)
.yellow()
);
if let Some(summary) = validation_summary(&output) {
println!(" {}", summary.dimmed());
}
}
Err(err) => {
println!(
"{}",
format!("⚠️ Failed to run {command_name}: {err}").yellow()
);
}
}
}

fn validation_summary(output: &Output) -> Option<String> {
let stderr = String::from_utf8_lossy(&output.stderr);
stderr
.lines()
.chain(String::from_utf8_lossy(&output.stdout).lines())
.map(str::trim)
.find(|line| !line.is_empty())
.map(str::to_string)
}

/// Apply a single suggestion to the TOML document.
/// Returns a description of what was done on success.
fn apply_single(doc: &mut DocumentMut, suggestion: &Suggestion) -> Result<String> {
Expand Down
Loading
Loading