file-ext 12.1.0

Collection of utility functions to work with files
Documentation
# Roadmap

`file-ext` (v12.0.0) is a small Rust crate of filesystem utility functions
(file, directory, path, symlink, user helpers). This document captures the
gaps found by reviewing the current source (`src/`), tests, tooling, and docs,
and lays out a prioritized plan to close them.

## Current state (for reference)

- **File**: read (full/partial), create, write (append-only, requires file to
  pre-exist), delete, copy (plain + chunked with progress/cancel callbacks),
  length, modification timestamp, read-or-create-and-write.
- **Directory**: exists, create (recursive), delete (recursive, shells out to
  `rm -rf` / `rd /s /q`).
- **Path**: separator, root, `..`, build path from parts, working directory,
  temp folder path.
- **Symlink**: exists, create, is-symlink, points-to, resolve relative target.
- **User**: current user (unix/windows), current user domain (windows only).
- **Input validation**: `FilterString::is_valid_input_string` blocks control
  chars and shell metacharacters (whitespace, quotes, `&`, `|`, `;`) — used
  inconsistently (see Gaps).
- **Tests**: ~1060 lines of unit tests plus doctests embedded in `lib.rs`.
  No CI workflow exists in the repo (no `.github/workflows`), so tests only
  run when a human runs `cargo test` locally.

## Gaps

