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
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed
- `--no-color` and `NO_COLOR` now correctly suppress all ANSI codes. Plain
`.red()` / `.green()` etc. emit codes unconditionally; switched everything to
`if_supports_color()` which respects the owo-colors override system.

## [0.1.1] - 2026-03-15

### Added
- Interactive fix mode: resolver control loop with IO abstraction and mock-driven tests
- Filesystem operation layer with `--dry-run` support
- `--batch-confirm` flag to collect all decisions then confirm before applying
- Safety confirmation layer for destructive actions (remove)
- Fuzzy candidate search with Levenshtein distance, path similarity, and depth penalty scoring
- Configurable ignore patterns (`-I`) propagated to scanner and fuzzy walker
- Cross-filesystem and loop-detection warnings
- CI lint workflow
- Release profile with LTO, single codegen unit, and stripped binaries

### Changed
- Replaced subprocess-based resolution with native Rust implementation

## [0.1.0] - 2026-03-13

### Added
- Initial release
- Recursive broken symlink scanner
- Reports dead target path for each broken link
- Basic `--list` and `--path` flags
- Fix for correctly identifying broken symlinks (not all symlinks)
54 changes: 54 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ overflow-checks = false
[profile.bench]
debug = true
strip = "none"

[workspace.dependencies]
owo-colors = { version = "4.3.0", features = ["supports-colors"] }
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ path = "src/main.rs"

[dependencies]
clap = { version = "4.6.0", features = ["derive"] }
owo-colors.workspace = true
unrot_core = { version = "0.1.1", path = "../core" }
56 changes: 51 additions & 5 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use owo_colors::{OwoColorize, Stream};
use std::{io::IsTerminal, path::PathBuf};
use unrot_core::{
BrokenSymlink, DEFAULT_IGNORE, RepairCase, TerminalIO, find_broken_symlinks, find_candidates,
run,
Expand All @@ -8,6 +9,20 @@ use unrot_core::{
fn main() {
let cli = Cli::parse();

let no_color = match &cli.subcommand {
Some(Sub::Scan(s)) => s.no_color,
Some(Sub::Fix(f)) => f.no_color,
Some(Sub::List(l)) => l.no_color,
None => cli.no_color,
};

// Configure colors: --no-color, NO_COLOR env, or piped stdout => plain
if no_color || std::env::var("NO_COLOR").is_ok() || !std::io::stdout().is_terminal() {
owo_colors::set_override(false);
} else {
owo_colors::unset_override();
}

let (mode, path, extra_ignore, fix_opts) = match &cli.subcommand {
Some(Sub::Scan(s)) => (Mode::Scan, resolve_path(&s.path), s.ignore.clone(), None),
Some(Sub::Fix(f)) => (
Expand Down Expand Up @@ -46,15 +61,27 @@ fn main() {
match mode {
Mode::List => {
for link in &broken {
println!("{}", link.link.display());
println!(
"{}",
link.link
.display()
.if_supports_color(Stream::Stdout, |v| v.red())
);
}
return;
}
Mode::Scan => {
if broken.is_empty() {
println!("no broken symlinks found");
println!(
"{}",
"no broken symlinks found".if_supports_color(Stream::Stdout, |v| v.green())
);
} else {
println!("found {} broken symlink(s):\n", broken.len());
println!(
"{}",
format!("found {} broken symlink(s):\n", broken.len())
.if_supports_color(Stream::Stdout, |v| v.yellow())
);
for b in &broken {
println!(" {b}");
}
Expand Down Expand Up @@ -89,7 +116,10 @@ fn main() {
}
}
Err(e) => {
eprintln!("error: {e}");
eprintln!(
"{}",
format!("error: {e}").if_supports_color(Stream::Stderr, |v| v.red())
);
std::process::exit(1);
}
}
Expand Down Expand Up @@ -140,6 +170,10 @@ struct Cli {
/// Collect all decisions, show summary, then confirm before applying
#[arg(long)]
batch_confirm: bool,

/// Disable colored output
#[arg(long)]
no_color: bool,
}

#[derive(Subcommand)]
Expand All @@ -162,6 +196,10 @@ struct ScanArgs {
/// Additional directory names to ignore during walks
#[arg(short = 'I', long)]
ignore: Vec<String>,

/// Disable colored output
#[arg(long)]
no_color: bool,
}

#[derive(clap::Args)]
Expand All @@ -184,6 +222,10 @@ struct FixArgs {
/// Collect all decisions, show summary, then confirm before applying
#[arg(long)]
batch_confirm: bool,

/// Disable colored output
#[arg(long)]
no_color: bool,
}

#[derive(clap::Args)]
Expand All @@ -194,6 +236,10 @@ struct ListArgs {
/// Additional directory names to ignore during walks
#[arg(short = 'I', long)]
ignore: Vec<String>,

/// Disable colored output
#[arg(long)]
no_color: bool,
}

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ categories = ["command-line-utilities", "filesystem"]

[dependencies]
walkdir = "2.5.0"
owo-colors.workspace = true

[dev-dependencies]
tempfile = "3"
Loading
Loading