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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ format and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
### Documentation
- Document installation command and provide docs.rs, source code, and issue tracker links near the top of README
- Remove mention of deprecated `-r` flag from README
- Explain `# keepsorted: ignore file` and `# keepsorted: ignore block` directives and clarify that directory traversal should be handled externally

## [0.1.5] - 2025-07-15

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cargo install keepsorted
- `keepsorted --check <path>` verifies sorting without modifying files.
- `keepsorted --diff <path>` shows a diff of required changes.
- `keepsorted --fix <path>` updates files in place.
- keepsorted only processes a single file at a time. Directory traversal and filtering are left to shell tools like `git ls-files` so that you can easily include or exclude paths. See [Architecture](docs/architecture.md) for details.

Run `keepsorted --check` in CI after filtering tracked files with
`git ls-files` to prevent unsorted changes.
Expand Down
49 changes: 35 additions & 14 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,24 @@ The CLI returns these codes:

The core feature is sorting lines while preserving any comments associated with each item. Multi-line items (for example in `Cargo.toml`) are kept intact when possible so that sections remain readable.

Sorting can be skipped with two special directives:
- **`# keepsorted: ignore file`** anywhere in a file leaves the entire file unchanged.
- **`# keepsorted: ignore block`** inside a `# Keep sorted` block preserves that block without reordering.

Some experimental features exist:

- **Rust derive sorting** temporarily supports alphabetical or canonical ordering of `#[derive(...)]` attributes because `cargo fmt` does not yet implement this. The functionality is intentionally basic and may be removed once rustfmt provides a stable implementation.
- **Gitignore and CODEOWNERS sorting** helps maintain consistent ordering but should be used carefully since pattern order can affect semantics.

## Experimental Features

The following features are behind flags and must be enabled explicitly using `--features`:

- `rust_derive_alphabetical` — sorts `#[derive(...)]` attributes alphabetically
- `rust_derive_canonical` — sorts `#[derive(...)]` attributes in canonical Rust order
- `gitignore` — enables sorting logic for `.gitignore` files
- `codeowners` — enables sorting logic for `CODEOWNERS` files

## Modules

### `strategies` directory
Expand All @@ -35,7 +49,9 @@ Each file under `src/strategies/` provides a `process` function that sorts lines

### CLI (`src/main.rs`)

`main.rs` implements the command-line interface using `clap`. It parses arguments, selects the formatting mode (check, diff or fix) and passes a single file to `handle_file`. Directory traversal is intentionally left to external scripts so that the binary stays simple and composable. The helper `handle_file` runs the crate API on each file and applies the chosen mode.
`main.rs` implements the command-line interface using `clap`. It parses arguments, selects the formatting mode (check, diff or fix) and passes a single file to `handle_file`. Directory traversal is intentionally left to external scripts so that the binary stays simple and composable. There is deliberately no `-r` or `--recursive` option; use tools like `git ls-files` if you need to process multiple files. The helper `handle_file` runs the crate API on each file and applies the chosen mode.

`keepsorted` focuses on sorting and does not try to walk directories itself. Implementing a fully featured crawler would require handling ignore files, generated sources and other project-specific rules. Existing tools already solve these problems, so the CLI expects callers to provide an explicit list of files. This design keeps the binary small while letting users combine it with powerful shell filters.

### Crate API (`src/lib.rs`)

Expand All @@ -46,22 +62,27 @@ Library code exposes two key functions:

Both functions rely on private helpers such as `classify` for strategy selection.

## File Processing Flow

1. CLI parses args and opens the file
2. `process_file` calls `classify` to select the strategy
3. The strategy module processes lines via `process_lines`
4. The result is checked, diffed, or written depending on the mode

## Testing Strategy

End-to-end tests in `e2e-tests` invoke the CLI using a command-line test framework to simulate real usage. Each flag or parameter has its own test file with descriptive names following the Arrange–Act–Assert style. This keeps tests short and focused while covering many combinations.

Tests use real input files and golden outputs. Each test case includes input.txt and expected.txt stored in the same subdirectory for clarity and simplicity.

Rust unit tests inside `tests/` verify the behaviour of individual strategies for different file and data types.

## Key functions

| Function | Location | Responsibility |
| -------- | -------- | -------------- |
| `process_file` | `src/lib.rs` | Load a file and return its sorted contents. |
| `process_lines` | `src/lib.rs` | Apply a sorting strategy to lines. |
| `classify` | `src/lib.rs` | Identify the strategy for a path and feature set. |
| `handle_file` | `src/main.rs` | CLI helper that checks, diffs or rewrites a file. |
| `generic::process` | `src/strategies/generic.rs` | Sort `# Keep sorted` blocks in text files. |
| `bazel::process` | `src/strategies/bazel.rs` | Sort string lists in Bazel files. |
| `cargo_toml::process` | `src/strategies/cargo_toml.rs` | Sort dependency sections in `Cargo.toml`. |
| `gitignore::process` | `src/strategies/gitignore.rs` | Sort `.gitignore` and `CODEOWNERS` files. |
| `rust_derive::process` | `src/strategies/rust_derive.rs` | Reorder traits inside `#[derive]` attributes. |
## Out of Scope

To stay focused and composable, `keepsorted` does **not** aim to:

- Perform recursive directory traversal (use external tools like `find`, `git ls-files`, or CI filters)
- Act as a full-fledged parser for every supported file type
- Handle ignore files or exclude paths automatically
- Automatically detect project structure or configuration files
- Replace formatting tools like `rustfmt` or `prettier`
Loading