dircat 0.5.2

High-performance Rust utility that concatenates and displays directory contents, similar to the C++ DirCat.
Documentation
// src/filtering/lockfile.rs

use std::path::Path;

// List of common lockfile names (all lowercase for efficient comparison)
const LOCKFILE_NAMES: &[&str] = &[
    // --- Web Development (JavaScript/TypeScript) ---
    "package-lock.json",   // npm
    "yarn.lock",           // Yarn (v1, v2+ in classic mode)
    "pnpm-lock.yaml",      // pnpm
    "npm-shrinkwrap.json", // Legacy npm, for deployed applications
    "bun.lockb",           // Bun (binary format)
    "deno.lock",           // Deno
    // --- PHP ---
    "composer.lock",
    // --- Ruby ---
    "gemfile.lock", // Bundler
    // --- Python ---
    "poetry.lock",    // Poetry
    "pipfile.lock",   // Pipenv
    "pdm.lock",       // PDM
    "uv.lock",        // uv
    "conda-lock.yml", // conda-lock
    ".req.lock",      // req-tools
    // Note: `requirements.txt` is often used as a de-facto lockfile when generated by tools like `pip-tools` from a `requirements.in` file.

    // --- Go ---
    "go.sum",     // Go Modules (checksums, acts as a lock)
    "gopkg.lock", // dep (legacy)
    "glide.lock", // Glide (legacy)
    // --- Java / JVM ---
    "gradle.lockfile", // Gradle's central lockfile
    // Individual Gradle configuration lockfiles also exist, e.g., `gradle/dependency-locks/*.lockfile`
    "pom.xml.lock", // Generated by the `maven-lockfile` plugin for Maven
    // --- Scala ---
    "dependency-lock.json", // sbt-dependency-lock plugin for sbt
    // --- Clojure ---
    // Clojure's `deps.edn` ecosystem doesn't produce a lockfile by default, but tools can be used to pin the resolved tree.

    // --- .NET (C#/F#/VB.NET) ---
    "packages.lock.json",  // NuGet
    "paket.lock",          // Paket
    "project.assets.json", // Implicitly acts as a lockfile for transitive dependencies, generated by `dotnet restore`
    // --- Swift / Objective-C (Apple Ecosystem) ---
    "package.resolved",  // Swift Package Manager
    "podfile.lock",      // CocoaPods
    "cartfile.resolved", // Carthage
    // --- Elixir ---
    "mix.lock",
    // --- Erlang ---
    "rebar.lock",
    // --- Dart / Flutter ---
    "pubspec.lock",
    // --- Haskell ---
    "stack.yaml.lock",      // Stack
    "cabal.project.freeze", // Cabal
    // --- Rust ---
    "cargo.lock",
    // --- Nix / NixOS ---
    "flake.lock",
    // --- Perl ---
    "cpanfile.snapshot", // Carton / cpanm
    "MYMETA.yml",        // Generated by build tools, contains resolved dependency versions
    "MYMETA.json",       // JSON version of MYMETA.yml
    // --- R ---
    "renv.lock",            // renv
    "packrat/packrat.lock", // Packrat
    // --- Julia ---
    "manifest.toml",
    // --- Infrastructure as Code / DevOps ---
    ".terraform.lock.hcl", // Terraform
    "Pulumi.lock.yaml",    // Pulumi
    "Chart.lock",          // Helm (Kubernetes package manager)
    "Berksfile.lock",      // Berkshelf (Chef)
    "Puppetfile.lock",     // r10k (Puppet)
    // Ansible uses `requirements.yml` but doesn't generate a separate lockfile by default.

    // --- Crystal ---
    "shard.lock",
    // --- C/C++ ---
    "conan.lock",                    // Conan
    "cpm.cmake.lock",                // CPM.cmake
    "subprojects/packagecache.json", // Meson build system wrap cache
    // vcpkg uses "baselines" in vcpkg.json, not a separate lockfile typically

    // --- Embedded Development ---
    "platformio.lock", // PlatformIO
    // --- Lua ---
    "luarocks.lock", // luarocks-lock
    // --- Elm ---
    "elm-stuff/exact-dependencies.json",
    // --- OCaml / ReasonML ---
    "esy.lock/index.json", // esy
    "opam.lock",           // opam (can be named this)
    "*.opam.locked",       // opam (another common pattern)
    // --- Nim ---
    "nimble.lock",
    // --- D ---
    "dub.selections.json",
    // --- Zig ---
    "zig.lock", // Zig Package Manager (>= 0.12)
    // --- Haxe ---
    "haxelib.lock", // lix package manager
    // --- V ---
    "v.lock", // V package manager
    // --- Common Lisp ---
    "qlfile.lock", // Qlot
    // --- BuckleScript / ReScript ---
    // Often uses Node.js ecosystem tools (npm/yarn/pnpm), covered above

    // --- Bazel ---
    "module.bazel.lock", // bzlmod
    // --- System-Level Package Managers ---
    "Brewfile.lock.json", // Homebrew Bundle (macOS)
];

/// Checks if a path corresponds to a common lockfile name or path suffix.
pub(crate) fn is_lockfile(path: &Path) -> bool {
    // The `path` argument is the absolute path of the file being checked.
    // The `lockfile` entries are relative paths/names.
    // `path.ends_with` is case-sensitive, but lockfile names can vary in case
    // (e.g., `Yarn.lock` vs `yarn.lock`). We perform a case-insensitive check.
    // We also normalize path separators to '/' for cross-platform consistency.
    let path_str_lower = path.to_string_lossy().to_lowercase().replace('\\', "/");
    LOCKFILE_NAMES
        .iter()
        .any(|&lockfile| path_str_lower.ends_with(lockfile))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_is_lockfile_matches() {
        assert!(is_lockfile(&PathBuf::from("path/to/Cargo.lock")));
        assert!(is_lockfile(&PathBuf::from("package-lock.json")));
        assert!(is_lockfile(&PathBuf::from("Yarn.lock"))); // Case insensitive
        assert!(is_lockfile(&PathBuf::from("PNPM-LOCK.YAML"))); // Case insensitive
        assert!(is_lockfile(&PathBuf::from("go.sum")));
        assert!(is_lockfile(&PathBuf::from("Gemfile.lock"))); // Check original case
        assert!(is_lockfile(&PathBuf::from("gemfile.lock"))); // Check lowercase
        assert!(is_lockfile(&PathBuf::from("Pipfile.lock"))); // Check original case
        assert!(is_lockfile(&PathBuf::from("pipfile.lock"))); // Check lowercase
        assert!(is_lockfile(&PathBuf::from("Manifest.toml"))); // Check original case
        assert!(is_lockfile(&PathBuf::from("manifest.toml"))); // Check lowercase
    }

    #[test]
    fn test_is_lockfile_no_match() {
        assert!(!is_lockfile(&PathBuf::from("src/main.rs")));
        assert!(!is_lockfile(&PathBuf::from("Cargo.toml")));
        assert!(!is_lockfile(&PathBuf::from("lockfile.txt")));
        assert!(!is_lockfile(&PathBuf::from("noextension")));
        assert!(!is_lockfile(&PathBuf::from("path/to/"))); // Directory path
    }

    #[test]
    fn test_is_lockfile_root() {
        assert!(is_lockfile(&PathBuf::from("Cargo.lock")));
    }
}