From 4a903008072357aab1c7be31b3343ce7b654822e Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Sun, 22 Feb 2026 18:37:39 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20karapace-tui=20=E2=80=94=20interactive?= =?UTF-8?q?=20terminal=20UI=20for=20environment=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ratatui + crossterm based TUI - List/Detail/Help views with vim-style keybindings (j/k, g/G, Enter, q) - Search/filter (/), sort cycling (s/S) - Freeze, archive, rename actions from UI - Destroy with confirmation dialog - Color-coded environment states --- crates/karapace-tui/Cargo.toml | 23 + crates/karapace-tui/karapace-tui.cdx.json | 3757 +++++++++++++++++++++ crates/karapace-tui/src/app.rs | 448 +++ crates/karapace-tui/src/lib.rs | 219 ++ crates/karapace-tui/src/ui.rs | 251 ++ 5 files changed, 4698 insertions(+) create mode 100644 crates/karapace-tui/Cargo.toml create mode 100644 crates/karapace-tui/karapace-tui.cdx.json create mode 100644 crates/karapace-tui/src/app.rs create mode 100644 crates/karapace-tui/src/lib.rs create mode 100644 crates/karapace-tui/src/ui.rs diff --git a/crates/karapace-tui/Cargo.toml b/crates/karapace-tui/Cargo.toml new file mode 100644 index 0000000..41e3007 --- /dev/null +++ b/crates/karapace-tui/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "karapace-tui" +description = "Terminal UI for Karapace environment manager" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[lib] +name = "karapace_tui" +path = "src/lib.rs" + +[dependencies] +ratatui.workspace = true +crossterm.workspace = true +karapace-core = { path = "../karapace-core" } +karapace-store = { path = "../karapace-store" } + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/karapace-tui/karapace-tui.cdx.json b/crates/karapace-tui/karapace-tui.cdx.json new file mode 100644 index 0000000..1a1ec53 --- /dev/null +++ b/crates/karapace-tui/karapace-tui.cdx.json @@ -0,0 +1,3757 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.3", + "version": 1, + "serialNumber": "urn:uuid:eb92188f-44e6-4f33-a3fb-fb253939a94c", + "metadata": { + "timestamp": "2026-02-22T14:03:10.604219273Z", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cargo-cyclonedx", + "version": "0.5.5" + } + ], + "component": { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0", + "name": "karapace-tui", + "version": "0.1.0", + "description": "Terminal UI for Karapace environment manager", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-tui@0.1.0?download_url=file://.", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ], + "components": [ + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0 bin-target-0", + "name": "karapace_tui", + "version": "0.1.0", + "purl": "pkg:cargo/karapace-tui@0.1.0?download_url=file://.#src/lib.rs" + } + ] + } + }, + "components": [ + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "name": "karapace-core", + "version": "0.1.0", + "description": "Build engine, lifecycle state machine, drift control, and concurrency for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-core@0.1.0?download_url=file://../karapace-core", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "name": "karapace-remote", + "version": "0.1.0", + "description": "Remote content-addressable store for Karapace environment sharing", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-remote@0.1.0?download_url=file://../karapace-remote", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "name": "karapace-runtime", + "version": "0.1.0", + "description": "Container runtime backends, image management, sandbox, and host integration for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-runtime@0.1.0?download_url=file://../karapace-runtime", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "name": "karapace-schema", + "version": "0.1.0", + "description": "Manifest parsing, normalization, identity hashing, and lock file for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-schema@0.1.0?download_url=file://../karapace-schema", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "name": "karapace-store", + "version": "0.1.0", + "description": "Content-addressable store, metadata, layers, GC, and integrity for Karapace", + "scope": "required", + "licenses": [ + { + "expression": "EUPL-1.2" + } + ], + "purl": "pkg:cargo/karapace-store@0.1.0?download_url=file://../karapace-store", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/marcoallegretti/karapace" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "name": "adler2", + "version": "2.0.1", + "description": "A simple clean-room implementation of the Adler-32 checksum", + "scope": "required", + "licenses": [ + { + "expression": "0BSD OR MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/adler2@2.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/adler2/" + }, + { + "type": "vcs", + "url": "https://github.com/oyvindln/adler2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "name": "allocator-api2", + "version": "0.2.21", + "description": "Mirror of Rust's allocator API", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/allocator-api2@0.2.21", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/allocator-api2" + }, + { + "type": "website", + "url": "https://github.com/zakarumych/allocator-api2" + }, + { + "type": "vcs", + "url": "https://github.com/zakarumych/allocator-api2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "name": "arrayref", + "version": "0.3.9", + "description": "Macros to take array references of slices", + "scope": "required", + "licenses": [ + { + "expression": "BSD-2-Clause" + } + ], + "purl": "pkg:cargo/arrayref@0.3.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/arrayref" + }, + { + "type": "vcs", + "url": "https://github.com/droundy/arrayref" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "name": "arrayvec", + "version": "0.7.6", + "description": "A vector with fixed capacity, backed by an array (it can be stored on the stack too). Implements fixed capacity ArrayVec and ArrayString.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/arrayvec@0.7.6", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/arrayvec/" + }, + { + "type": "vcs", + "url": "https://github.com/bluss/arrayvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0", + "name": "autocfg", + "version": "1.5.0", + "description": "Automatic cfg for Rust compiler features", + "scope": "excluded", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/autocfg@1.5.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/autocfg/" + }, + { + "type": "vcs", + "url": "https://github.com/cuviper/autocfg" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "name": "base64", + "version": "0.22.1", + "description": "encodes and decodes base64 as bytes or utf8", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/base64@0.22.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/base64" + }, + { + "type": "vcs", + "url": "https://github.com/marshallpierce/rust-base64" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "name": "bitflags", + "version": "2.11.0", + "description": "A macro to generate structures which behave like bitflags. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/bitflags@2.11.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/bitflags" + }, + { + "type": "website", + "url": "https://github.com/bitflags/bitflags" + }, + { + "type": "vcs", + "url": "https://github.com/bitflags/bitflags" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "name": "blake3", + "version": "1.8.3", + "description": "the BLAKE3 hash function", + "scope": "required", + "licenses": [ + { + "expression": "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception" + } + ], + "purl": "pkg:cargo/blake3@1.8.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/blake3" + }, + { + "type": "vcs", + "url": "https://github.com/BLAKE3-team/BLAKE3" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "name": "bytes", + "version": "1.11.1", + "description": "Types and traits for working with bytes", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/bytes@1.11.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/tokio-rs/bytes" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "name": "cassowary", + "version": "0.3.0", + "description": "A Rust implementation of the Cassowary linear constraint solving algorithm. The Cassowary algorithm is designed for naturally laying out user interfaces using linear constraints, like 'this button must line up with this text box'. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cassowary@0.3.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://dylanede.github.io/cassowary-rs" + }, + { + "type": "website", + "url": "https://github.com/dylanede/cassowary-rs" + }, + { + "type": "vcs", + "url": "https://github.com/dylanede/cassowary-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "name": "castaway", + "version": "0.2.4", + "description": "Safe, zero-cost downcasting for limited compile-time specialization.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/castaway@0.2.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/sagebind/castaway" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "name": "cc", + "version": "1.2.56", + "description": "A build-time dependency for Cargo build scripts to assist in invoking the native C compiler to compile native C code into a static archive to be linked into Rust code. ", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cc@1.2.56", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cc" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/cc-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "name": "cfg-if", + "version": "1.0.4", + "description": "A macro to ergonomically define an item depending on a large number of #[cfg] parameters. Structured like an if-else chain, the first matching branch is the item that gets emitted. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cfg-if@1.0.4", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/cfg-if" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "name": "cfg_aliases", + "version": "0.2.1", + "description": "A tiny utility to help save you a lot of effort with long winded `#[cfg()]` checks.", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/cfg_aliases@0.2.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cfg_aliases" + }, + { + "type": "website", + "url": "https://github.com/katharostech/cfg_aliases" + }, + { + "type": "vcs", + "url": "https://github.com/katharostech/cfg_aliases" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "name": "chrono", + "version": "0.4.43", + "description": "Date and time library for Rust", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/chrono@0.4.43", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/chrono/" + }, + { + "type": "website", + "url": "https://github.com/chronotope/chrono" + }, + { + "type": "vcs", + "url": "https://github.com/chronotope/chrono" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "name": "compact_str", + "version": "0.8.1", + "description": "A memory efficient string type that transparently stores strings on the stack, when possible", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/compact_str@0.8.1", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/ParkMyCar/compact_str" + }, + { + "type": "vcs", + "url": "https://github.com/ParkMyCar/compact_str" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "name": "constant_time_eq", + "version": "0.4.2", + "description": "Compares two equal-sized byte strings in constant time.", + "scope": "required", + "licenses": [ + { + "expression": "CC0-1.0 OR MIT-0 OR Apache-2.0" + } + ], + "purl": "pkg:cargo/constant_time_eq@0.4.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/constant_time_eq" + }, + { + "type": "vcs", + "url": "https://github.com/cesarb/constant_time_eq" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17", + "name": "cpufeatures", + "version": "0.2.17", + "description": "Lightweight runtime CPU feature detection for aarch64, loongarch64, and x86/x86_64 targets, with no_std support and support for mobile targets including Android and iOS ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/cpufeatures@0.2.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/cpufeatures" + }, + { + "type": "vcs", + "url": "https://github.com/RustCrypto/utils" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "name": "crc32fast", + "version": "1.5.0", + "description": "Fast, SIMD-accelerated CRC32 (IEEE) checksum computation", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/crc32fast@1.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/srijs/rust-crc32fast" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "name": "crossterm", + "version": "0.28.1", + "description": "A crossplatform terminal library for manipulating terminals.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/crossterm@0.28.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/crossterm/" + }, + { + "type": "vcs", + "url": "https://github.com/crossterm-rs/crossterm" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "name": "ctrlc", + "version": "3.5.2", + "description": "Easy Ctrl-C handler for Rust projects", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ctrlc@3.5.2", + "externalReferences": [ + { + "type": "documentation", + "url": "https://detegr.github.io/doc/ctrlc" + }, + { + "type": "website", + "url": "https://github.com/Detegr/rust-ctrlc" + }, + { + "type": "vcs", + "url": "https://github.com/Detegr/rust-ctrlc.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "name": "darling", + "version": "0.23.0", + "description": "A proc-macro library for reading attributes into structs when implementing custom derives. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling@0.23.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/darling/0.23.0" + }, + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "name": "darling_core", + "version": "0.23.0", + "description": "Helper crate for proc-macro library for reading attributes into structs when implementing custom derives. Use https://crates.io/crates/darling in your code. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling_core@0.23.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0", + "name": "darling_macro", + "version": "0.23.0", + "description": "Internal support for a proc-macro library for reading attributes into structs when implementing custom derives. Use https://crates.io/crates/darling in your code. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/darling_macro@0.23.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/TedDriggs/darling" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0", + "name": "either", + "version": "1.15.0", + "description": "The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/either@1.15.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/either/1/" + }, + { + "type": "vcs", + "url": "https://github.com/rayon-rs/either" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "name": "equivalent", + "version": "1.0.2", + "description": "Traits for key comparison in maps.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/equivalent@1.0.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/indexmap-rs/equivalent" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "name": "errno", + "version": "0.3.14", + "description": "Cross-platform interface to the `errno` variable.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/errno@0.3.14", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/errno" + }, + { + "type": "vcs", + "url": "https://github.com/lambda-fairy/rust-errno" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "name": "fastrand", + "version": "2.3.0", + "description": "A simple and fast random number generator", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/fastrand@2.3.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/smol-rs/fastrand" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "name": "filetime", + "version": "0.2.27", + "description": "Platform-agnostic accessors of timestamps in File metadata ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/filetime@0.2.27", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/filetime" + }, + { + "type": "website", + "url": "https://github.com/alexcrichton/filetime" + }, + { + "type": "vcs", + "url": "https://github.com/alexcrichton/filetime" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "name": "find-msvc-tools", + "version": "0.1.9", + "description": "Find windows-specific tools, read MSVC versions from the registry and from COM interfaces", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/find-msvc-tools@0.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/find-msvc-tools" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/cc-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "name": "flate2", + "version": "1.1.9", + "description": "DEFLATE compression and decompression exposed as Read/BufRead/Write streams. Supports miniz_oxide and multiple zlib implementations. Supports zlib, gzip, and raw deflate streams. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/flate2@1.1.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/flate2" + }, + { + "type": "website", + "url": "https://github.com/rust-lang/flate2-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/flate2-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5", + "name": "foldhash", + "version": "0.1.5", + "description": "A fast, non-cryptographic, minimally DoS-resistant hashing algorithm.", + "scope": "required", + "licenses": [ + { + "expression": "Zlib" + } + ], + "purl": "pkg:cargo/foldhash@0.1.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/orlp/foldhash" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "name": "fs2", + "version": "0.4.3", + "description": "Cross-platform file locks and file duplication.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/fs2@0.4.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/fs2" + }, + { + "type": "vcs", + "url": "https://github.com/danburkert/fs2-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "name": "getrandom", + "version": "0.2.17", + "description": "A small cross-platform library for retrieving random data from system source", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/getrandom@0.2.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/getrandom" + }, + { + "type": "vcs", + "url": "https://github.com/rust-random/getrandom" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "name": "getrandom", + "version": "0.4.1", + "description": "A small cross-platform library for retrieving random data from system source", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/getrandom@0.4.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/getrandom" + }, + { + "type": "vcs", + "url": "https://github.com/rust-random/getrandom" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5", + "name": "hashbrown", + "version": "0.15.5", + "description": "A Rust port of Google's SwissTable hash map", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hashbrown@0.15.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/hashbrown" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "name": "hashbrown", + "version": "0.16.1", + "description": "A Rust port of Google's SwissTable hash map", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/hashbrown@0.16.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/hashbrown" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "name": "heck", + "version": "0.5.0", + "description": "heck is a case conversion library.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/heck@0.5.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/withoutboats/heck" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "name": "http", + "version": "1.4.0", + "description": "A set of types for representing HTTP requests and responses. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/http@1.4.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/http" + }, + { + "type": "vcs", + "url": "https://github.com/hyperium/http" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "name": "httparse", + "version": "1.10.1", + "description": "A tiny, safe, speedy, zero-copy HTTP/1.x parser.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/httparse@1.10.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/httparse" + }, + { + "type": "vcs", + "url": "https://github.com/seanmonstar/httparse" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "name": "iana-time-zone", + "version": "0.1.65", + "description": "get the IANA time zone for the current system", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/iana-time-zone@0.1.65", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/strawlab/iana-time-zone" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "name": "ident_case", + "version": "1.0.1", + "description": "Utility for applying case rules to Rust identifiers.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ident_case@1.0.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ident_case/1.0.1" + }, + { + "type": "vcs", + "url": "https://github.com/TedDriggs/ident_case" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "name": "indexmap", + "version": "2.13.0", + "description": "A hash table with consistent order and fast iteration.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/indexmap@2.13.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/indexmap/" + }, + { + "type": "vcs", + "url": "https://github.com/indexmap-rs/indexmap" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "name": "indoc", + "version": "2.0.7", + "description": "Indented document literals", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/indoc@2.0.7", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/indoc" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/indoc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "name": "instability", + "version": "0.3.11", + "description": "Rust API stability attributes for the rest of us. A fork of the `stability` crate.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/instability@0.3.11", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/instability/" + }, + { + "type": "vcs", + "url": "https://github.com/ratatui/instability" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "name": "itertools", + "version": "0.13.0", + "description": "Extra iterator adaptors, iterator methods, free functions, and macros.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itertools@0.13.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itertools/" + }, + { + "type": "vcs", + "url": "https://github.com/rust-itertools/itertools" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "name": "itoa", + "version": "1.0.17", + "description": "Fast integer primitive to string conversion", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/itoa@1.0.17", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/itoa" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/itoa" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "name": "libc", + "version": "0.2.180", + "description": "Raw FFI bindings to platform libraries like libc.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/libc@0.2.180", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rust-lang/libc" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0", + "name": "linux-raw-sys", + "version": "0.11.0", + "description": "Generated bindings for Linux's userspace API", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/linux-raw-sys@0.11.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/linux-raw-sys" + }, + { + "type": "vcs", + "url": "https://github.com/sunfishcode/linux-raw-sys" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15", + "name": "linux-raw-sys", + "version": "0.4.15", + "description": "Generated bindings for Linux's userspace API", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/linux-raw-sys@0.4.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/linux-raw-sys" + }, + { + "type": "vcs", + "url": "https://github.com/sunfishcode/linux-raw-sys" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "name": "lock_api", + "version": "0.4.14", + "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/lock_api@0.4.14", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "name": "log", + "version": "0.4.29", + "description": "A lightweight logging facade for Rust ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/log@0.4.29", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/log" + }, + { + "type": "vcs", + "url": "https://github.com/rust-lang/log" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "name": "lru", + "version": "0.12.5", + "description": "A LRU cache implementation", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/lru@0.12.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/lru/" + }, + { + "type": "website", + "url": "https://github.com/jeromefroe/lru-rs" + }, + { + "type": "vcs", + "url": "https://github.com/jeromefroe/lru-rs.git" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "name": "memchr", + "version": "2.8.0", + "description": "Provides extremely fast (uses SIMD on x86_64, aarch64 and wasm32) routines for 1, 2 or 3 byte search and single substring search. ", + "scope": "required", + "licenses": [ + { + "expression": "Unlicense OR MIT" + } + ], + "purl": "pkg:cargo/memchr@2.8.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/memchr/" + }, + { + "type": "website", + "url": "https://github.com/BurntSushi/memchr" + }, + { + "type": "vcs", + "url": "https://github.com/BurntSushi/memchr" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9", + "name": "miniz_oxide", + "version": "0.8.9", + "description": "DEFLATE compression and decompression library rewritten in Rust based on miniz", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Zlib OR Apache-2.0" + } + ], + "purl": "pkg:cargo/miniz_oxide@0.8.9", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/miniz_oxide" + }, + { + "type": "website", + "url": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide" + }, + { + "type": "vcs", + "url": "https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "name": "mio", + "version": "1.1.1", + "description": "Lightweight non-blocking I/O.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/mio@1.1.1", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/tokio-rs/mio" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/mio" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1", + "name": "nix", + "version": "0.31.1", + "description": "Rust friendly bindings to *nix APIs", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/nix@0.31.1", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/nix-rust/nix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "name": "num-traits", + "version": "0.2.19", + "description": "Numeric traits for generic mathematics", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/num-traits@0.2.19", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/num-traits" + }, + { + "type": "website", + "url": "https://github.com/rust-num/num-traits" + }, + { + "type": "vcs", + "url": "https://github.com/rust-num/num-traits" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "name": "once_cell", + "version": "1.21.3", + "description": "Single assignment cells and lazy values.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/once_cell@1.21.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/once_cell" + }, + { + "type": "vcs", + "url": "https://github.com/matklad/once_cell" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "name": "parking_lot", + "version": "0.12.5", + "description": "More compact and efficient implementations of the standard synchronization primitives.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/parking_lot@0.12.5", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12", + "name": "parking_lot_core", + "version": "0.9.12", + "description": "An advanced API for creating custom synchronization primitives.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/parking_lot_core@0.9.12", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/Amanieu/parking_lot" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "name": "paste", + "version": "1.0.15", + "description": "Macros for all your token pasting needs", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/paste@1.0.15", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/paste" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/paste" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "name": "percent-encoding", + "version": "2.3.2", + "description": "Percent encoding and decoding", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/percent-encoding@2.3.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/servo/rust-url/" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "name": "pin-project-lite", + "version": "0.2.16", + "description": "A lightweight version of pin-project written with declarative macros. ", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/pin-project-lite@0.2.16", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/taiki-e/pin-project-lite" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "name": "proc-macro2", + "version": "1.0.106", + "description": "A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/proc-macro2@1.0.106", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/proc-macro2" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/proc-macro2" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "name": "quote", + "version": "1.0.44", + "description": "Quasi-quoting macro quote!(...)", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/quote@1.0.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/quote/" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/quote" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0", + "name": "ratatui", + "version": "0.29.0", + "description": "A library that's all about cooking up terminal user interfaces", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/ratatui@0.29.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ratatui/latest/ratatui/" + }, + { + "type": "website", + "url": "https://ratatui.rs" + }, + { + "type": "vcs", + "url": "https://github.com/ratatui/ratatui" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "name": "ring", + "version": "0.17.14", + "description": "An experiment.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 AND ISC" + } + ], + "purl": "pkg:cargo/ring@0.17.14", + "externalReferences": [ + { + "type": "other", + "url": "ring_core_0_17_14_" + }, + { + "type": "vcs", + "url": "https://github.com/briansmith/ring" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "name": "rustix", + "version": "0.38.44", + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/rustix@0.38.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustix" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/rustix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3", + "name": "rustix", + "version": "1.1.3", + "description": "Safe Rust bindings to POSIX/Unix/Linux/Winsock-like syscalls", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/rustix@1.1.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustix" + }, + { + "type": "vcs", + "url": "https://github.com/bytecodealliance/rustix" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "name": "rustls-pki-types", + "version": "1.14.0", + "description": "Shared types for the rustls PKI ecosystem", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/rustls-pki-types@1.14.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustls-pki-types" + }, + { + "type": "website", + "url": "https://github.com/rustls/pki-types" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/pki-types" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "name": "rustls-webpki", + "version": "0.103.9", + "description": "Web PKI X.509 Certificate Verification.", + "scope": "required", + "licenses": [ + { + "expression": "ISC" + } + ], + "purl": "pkg:cargo/rustls-webpki@0.103.9", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/rustls/webpki" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "name": "rustls", + "version": "0.23.36", + "description": "Rustls is a modern TLS library written in Rust.", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR ISC OR MIT" + } + ], + "purl": "pkg:cargo/rustls@0.23.36", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/rustls/rustls" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/rustls" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "name": "rustversion", + "version": "1.0.22", + "description": "Conditional compilation according to rustc compiler version", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/rustversion@1.0.22", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/rustversion" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/rustversion" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "name": "ryu", + "version": "1.0.23", + "description": "Fast floating point to string conversion", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR BSL-1.0" + } + ], + "purl": "pkg:cargo/ryu@1.0.23", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/ryu" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/ryu" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0", + "name": "scopeguard", + "version": "1.2.0", + "description": "A RAII scope guard that will run a given closure when it goes out of scope, even if the code between panics (assuming unwinding panic). Defines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as shorthands for guards with one of the implemented strategies. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/scopeguard@1.2.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/scopeguard/" + }, + { + "type": "vcs", + "url": "https://github.com/bluss/scopeguard" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "name": "serde", + "version": "1.0.228", + "description": "A generic serialization/deserialization framework", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "name": "serde_core", + "version": "1.0.228", + "description": "Serde traits only, with no support for derive -- use the `serde` crate instead", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_core@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_core" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "name": "serde_derive", + "version": "1.0.228", + "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_derive@1.0.228", + "externalReferences": [ + { + "type": "documentation", + "url": "https://serde.rs/derive.html" + }, + { + "type": "website", + "url": "https://serde.rs" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/serde" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "name": "serde_json", + "version": "1.0.149", + "description": "A JSON serialization file format", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_json@1.0.149", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/serde_json" + }, + { + "type": "vcs", + "url": "https://github.com/serde-rs/json" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "name": "serde_spanned", + "version": "0.6.9", + "description": "Serde-compatible spanned Value", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/serde_spanned@0.6.9", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0", + "name": "shlex", + "version": "1.3.0", + "description": "Split a string into shell words, like Python's shlex.", + "scope": "excluded", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/shlex@1.3.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/comex/rust-shlex" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5", + "name": "signal-hook-mio", + "version": "0.2.5", + "description": "MIO support for signal-hook", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/signal-hook-mio@0.2.5", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook-mio" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8", + "name": "signal-hook-registry", + "version": "1.4.8", + "description": "Backend crate for signal-hook", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/signal-hook-registry@1.4.8", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook-registry" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "name": "signal-hook", + "version": "0.3.18", + "description": "Unix signal handling", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/signal-hook@0.3.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/signal-hook" + }, + { + "type": "vcs", + "url": "https://github.com/vorner/signal-hook" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8", + "name": "simd-adler32", + "version": "0.3.8", + "description": "A SIMD-accelerated Adler-32 hash algorithm implementation.", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/simd-adler32@0.3.8", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/mcountryman/simd-adler32" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "name": "smallvec", + "version": "1.15.1", + "description": "'Small vector' optimization: store up to a small number of items on the stack", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/smallvec@1.15.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/smallvec/" + }, + { + "type": "vcs", + "url": "https://github.com/servo/rust-smallvec" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0", + "name": "static_assertions", + "version": "1.1.0", + "description": "Compile-time assertions to ensure that invariants are met.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/static_assertions@1.1.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/static_assertions/" + }, + { + "type": "website", + "url": "https://github.com/nvzqz/static-assertions-rs" + }, + { + "type": "vcs", + "url": "https://github.com/nvzqz/static-assertions-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "name": "strsim", + "version": "0.11.1", + "description": "Implementations of string similarity metrics. Includes Hamming, Levenshtein, OSA, Damerau-Levenshtein, Jaro, Jaro-Winkler, and Sørensen-Dice. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strsim@0.11.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strsim/" + }, + { + "type": "website", + "url": "https://github.com/rapidfuzz/strsim-rs" + }, + { + "type": "vcs", + "url": "https://github.com/rapidfuzz/strsim-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "name": "strum", + "version": "0.26.3", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum@0.26.3", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4", + "name": "strum_macros", + "version": "0.26.4", + "description": "Helpful macros for working with enums and strings", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/strum_macros@0.26.4", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/strum" + }, + { + "type": "website", + "url": "https://github.com/Peternator7/strum" + }, + { + "type": "vcs", + "url": "https://github.com/Peternator7/strum" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "name": "subtle", + "version": "2.6.1", + "description": "Pure-Rust traits and utilities for constant-time cryptographic implementations.", + "scope": "required", + "licenses": [ + { + "expression": "BSD-3-Clause" + } + ], + "purl": "pkg:cargo/subtle@2.6.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/subtle" + }, + { + "type": "website", + "url": "https://dalek.rs/" + }, + { + "type": "vcs", + "url": "https://github.com/dalek-cryptography/subtle" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "name": "syn", + "version": "2.0.117", + "description": "Parser for Rust source code", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/syn@2.0.117", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/syn" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/syn" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "name": "tar", + "version": "0.4.44", + "description": "A Rust implementation of a TAR file reader and writer. This library does not currently handle compression, but it is abstract over all I/O readers and writers. Additionally, great lengths are taken to ensure that the entire contents are never required to be entirely resident in memory all at once. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/tar@0.4.44", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/tar" + }, + { + "type": "website", + "url": "https://github.com/alexcrichton/tar-rs" + }, + { + "type": "vcs", + "url": "https://github.com/alexcrichton/tar-rs" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "name": "tempfile", + "version": "3.25.0", + "description": "A library for managing temporary files and directories.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/tempfile@3.25.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/tempfile" + }, + { + "type": "website", + "url": "https://stebalien.com/projects/tempfile-rs/" + }, + { + "type": "vcs", + "url": "https://github.com/Stebalien/tempfile" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18", + "name": "thiserror-impl", + "version": "2.0.18", + "description": "Implementation detail of the `thiserror` crate", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/thiserror-impl@2.0.18", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/dtolnay/thiserror" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "name": "thiserror", + "version": "2.0.18", + "description": "derive(Error)", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/thiserror@2.0.18", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/thiserror" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/thiserror" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23", + "name": "toml", + "version": "0.8.23", + "description": "A native Rust encoder and decoder of TOML-formatted files and streams. Provides implementations of the standard Serialize/Deserialize traits for TOML data to facilitate deserializing and serializing Rust structures. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml@0.8.23", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "name": "toml_datetime", + "version": "0.6.11", + "description": "A TOML-compatible datetime type", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_datetime@0.6.11", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27", + "name": "toml_edit", + "version": "0.22.27", + "description": "Yet another format-preserving TOML parser.", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_edit@0.22.27", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "name": "toml_write", + "version": "0.1.2", + "description": "A low-level interface for writing out TOML ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/toml_write@0.1.2", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/toml-rs/toml" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "name": "tracing-attributes", + "version": "0.1.31", + "description": "Procedural macro attributes for automatically instrumenting functions. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-attributes@0.1.31", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36", + "name": "tracing-core", + "version": "0.1.36", + "description": "Core primitives for application-level tracing. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing-core@0.1.36", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "name": "tracing", + "version": "0.1.44", + "description": "Application-level tracing for Rust. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/tracing@0.1.44", + "externalReferences": [ + { + "type": "website", + "url": "https://tokio.rs" + }, + { + "type": "vcs", + "url": "https://github.com/tokio-rs/tracing" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "name": "unicode-ident", + "version": "1.0.24", + "description": "Determine whether characters have the XID_Start or XID_Continue properties according to Unicode Standard Annex #31", + "scope": "required", + "licenses": [ + { + "expression": "(MIT OR Apache-2.0) AND Unicode-3.0" + } + ], + "purl": "pkg:cargo/unicode-ident@1.0.24", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/unicode-ident" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/unicode-ident" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "name": "unicode-segmentation", + "version": "1.12.0", + "description": "This crate provides Grapheme Cluster, Word and Sentence boundaries according to Unicode Standard Annex #29 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-segmentation@1.12.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-segmentation" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-segmentation" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "name": "unicode-truncate", + "version": "1.1.0", + "description": "Unicode-aware algorithm to pad or truncate `str` in terms of displayed width. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-truncate@1.1.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/Aetf/unicode-truncate" + }, + { + "type": "vcs", + "url": "https://github.com/Aetf/unicode-truncate" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14", + "name": "unicode-width", + "version": "0.1.14", + "description": "Determine displayed width of `char` and `str` types according to Unicode Standard Annex #11 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-width@0.1.14", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-width" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-width" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0", + "name": "unicode-width", + "version": "0.2.0", + "description": "Determine displayed width of `char` and `str` types according to Unicode Standard Annex #11 rules. ", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/unicode-width@0.2.0", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/unicode-rs/unicode-width" + }, + { + "type": "vcs", + "url": "https://github.com/unicode-rs/unicode-width" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0", + "name": "untrusted", + "version": "0.9.0", + "description": "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust.", + "scope": "required", + "licenses": [ + { + "expression": "ISC" + } + ], + "purl": "pkg:cargo/untrusted@0.9.0", + "externalReferences": [ + { + "type": "documentation", + "url": "https://briansmith.org/rustdoc/untrusted/" + }, + { + "type": "vcs", + "url": "https://github.com/briansmith/untrusted" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "name": "ureq-proto", + "version": "0.5.3", + "description": "ureq support crate", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ureq-proto@0.5.3", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/algesten/ureq-proto" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0", + "name": "ureq", + "version": "3.2.0", + "description": "Simple, safe HTTP client", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/ureq@3.2.0", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/algesten/ureq" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "name": "utf-8", + "version": "0.7.6", + "description": "Incremental, zero-copy UTF-8 decoding with error handling", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/utf-8@0.7.6", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/SimonSapin/rust-utf8" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6", + "name": "webpki-roots", + "version": "1.0.6", + "description": "Mozilla's CA root certificates for use with webpki", + "scope": "required", + "licenses": [ + { + "expression": "CDLA-Permissive-2.0" + } + ], + "purl": "pkg:cargo/webpki-roots@1.0.6", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/rustls/webpki-roots" + }, + { + "type": "vcs", + "url": "https://github.com/rustls/webpki-roots" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14", + "name": "winnow", + "version": "0.7.14", + "description": "A byte-oriented, zero-copy, parser combinators library", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/winnow@0.7.14", + "externalReferences": [ + { + "type": "vcs", + "url": "https://github.com/winnow-rs/winnow" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1", + "name": "xattr", + "version": "1.6.1", + "description": "unix extended filesystem attributes", + "scope": "required", + "licenses": [ + { + "expression": "MIT OR Apache-2.0" + } + ], + "purl": "pkg:cargo/xattr@1.6.1", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/xattr" + }, + { + "type": "vcs", + "url": "https://github.com/Stebalien/xattr" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2", + "name": "zeroize", + "version": "1.8.2", + "description": "Securely clear secrets from memory with a simple trait built on stable Rust primitives which guarantee memory is zeroed using an operation will not be 'optimized away' by the compiler. Uses a portable pure Rust implementation that works everywhere, even WASM! ", + "scope": "required", + "licenses": [ + { + "expression": "Apache-2.0 OR MIT" + } + ], + "purl": "pkg:cargo/zeroize@1.8.2", + "externalReferences": [ + { + "type": "website", + "url": "https://github.com/RustCrypto/utils/tree/master/zeroize" + }, + { + "type": "vcs", + "url": "https://github.com/RustCrypto/utils" + } + ] + }, + { + "type": "library", + "bom-ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21", + "name": "zmij", + "version": "1.0.21", + "description": "A double-to-string conversion algorithm based on Schubfach and yy", + "scope": "required", + "licenses": [ + { + "expression": "MIT" + } + ], + "purl": "pkg:cargo/zmij@1.0.21", + "externalReferences": [ + { + "type": "documentation", + "url": "https://docs.rs/zmij" + }, + { + "type": "vcs", + "url": "https://github.com/dtolnay/zmij" + } + ] + } + ], + "dependencies": [ + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-remote#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-runtime#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-schema#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44" + ] + }, + { + "ref": "path+file:///home/lateuf/Projects/Karapace/crates/karapace-tui#0.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-core#0.1.0", + "path+file:///home/lateuf/Projects/Karapace/crates/karapace-store#0.1.0", + "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#blake3@1.8.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#arrayref@0.3.9", + "registry+https://github.com/rust-lang/crates.io-index#arrayvec@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#chrono@0.4.43", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#castaway@0.2.4", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#constant_time_eq@0.4.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#cpufeatures@0.2.17", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ctrlc@3.5.2", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#darling_macro@0.23.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling_core@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#find-msvc-tools@0.1.9", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#crc32fast@1.5.0", + "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#fs2@0.4.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#allocator-api2@0.2.21", + "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "registry+https://github.com/rust-lang/crates.io-index#foldhash@0.1.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bytes@1.11.1", + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#iana-time-zone@0.1.65", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ident_case@1.0.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#equivalent@1.0.2", + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.16.1", + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#darling@0.23.0", + "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#either@1.15.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#hashbrown@0.15.5" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#miniz_oxide@0.8.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#adler2@2.0.1", + "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#nix@0.31.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#cfg_aliases@0.2.1", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#num-traits@0.2.19", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#autocfg@1.5.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot@0.12.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#lock_api@0.4.14", + "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#parking_lot_core@0.9.12", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ratatui@0.29.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#cassowary@0.3.0", + "registry+https://github.com/rust-lang/crates.io-index#compact_str@0.8.1", + "registry+https://github.com/rust-lang/crates.io-index#crossterm@0.28.1", + "registry+https://github.com/rust-lang/crates.io-index#indoc@2.0.7", + "registry+https://github.com/rust-lang/crates.io-index#instability@0.3.11", + "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "registry+https://github.com/rust-lang/crates.io-index#lru@0.12.5", + "registry+https://github.com/rust-lang/crates.io-index#paste@1.0.15", + "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#cc@1.2.56", + "registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4", + "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.2.17", + "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@0.38.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.4.15" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#bitflags@2.11.0", + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#linux-raw-sys@0.11.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#ring@0.17.14", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#rustls-webpki@0.103.9", + "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ryu@1.0.23", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#scopeguard@1.2.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.228", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.149", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.17", + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0", + "registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#shlex@1.3.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-mio@0.2.5", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#mio@1.1.1", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#errno@0.3.14", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#signal-hook@0.3.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#signal-hook-registry@1.4.8" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#simd-adler32@0.3.8", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#smallvec@1.15.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#static_assertions@1.1.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strsim@0.11.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum@0.26.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#strum_macros@0.26.4", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#heck@0.5.0", + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#rustversion@1.0.22", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#subtle@2.6.1", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tar@0.4.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#filetime@0.2.27", + "registry+https://github.com/rust-lang/crates.io-index#libc@0.2.180", + "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.25.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#fastrand@2.3.0", + "registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.1", + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3", + "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.18", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.18" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml@0.8.23", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_edit@0.22.27", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#indexmap@2.13.0", + "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.228", + "registry+https://github.com/rust-lang/crates.io-index#serde_spanned@0.6.9", + "registry+https://github.com/rust-lang/crates.io-index#toml_datetime@0.6.11", + "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#toml_write@0.1.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.106", + "registry+https://github.com/rust-lang/crates.io-index#quote@1.0.44", + "registry+https://github.com/rust-lang/crates.io-index#syn@2.0.117" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#tracing@0.1.44", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.16", + "registry+https://github.com/rust-lang/crates.io-index#tracing-attributes@0.1.31", + "registry+https://github.com/rust-lang/crates.io-index#tracing-core@0.1.36" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-truncate@1.1.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#itertools@0.13.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-segmentation@1.12.0", + "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.1.14", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#unicode-width@0.2.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#untrusted@0.9.0", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#http@1.4.0", + "registry+https://github.com/rust-lang/crates.io-index#httparse@1.10.1", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#ureq@3.2.0", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#base64@0.22.1", + "registry+https://github.com/rust-lang/crates.io-index#flate2@1.1.9", + "registry+https://github.com/rust-lang/crates.io-index#log@0.4.29", + "registry+https://github.com/rust-lang/crates.io-index#percent-encoding@2.3.2", + "registry+https://github.com/rust-lang/crates.io-index#rustls@0.23.36", + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0", + "registry+https://github.com/rust-lang/crates.io-index#ureq-proto@0.5.3", + "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#utf-8@0.7.6", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#webpki-roots@1.0.6", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustls-pki-types@1.14.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#winnow@0.7.14", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.0" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#xattr@1.6.1", + "dependsOn": [ + "registry+https://github.com/rust-lang/crates.io-index#rustix@1.1.3" + ] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zeroize@1.8.2", + "dependsOn": [] + }, + { + "ref": "registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.21", + "dependsOn": [] + } + ] +} \ No newline at end of file diff --git a/crates/karapace-tui/src/app.rs b/crates/karapace-tui/src/app.rs new file mode 100644 index 0000000..40f0485 --- /dev/null +++ b/crates/karapace-tui/src/app.rs @@ -0,0 +1,448 @@ +use crossterm::event::KeyCode; +use karapace_core::Engine; +use karapace_store::EnvMetadata; +use std::path::{Path, PathBuf}; + +#[derive(Debug, PartialEq, Eq)] +pub enum AppAction { + None, + Quit, + Refresh, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum View { + List, + Detail, + Help, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InputMode { + Normal, + Search, + Rename, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SortColumn { + ShortId, + Name, + State, +} + +pub struct App { + pub store_root: PathBuf, + pub environments: Vec, + pub filtered: Vec, + pub selected: usize, + pub view: View, + pub input_mode: InputMode, + pub text_input: String, + pub input_cursor: usize, + pub filter: String, + pub sort_column: SortColumn, + pub sort_ascending: bool, + pub status_message: String, + pub show_confirm: Option, +} + +impl App { + pub fn new(store_root: &Path) -> Self { + Self { + store_root: store_root.to_path_buf(), + environments: Vec::new(), + filtered: Vec::new(), + selected: 0, + view: View::List, + input_mode: InputMode::Normal, + text_input: String::new(), + input_cursor: 0, + filter: String::new(), + sort_column: SortColumn::Name, + sort_ascending: true, + status_message: String::new(), + show_confirm: None, + } + } + + pub fn engine(&self) -> Engine { + Engine::new(&self.store_root) + } + + pub fn refresh(&mut self) -> Result<(), String> { + match self.engine().list() { + Ok(envs) => { + self.environments = envs; + self.apply_sort(); + self.apply_filter(); + self.status_message = format!("{} environment(s)", self.environments.len()); + Ok(()) + } + Err(e) => { + self.status_message = format!("error: {e}"); + Err(e.to_string()) + } + } + } + + pub fn apply_filter(&mut self) { + if self.filter.is_empty() { + self.filtered = (0..self.environments.len()).collect(); + } else { + let needle = self.filter.to_lowercase(); + self.filtered = self + .environments + .iter() + .enumerate() + .filter(|(_, e)| { + e.short_id.to_lowercase().contains(&needle) + || e.env_id.to_lowercase().contains(&needle) + || e.name + .as_deref() + .unwrap_or("") + .to_lowercase() + .contains(&needle) + || e.state.to_string().to_lowercase().contains(&needle) + }) + .map(|(i, _)| i) + .collect(); + } + if self.selected >= self.filtered.len() && !self.filtered.is_empty() { + self.selected = self.filtered.len() - 1; + } else if self.filtered.is_empty() { + self.selected = 0; + } + } + + pub fn apply_sort(&mut self) { + let asc = self.sort_ascending; + match self.sort_column { + SortColumn::ShortId => self.environments.sort_by(|a, b| { + let ord = a.short_id.cmp(&b.short_id); + if asc { + ord + } else { + ord.reverse() + } + }), + SortColumn::Name => self.environments.sort_by(|a, b| { + let ord = a + .name + .as_deref() + .unwrap_or("") + .cmp(b.name.as_deref().unwrap_or("")); + if asc { + ord + } else { + ord.reverse() + } + }), + SortColumn::State => self.environments.sort_by(|a, b| { + let ord = a.state.to_string().cmp(&b.state.to_string()); + if asc { + ord + } else { + ord.reverse() + } + }), + } + } + + pub fn selected_env(&self) -> Option<&EnvMetadata> { + self.filtered + .get(self.selected) + .and_then(|&i| self.environments.get(i)) + } + + pub fn visible_count(&self) -> usize { + self.filtered.len() + } + + pub fn handle_key(&mut self, key: KeyCode) -> AppAction { + // Search input mode + if self.input_mode == InputMode::Search { + return self.handle_search_key(key); + } + + // Rename input mode + if self.input_mode == InputMode::Rename { + return self.handle_rename_key(key); + } + + // Confirmation dialog active + if let Some(ref action) = self.show_confirm.clone() { + if let KeyCode::Char('y' | 'Y') = key { + self.execute_confirmed_action(action); + self.show_confirm = None; + return AppAction::Refresh; + } + self.show_confirm = None; + "cancelled".clone_into(&mut self.status_message); + return AppAction::None; + } + + match self.view { + View::Help => match key { + KeyCode::Char('q') | KeyCode::Esc => { + self.view = View::List; + AppAction::None + } + _ => AppAction::None, + }, + View::Detail => self.handle_detail_key(key), + View::List => self.handle_list_key(key), + } + } + + fn handle_detail_key(&mut self, key: KeyCode) -> AppAction { + match key { + KeyCode::Char('q') | KeyCode::Esc => { + self.view = View::List; + AppAction::None + } + KeyCode::Char('d') => { + self.prompt_destroy(); + AppAction::None + } + KeyCode::Char('f') => { + self.action_freeze(); + AppAction::Refresh + } + KeyCode::Char('a') => { + self.action_archive(); + AppAction::Refresh + } + KeyCode::Char('n') => { + self.start_rename(); + AppAction::None + } + _ => AppAction::None, + } + } + + fn handle_list_key(&mut self, key: KeyCode) -> AppAction { + match key { + KeyCode::Char('q') => AppAction::Quit, + KeyCode::Char('j') | KeyCode::Down => { + if !self.filtered.is_empty() { + self.selected = (self.selected + 1).min(self.filtered.len() - 1); + } + AppAction::None + } + KeyCode::Char('k') | KeyCode::Up => { + self.selected = self.selected.saturating_sub(1); + AppAction::None + } + KeyCode::Char('g') | KeyCode::Home => { + self.selected = 0; + AppAction::None + } + KeyCode::Char('G') | KeyCode::End => { + if !self.filtered.is_empty() { + self.selected = self.filtered.len() - 1; + } + AppAction::None + } + KeyCode::Enter => { + if self.selected_env().is_some() { + self.view = View::Detail; + } + AppAction::None + } + KeyCode::Char('r') => AppAction::Refresh, + KeyCode::Char('d') => { + self.prompt_destroy(); + AppAction::None + } + KeyCode::Char('f') => { + self.action_freeze(); + AppAction::Refresh + } + KeyCode::Char('a') => { + self.action_archive(); + AppAction::Refresh + } + KeyCode::Char('n') => { + self.start_rename(); + AppAction::None + } + KeyCode::Char('/') => { + self.input_mode = InputMode::Search; + self.text_input.clear(); + self.input_cursor = 0; + "search: ".clone_into(&mut self.status_message); + AppAction::None + } + KeyCode::Char('s') => { + self.cycle_sort(); + AppAction::None + } + KeyCode::Char('S') => { + self.sort_ascending = !self.sort_ascending; + self.apply_sort(); + self.apply_filter(); + AppAction::None + } + KeyCode::Char('?') => { + self.view = View::Help; + AppAction::None + } + _ => AppAction::None, + } + } + + fn handle_search_key(&mut self, key: KeyCode) -> AppAction { + match key { + KeyCode::Esc => { + self.input_mode = InputMode::Normal; + self.filter.clear(); + self.apply_filter(); + self.status_message = format!("{} environment(s)", self.environments.len()); + AppAction::None + } + KeyCode::Enter => { + self.input_mode = InputMode::Normal; + self.filter = self.text_input.clone(); + self.apply_filter(); + self.status_message = if self.filter.is_empty() { + format!("{} environment(s)", self.environments.len()) + } else { + format!( + "filter '{}': {} match(es)", + self.filter, + self.filtered.len() + ) + }; + AppAction::None + } + KeyCode::Char(c) => { + self.text_input.insert(self.input_cursor, c); + self.input_cursor += 1; + self.filter = self.text_input.clone(); + self.apply_filter(); + self.status_message = format!("search: {}", self.text_input); + AppAction::None + } + KeyCode::Backspace => { + if self.input_cursor > 0 { + self.input_cursor -= 1; + self.text_input.remove(self.input_cursor); + self.filter = self.text_input.clone(); + self.apply_filter(); + } + self.status_message = format!("search: {}", self.text_input); + AppAction::None + } + _ => AppAction::None, + } + } + + fn handle_rename_key(&mut self, key: KeyCode) -> AppAction { + match key { + KeyCode::Esc => { + self.input_mode = InputMode::Normal; + "rename cancelled".clone_into(&mut self.status_message); + AppAction::None + } + KeyCode::Enter => { + self.input_mode = InputMode::Normal; + let new_name = self.text_input.clone(); + if let Some(env) = self.selected_env() { + let env_id = env.env_id.clone(); + match self.engine().rename(&env_id, &new_name) { + Ok(()) => { + self.status_message = format!("renamed to '{new_name}'"); + } + Err(e) => { + self.status_message = format!("rename failed: {e}"); + } + } + } + AppAction::Refresh + } + KeyCode::Char(c) => { + self.text_input.insert(self.input_cursor, c); + self.input_cursor += 1; + self.status_message = format!("rename: {}", self.text_input); + AppAction::None + } + KeyCode::Backspace => { + if self.input_cursor > 0 { + self.input_cursor -= 1; + self.text_input.remove(self.input_cursor); + } + self.status_message = format!("rename: {}", self.text_input); + AppAction::None + } + _ => AppAction::None, + } + } + + fn prompt_destroy(&mut self) { + if let Some(env) = self.selected_env() { + let label = env.name.clone().unwrap_or_else(|| env.short_id.to_string()); + self.show_confirm = Some(format!("destroy:{}", env.env_id)); + self.status_message = format!("destroy '{label}'? (y/n)"); + } + } + + fn action_freeze(&mut self) { + if let Some(env) = self.selected_env() { + let env_id = env.env_id.to_string(); + let label = env.name.clone().unwrap_or_else(|| env.short_id.to_string()); + match self.engine().freeze(&env_id) { + Ok(()) => self.status_message = format!("frozen '{label}'"), + Err(e) => self.status_message = format!("freeze failed: {e}"), + } + } + } + + fn action_archive(&mut self) { + if let Some(env) = self.selected_env() { + let env_id = env.env_id.to_string(); + let label = env.name.clone().unwrap_or_else(|| env.short_id.to_string()); + match self.engine().archive(&env_id) { + Ok(()) => self.status_message = format!("archived '{label}'"), + Err(e) => self.status_message = format!("archive failed: {e}"), + } + } + } + + fn start_rename(&mut self) { + if self.selected_env().is_some() { + self.input_mode = InputMode::Rename; + self.text_input.clear(); + self.input_cursor = 0; + "rename: ".clone_into(&mut self.status_message); + } + } + + fn cycle_sort(&mut self) { + self.sort_column = match self.sort_column { + SortColumn::ShortId => SortColumn::Name, + SortColumn::Name => SortColumn::State, + SortColumn::State => SortColumn::ShortId, + }; + self.apply_sort(); + self.apply_filter(); + self.status_message = format!( + "sort: {:?} {}", + self.sort_column, + if self.sort_ascending { "↑" } else { "↓" } + ); + } + + fn execute_confirmed_action(&mut self, action: &str) { + if let Some(env_id) = action.strip_prefix("destroy:") { + match self.engine().destroy(env_id) { + Ok(()) => { + self.status_message = format!("destroyed {}", &env_id[..12.min(env_id.len())]); + } + Err(e) => { + self.status_message = format!("destroy failed: {e}"); + } + } + } + } +} diff --git a/crates/karapace-tui/src/lib.rs b/crates/karapace-tui/src/lib.rs new file mode 100644 index 0000000..e1bd7d1 --- /dev/null +++ b/crates/karapace-tui/src/lib.rs @@ -0,0 +1,219 @@ +//! Terminal UI for interactive Karapace environment management. +//! +//! This crate provides a ratatui-based TUI with environment listing, detail views, +//! search/filter, sorting, and keyboard-driven lifecycle actions (destroy, freeze, +//! archive, rename). + +mod app; +mod ui; + +pub use app::{App, AppAction, InputMode, SortColumn, View}; + +use crossterm::{ + event::{self, Event, KeyEventKind}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::prelude::*; +use std::io; +use std::path::Path; + +pub fn run(store_root: &Path) -> Result<(), String> { + enable_raw_mode().map_err(|e| format!("failed to enable raw mode: {e}"))?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen).map_err(|e| format!("alternate screen: {e}"))?; + + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend).map_err(|e| format!("terminal init: {e}"))?; + + let mut app = App::new(store_root); + app.refresh().ok(); + + let result = run_loop(&mut terminal, &mut app); + + disable_raw_mode().map_err(|e| format!("failed to disable raw mode: {e}"))?; + execute!(terminal.backend_mut(), LeaveAlternateScreen) + .map_err(|e| format!("leave alternate screen: {e}"))?; + terminal + .show_cursor() + .map_err(|e| format!("show cursor: {e}"))?; + + result +} + +fn run_loop( + terminal: &mut Terminal>, + app: &mut App, +) -> Result<(), String> { + loop { + terminal + .draw(|f| ui::draw(f, app)) + .map_err(|e| format!("draw: {e}"))?; + + if event::poll(std::time::Duration::from_millis(250)).map_err(|e| format!("poll: {e}"))? { + if let Event::Key(key) = event::read().map_err(|e| format!("read: {e}"))? { + if key.kind != KeyEventKind::Press { + continue; + } + match app.handle_key(key.code) { + AppAction::None => {} + AppAction::Quit => return Ok(()), + AppAction::Refresh => { + app.refresh().ok(); + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crossterm::event::KeyCode; + + fn make_app() -> (tempfile::TempDir, App) { + let dir = tempfile::tempdir().unwrap(); + let app = App::new(dir.path()); + (dir, app) + } + + #[test] + fn app_creates_and_refreshes() { + let (_dir, mut app) = make_app(); + assert!(app.refresh().is_ok() || app.refresh().is_err()); + } + + #[test] + fn app_quit_key() { + let (_dir, mut app) = make_app(); + assert_eq!(app.handle_key(KeyCode::Char('q')), AppAction::Quit); + } + + #[test] + fn app_refresh_key() { + let (_dir, mut app) = make_app(); + assert_eq!(app.handle_key(KeyCode::Char('r')), AppAction::Refresh); + } + + #[test] + fn app_navigation_j_k() { + let (_dir, mut app) = make_app(); + assert_eq!(app.handle_key(KeyCode::Char('j')), AppAction::None); + assert_eq!(app.handle_key(KeyCode::Char('k')), AppAction::None); + assert_eq!(app.selected, 0); + } + + #[test] + fn app_help_view() { + let (_dir, mut app) = make_app(); + app.handle_key(KeyCode::Char('?')); + assert_eq!(app.view, View::Help); + app.handle_key(KeyCode::Esc); + assert_eq!(app.view, View::List); + } + + #[test] + fn app_search_mode_enter_exit() { + let (_dir, mut app) = make_app(); + app.handle_key(KeyCode::Char('/')); + assert_eq!(app.input_mode, InputMode::Search); + app.handle_key(KeyCode::Esc); + assert_eq!(app.input_mode, InputMode::Normal); + assert!(app.filter.is_empty()); + } + + #[test] + fn app_search_typing() { + let (_dir, mut app) = make_app(); + app.handle_key(KeyCode::Char('/')); + app.handle_key(KeyCode::Char('t')); + app.handle_key(KeyCode::Char('e')); + app.handle_key(KeyCode::Char('s')); + app.handle_key(KeyCode::Char('t')); + assert_eq!(app.text_input, "test"); + app.handle_key(KeyCode::Enter); + assert_eq!(app.filter, "test"); + assert_eq!(app.input_mode, InputMode::Normal); + } + + #[test] + fn app_search_backspace() { + let (_dir, mut app) = make_app(); + app.handle_key(KeyCode::Char('/')); + app.handle_key(KeyCode::Char('a')); + app.handle_key(KeyCode::Char('b')); + app.handle_key(KeyCode::Backspace); + assert_eq!(app.text_input, "a"); + } + + #[test] + fn app_sort_cycle() { + let (_dir, mut app) = make_app(); + assert_eq!(app.sort_column, SortColumn::Name); + app.handle_key(KeyCode::Char('s')); + assert_eq!(app.sort_column, SortColumn::State); + app.handle_key(KeyCode::Char('s')); + assert_eq!(app.sort_column, SortColumn::ShortId); + app.handle_key(KeyCode::Char('s')); + assert_eq!(app.sort_column, SortColumn::Name); + } + + #[test] + fn app_sort_direction_toggle() { + let (_dir, mut app) = make_app(); + assert!(app.sort_ascending); + app.handle_key(KeyCode::Char('S')); + assert!(!app.sort_ascending); + app.handle_key(KeyCode::Char('S')); + assert!(app.sort_ascending); + } + + #[test] + fn app_detail_view_enter_back() { + let (_dir, mut app) = make_app(); + // No envs, Enter should not switch view + app.handle_key(KeyCode::Enter); + assert_eq!(app.view, View::List); + } + + #[test] + fn app_confirm_cancel() { + let (_dir, mut app) = make_app(); + app.show_confirm = Some("destroy:abc123".to_owned()); + app.handle_key(KeyCode::Char('n')); + assert!(app.show_confirm.is_none()); + assert_eq!(app.status_message, "cancelled"); + } + + #[test] + fn app_home_end_keys() { + let (_dir, mut app) = make_app(); + app.handle_key(KeyCode::Home); + assert_eq!(app.selected, 0); + app.handle_key(KeyCode::End); + assert_eq!(app.selected, 0); // No envs + } + + #[test] + fn app_rename_mode_enter_exit() { + let (_dir, mut app) = make_app(); + // No envs, so rename shouldn't activate + app.handle_key(KeyCode::Char('n')); + assert_eq!(app.input_mode, InputMode::Normal); + } + + #[test] + fn app_visible_count_empty() { + let (_dir, app) = make_app(); + assert_eq!(app.visible_count(), 0); + } + + #[test] + fn app_filter_with_no_envs() { + let (_dir, mut app) = make_app(); + app.filter = "test".to_owned(); + app.apply_filter(); + assert!(app.filtered.is_empty()); + } +} diff --git a/crates/karapace-tui/src/ui.rs b/crates/karapace-tui/src/ui.rs new file mode 100644 index 0000000..47fc17c --- /dev/null +++ b/crates/karapace-tui/src/ui.rs @@ -0,0 +1,251 @@ +use crate::app::{App, InputMode, View}; +use ratatui::{ + prelude::*, + widgets::{Block, Borders, Cell, Paragraph, Row, Table, Wrap}, +}; + +pub fn draw(f: &mut Frame<'_>, app: &App) { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Length(1), + Constraint::Min(5), + Constraint::Length(1), + ]) + .split(f.area()); + + draw_header(f, chunks[0]); + + match app.view { + View::List => draw_list(f, app, chunks[1]), + View::Detail => draw_detail(f, app, chunks[1]), + View::Help => draw_help(f, chunks[1]), + } + + draw_status_bar(f, app, chunks[2]); +} + +fn draw_header(f: &mut Frame<'_>, area: Rect) { + let title = Paragraph::new(format!( + " Karapace Environment Manager v{}", + env!("CARGO_PKG_VERSION") + )) + .style( + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ); + f.render_widget(title, area); +} + +fn draw_list(f: &mut Frame<'_>, app: &App, area: Rect) { + if app.environments.is_empty() { + let msg = Paragraph::new(" No environments found. Press 'q' to quit.").block( + Block::default() + .borders(Borders::ALL) + .title(" Environments "), + ); + f.render_widget(msg, area); + return; + } + + let header = Row::new(vec![ + Cell::from("SHORT_ID").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("NAME").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("STATE").style(Style::default().add_modifier(Modifier::BOLD)), + Cell::from("ENV_ID").style(Style::default().add_modifier(Modifier::BOLD)), + ]) + .height(1); + + let rows: Vec> = app + .filtered + .iter() + .enumerate() + .map(|(vi, &ei)| { + let env = &app.environments[ei]; + let style = if vi == app.selected { + Style::default() + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD) + } else { + Style::default() + }; + let state_style = state_color(&env.state.to_string()); + Row::new(vec![ + Cell::from(env.short_id.to_string()), + Cell::from(env.name.as_deref().unwrap_or("").to_owned()), + Cell::from(env.state.to_string()).style(state_style), + Cell::from(env.env_id.to_string()), + ]) + .style(style) + }) + .collect(); + + let table = Table::new( + rows, + [ + Constraint::Length(14), + Constraint::Length(16), + Constraint::Length(10), + Constraint::Min(20), + ], + ) + .header(header) + .block(Block::default().borders(Borders::ALL).title(format!( + " Environments ({}/{}) ", + app.visible_count(), + app.environments.len() + ))); + + f.render_widget(table, area); +} + +fn draw_detail(f: &mut Frame<'_>, app: &App, area: Rect) { + let Some(env) = app.selected_env() else { + let msg = Paragraph::new(" No environment selected.") + .block(Block::default().borders(Borders::ALL).title(" Detail ")); + f.render_widget(msg, area); + return; + }; + + let text = vec![ + Line::from(vec![ + Span::styled( + "env_id: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.env_id.to_string()), + ]), + Line::from(vec![ + Span::styled( + "short_id: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.short_id.to_string()), + ]), + Line::from(vec![ + Span::styled( + "name: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.name.as_deref().unwrap_or("(none)")), + ]), + Line::from(vec![ + Span::styled( + "state: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::styled(env.state.to_string(), state_color(&env.state.to_string())), + ]), + Line::from(vec![ + Span::styled( + "base_layer: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.base_layer.to_string()), + ]), + Line::from(vec![ + Span::styled( + "deps: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.dependency_layers.len().to_string()), + ]), + Line::from(vec![ + Span::styled( + "ref_count: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(env.ref_count.to_string()), + ]), + Line::from(vec![ + Span::styled( + "created_at: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(&env.created_at), + ]), + Line::from(vec![ + Span::styled( + "updated_at: ", + Style::default().add_modifier(Modifier::BOLD), + ), + Span::raw(&env.updated_at), + ]), + Line::from(""), + Line::from(Span::styled( + " [Esc] back [d] destroy [f] freeze [a] archive [n] rename", + Style::default().fg(Color::DarkGray), + )), + ]; + + let detail = Paragraph::new(text) + .block(Block::default().borders(Borders::ALL).title(format!( + " {} ", + env.name.as_deref().unwrap_or(&env.short_id) + ))) + .wrap(Wrap { trim: false }); + + f.render_widget(detail, area); +} + +fn draw_help(f: &mut Frame<'_>, area: Rect) { + let text = vec![ + Line::from(Span::styled( + "Keybindings", + Style::default().add_modifier(Modifier::BOLD), + )), + Line::from(""), + Line::from(" j / ↓ Move down"), + Line::from(" k / ↑ Move up"), + Line::from(" g / Home Go to top"), + Line::from(" G / End Go to bottom"), + Line::from(" Enter View details"), + Line::from(" d Destroy (with confirm)"), + Line::from(" f Freeze environment"), + Line::from(" a Archive environment"), + Line::from(" n Rename environment"), + Line::from(" / Search / filter"), + Line::from(" s Cycle sort column"), + Line::from(" S Toggle sort direction"), + Line::from(" r Refresh list"), + Line::from(" ? Show this help"), + Line::from(" q / Esc Quit / Back"), + ]; + + let help = Paragraph::new(text) + .block(Block::default().borders(Borders::ALL).title(" Help ")) + .wrap(Wrap { trim: false }); + + f.render_widget(help, area); +} + +fn draw_status_bar(f: &mut Frame<'_>, app: &App, area: Rect) { + let status = if app.show_confirm.is_some() || app.input_mode != InputMode::Normal { + Paragraph::new(format!(" {} ", app.status_message)).style( + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD), + ) + } else { + Paragraph::new(format!( + " {} │ [j/k] nav [Enter] detail [d] destroy [f] freeze [/] search [?] help [q] quit", + app.status_message + )) + .style(Style::default().fg(Color::DarkGray)) + }; + f.render_widget(status, area); +} + +fn state_color(state: &str) -> Style { + match state { + "built" => Style::default().fg(Color::Green), + "running" => Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + "defined" => Style::default().fg(Color::Yellow), + "frozen" => Style::default().fg(Color::Blue), + "archived" => Style::default().fg(Color::DarkGray), + _ => Style::default(), + } +}