### 1. Missing core API surface
- **No directory listing** (`read_dir` equivalent). There's no way to
  enumerate files/subdirectories, which is a basic expectation for a
  file-utility crate and is needed before `delete_directory`/`copy` style
  recursive operations can be reimplemented in pure Rust (see #2).
- **No move/rename** (`fs::rename` wrapper). Only copy + delete exist, which
  is not atomic and is wasteful for same-volume moves.
- **No file/directory metadata beyond size and mtime** — no permissions
  (readonly/executable bits), no created-time, no accessed-time, no
  `is_file`/`is_dir`/`file_type` convenience beyond the boolean exists checks.
- **No recursive directory copy** — only single-file copy exists; copying a
  directory tree is a common companion operation to `delete_directory`.
- **`write_file` cannot create-or-truncate** — it opens with
  `.create(false).truncate(false)` and always seeks to `SeekFrom::End(0)`, so
  it only *appends* to an existing file. There is no "overwrite/truncate"
  primitive and no way to write at an arbitrary offset. This is surprising
  given the function is named `write_file` rather than `append_file`.
- **No symlink deletion helper** — symlinks are deleted via the generic
  `delete_file`, which works on Unix but should be verified/documented for
  Windows directory-symlinks (`rd` vs `remove_file` semantics differ).
- **User module is thin** — no `get_current_user` for anything beyond
  shelling out to `whoami`; no group info, no home-directory lookup.

### 2. Robustness / correctness
- **`delete_directory` shells out to `rm -Rf` / `rd /s /q`** instead of using
  `std::fs::remove_dir_all`. This adds a process-spawn dependency, is slower,
  and — more importantly — means the crate's safety validation
  (`FilterString::is_valid_input_string`) is the *only* thing standing between
  a caller-controlled path and arbitrary shell argument injection. Worth
  migrating to `fs::remove_dir_all` (std, no shell involved, cross-platform)
  unless there's a specific reason (e.g. Windows long-path handling) the shell
  command was chosen — that reason isn't documented anywhere.
- **Inconsistent input validation**`FilterString::is_valid_input_string` is
  called in `file_ext_impl` and `directory_ext_impl` but not in
  `symlink_ext_impl` (`create_symlink`, `symlink_points_to`) or
  `path_ext_impl`. Symlink creation ultimately writes to disk via
  paths built from user input without going through the same filter.
- **`unwrap()` after `is_err()`/`is_some()` checks** (flagged by clippy) in
  `symlink_ext_impl::resolve_symlink_path` and
  `file_ext_impl::copy_file_with_callbacks_starting_from_byte_and_ending_at_byte`.
  Not bugs today, but fragile — a future refactor that reorders the checks
  will panic instead of returning `Result::Err`.
- **No handling for zero-length files** in the copy/chunking math
  (`end = file_length - 1` etc.) — worth a targeted test; integer underflow
  (`file_length - 1` when `file_length == 0`) would panic in debug builds.
- **Dead code**: `Symbol` struct has ~12 fields clippy reports as never read
  (`new_line`, `carriage_return`, `equals`, `comma`, `hyphen`, `colon`, etc.) —
  either they're unused and should be removed, or they're part of the public
  contract for consumers and should be `#[allow(dead_code)]`'d with a comment
  explaining why.

### 3. Testing & CI
- **No CI pipeline at all.** There's no `.github/workflows/`, so `cargo test`,
  `cargo clippy`, and cross-platform verification (the README explicitly
  claims Linux/macOS/Windows support) only happen when someone remembers to
  run them locally. Given the crate has Windows-specific and Unix-specific
  `#[cfg(...)]` code paths that can't be compile-checked on a single dev
  machine, this is the highest-leverage gap to close.
- **`cargo clippy` currently reports 40 warnings** (mostly doctest `#[test]`
  attributes, a few real logic nits — see above). None are gated in CI
  because there is no CI.
- **No test coverage for Windows-only code** (`get_current_user_domain`,
  Windows symlink creation, Windows temp-folder path) can run in this
  environment — needs a Windows CI runner to ever be exercised.
- **No fuzz/property tests** around `FilterString::is_valid_input_string` or
  `resolve_symlink_path`, both of which do manual string parsing that's easy
  to get subtly wrong on edge cases (empty strings, trailing separators,
  repeated `../..`).
- **No benchmarks** for the chunked copy path, despite it being the most
  performance-sensitive code in the crate (block size default 100KB is a
  hardcoded magic number with no measurement backing it).

### 4. Documentation
- **README feature list is stale** — it lists high-level categories (Read,
  Create, Delete, Copy, etc.) but omits several already-shipped functions:
  `file_length`, `copy_file_with_callbacks*` (progress/cancel/partial-range
  variants), `read_or_create_and_write`, `build_path`/`root`/`folder_up`,
  `resolve_symlink_path`, `file_modified_utc`. A new user reading the README
  would not discover half the API.
- **No CHANGELOG.md** — 13 tagged releases (1.0.0 → 12.0.0) with no changelog
  makes it hard for consumers to know what changed between versions before
  upgrading.
- **No CONTRIBUTING.md** — multi-license project (MIT/Apache-2.0/ISC/LGPL-3.0/
  CC-BY-4.0) with no guidance on how a contributor should format commits (the
  git history is 30+ commits all messaged "wip"), run tests, or pick a
  license header for new files.
- **`write_file`'s append-only behavior is not documented** in its doc
  comment/README — a new consumer would reasonably expect "write" to mean
  "create or overwrite," not "append to an existing file."

### 5. Tooling
- **No `rustfmt.toml` / `clippy.toml`** — no enforced style baseline.
- **No `Cargo.lock` committed** (it's gitignored) — reasonable for a library,
  but worth confirming that's intentional rather than incidental.
- **No MSRV (minimum supported Rust version) declared** in `Cargo.toml`,
  despite `edition = "2021"` being set.

## Proposed roadmap

### Phase 1 — Safety & CI foundation (do first, low risk)
1. Add `.github/workflows/ci.yml` running `cargo build`, `cargo test`, and
   `cargo clippy -- -D warnings` on a matrix of `ubuntu-latest`,
   `macos-latest`, `windows-latest` — this is the single highest-value change
   since it protects every future change and finally exercises the
   Windows-only code paths.
2. Fix the clippy-flagged `unwrap`-after-check patterns (real fragility, not
   just style).
3. Route `symlink_ext_impl` path inputs through
   `FilterString::is_valid_input_string` for consistency with `file_ext_impl`
   / `directory_ext_impl`.
4. Decide on the `Symbol` dead fields: delete unused ones or annotate with
   `#[allow(dead_code)]` + a one-line reason.
5. Add a regression test for zero-length file copy (guards the
   `file_length - 1` underflow risk).

### Phase 2 — Close API gaps
6. Add `FileExt::move_file` / `FileExt::rename` (thin wrapper over
   `fs::rename`, with a cross-device fallback to copy+delete).
7. Add `FileExt::list_directory` (non-recursive) returning entry names/paths,
   as the foundation for:
8. Add `FileExt::copy_directory` (recursive tree copy), built on #7.
9. Consider replacing `delete_directory`'s shell-out with `fs::remove_dir_all`
   now that Phase 1 CI can catch any Windows-specific regression; keep the
   shell-based version behind a feature flag only if there's a concrete
   compatibility reason to keep it.
10. Split `write_file` semantics: keep current append behavior (rename or
    document clearly) and add an explicit `overwrite_file` /
    `write_file_at_offset` for callers who want create-or-truncate or
    positional writes.
11. Add basic metadata accessors: `is_readonly`, `set_readonly`,
    `file_created_utc`, `file_accessed_utc`.

### Phase 3 — Documentation & release hygiene
12. Regenerate the README feature list from the actual public API in
    `lib.rs` (or add a doc test / script that fails CI if they drift).
13. Add `CHANGELOG.md` starting from 12.0.0 going forward.
14. Add a short `CONTRIBUTING.md` (commit message convention, how to run
    `cargo fmt`/`cargo clippy`/`cargo test` before a PR, license header
    expectations given the multi-license setup).
15. Document `write_file`'s append semantics explicitly in its rustdoc and
    the README.

### Phase 4 — Hardening & performance (lower urgency)
16. Property/fuzz tests for `FilterString::is_valid_input_string` and
    `SymlinkExtImpl::resolve_symlink_path`.
17. Benchmarks (`cargo bench` via `criterion`) for the chunked copy path to
    validate/tune the 100KB default block size.
18. Declare an MSRV in `Cargo.toml` and check it in CI.

## Suggested sequencing

Phase 1 is small, low-risk, and unlocks safe iteration on everything else —
do it first regardless of what else gets prioritized. Phases 2–4 can be
reordered based on actual consumer demand (e.g., if no one's asked for
`move_file`, it can wait behind documentation fixes).