diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..4f82b03 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,204 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +`fb-cli` is a command-line client for Firebolt database written in Rust. It supports both single-query execution and an interactive REPL mode with query history and dynamic parameter configuration. + +**Note**: This project has been moved to https://github.com/firebolt-db/fb-cli + +## Build and Development Commands + +### Building +```bash +cargo build --release +``` + +### Testing +```bash +# Run all tests +cargo test + +# Run specific test +cargo test test_name + +# Run tests with output +cargo test -- --nocapture + +# Integration tests only +cargo test --test cli +``` + +### Installation +```bash +cargo install --path . --locked +``` + +### Formatting +```bash +cargo fmt +``` + +### Linting +```bash +cargo clippy +``` + +## Architecture + +### Module Structure + +- **main.rs**: Entry point, handles REPL mode with rustyline for line editing and history +- **args.rs**: Command-line argument parsing with gumdrop, URL generation, and default config management +- **context.rs**: Application state container holding Args and ServiceAccountToken +- **query.rs**: HTTP query execution, set/unset parameter handling, and SQL query splitting using Pest parser +- **auth.rs**: OAuth2 service account authentication with token caching +- **utils.rs**: File paths (~/.firebolt/), spinner animation, and time formatting utilities +- **sql.pest**: Pest grammar for parsing SQL queries (handles strings, comments, semicolons) + +### Key Design Patterns + +**Context Management**: The `Context` struct is passed mutably through the application, allowing runtime updates to Args and URL as users `set` and `unset` parameters in REPL mode. + +**Query Splitting**: SQL queries are parsed using a Pest grammar (sql.pest) that correctly handles: +- String literals (single quotes with escape sequences) +- E-strings (PostgreSQL-style escaped strings) +- Raw strings ($$...$$) +- Quoted identifiers (double quotes) +- Line comments (--) and nested block comments (/* /* */ */) +- Semicolons as query terminators (but not within strings/comments) + +**Authentication Flow**: +1. Service Account tokens are cached in `~/.firebolt/fb_sa_token` +2. Tokens are valid for 30 minutes and automatically reused if valid +3. JWT can be loaded from `~/.firebolt/jwt` with `--jwt-from-file` +4. Bearer tokens can be passed directly with `--bearer` + +**Configuration Persistence**: Defaults are stored in `~/.firebolt/fb_config` as YAML. When `--update-defaults` is used, current arguments (excluding update_defaults itself) are saved and merged with future invocations. + +**Dynamic Parameter Updates**: The REPL supports `set key=value` and `unset key` commands to modify query parameters at runtime without restarting the CLI. Server can also update parameters via `firebolt-update-parameters` and `firebolt-remove-parameters` headers. + +### HTTP Protocol + +- Uses reqwest with HTTP/2 keep-alive (3600s timeout, 60s intervals) +- Sends queries as POST body to constructed URL with query parameters +- Headers: `user-agent`, `Firebolt-Protocol-Version: 2.3`, `authorization` (Bearer token) +- Handles special response headers: `firebolt-update-parameters`, `firebolt-remove-parameters`, `firebolt-update-endpoint` + +### Data Type Handling in JSONLines_Compact Format + +**Firebolt Serialization Patterns:** + +The `JSONLines_Compact` format uses type-specific JSON serialization to preserve precision and avoid limitations of JSON numbers: + +| Firebolt Type | JSON Representation | Example | +|---------------|---------------------|---------| +| INT | JSON number | `42` | +| BIGINT | JSON **string** | `"9223372036854775807"` | +| NUMERIC/DECIMAL | JSON **string** | `"1.23"` | +| DOUBLE/REAL | JSON number | `3.14` | +| TEXT | JSON string | `"text"` | +| DATE | JSON string (ISO) | `"2026-02-06"` | +| TIMESTAMP | JSON string | `"2026-02-06 15:35:34+00"` | +| BYTEA | JSON string (hex) | `"\\x48656c6c6f"` | +| GEOGRAPHY | JSON string (WKB hex) | `"0101000020E6..."` | +| ARRAY | JSON array | `[1,2,3]` | +| BOOLEAN | JSON boolean | `true` | +| NULL | JSON `null` | `null` | + +**Why certain types are JSON strings:** +- **BIGINT/NUMERIC**: JavaScript/JSON numbers are 64-bit floats with ~53 bits precision. BIGINT (64-bit int) and NUMERIC (38 digits) need strings to avoid precision loss. +- **DATE/TIMESTAMP**: ISO format strings are portable and timezone-aware +- **BYTEA**: Binary data encoded as hex strings (e.g., `\x48656c6c6f` = "Hello") +- **GEOGRAPHY**: Spatial data in WKB (Well-Known Binary) format encoded as hex strings + +**Client-Side Rendering:** + +The `format_value()` function renders these types for display: +- Numbers displayed via `.to_string()` (e.g., `42`, `3.14`) +- Strings displayed as-is without quotes (BIGINT `"9223372036854775807"` → displays as `9223372036854775807`) +- NULL rendered as SQL-style `"NULL"` string in tables, empty string in CSV +- Arrays/Objects JSON-serialized with truncation at 1000 characters + +The `column_type` field from the server is currently unused but available for future type-aware formatting. + +**Server-Side Formats:** + +Formats like `PSQL`, `TabSeparatedWithNames`, etc. bypass client parsing entirely and are printed raw to stdout. + +### URL Construction + +URLs are built from: +- Protocol: `http` for localhost, `https` otherwise +- Host: from `--host` or defaults +- Database: from `--database` or `--extra database=...` +- Format: from `--format` or `--extra format=...` +- Extra parameters: from `--extra` (URL-encoded once during normalize_extras) +- Query label: from `--label` +- Advanced mode: automatically added for non-localhost + +### Output Format Options with Client Prefix + +fb-cli uses a single `--format` option with a prefix notation to distinguish between client-side and server-side rendering: + +**Client-Side Rendering** (prefix with `client:`): +- Values: `client:auto`, `client:vertical`, `client:horizontal` +- Behavior: Client requests `JSONLines_Compact` and renders it with formatting +- Supports: Pretty tables, NULL coloring, csvlens viewer integration +- Default in interactive mode: `client:auto` + +**Client display modes:** +- `client:auto` - Smart switching: horizontal for narrow tables, vertical for wide tables +- `client:horizontal` - Force horizontal table with column headers +- `client:vertical` - Force vertical two-column layout (column | value) + +**Server-Side Rendering** (no prefix): +- Values: `PSQL`, `TabSeparatedWithNames`, `JSON`, `CSV`, `JSONLines_Compact`, etc. +- Behavior: Server renders output in this format, client prints raw output +- Default in non-interactive mode: `PSQL` + +**Detection:** +- If `--format` starts with `client:`: Use client-side rendering +- Otherwise: Use server-side rendering + +**Default Behavior:** +- Interactive sessions (TTY): `--format client:auto` (client-side pretty tables) +- Non-interactive (piped/redirected): `--format PSQL` (server-side, backwards compatible) +- Can be overridden with explicit `--format` flag + +**Changing format at runtime:** +- Command-line: `--format client:auto` or `--format JSON` +- SQL-style: `set format = client:vertical;` or `set format = CSV;` +- Reset: `unset format;` (resets to default based on interactive mode) + +**Config persistence:** +- Saved in `~/.firebolt/fb_config` +- Format can be persisted with `--update-defaults` +- Clear which mode: `client:` prefix means client-side, no prefix means server-side + +**csvlens Integration:** +- Only works with client-side formats (`client:*`) +- Automatically checks and displays error if server-side format used + +### Firebolt Core vs Standard + +- **Core mode** (`--core`): Connects to Firebolt Core at `localhost:3473`, database `firebolt`, format `PSQL`, no JWT +- **Standard mode** (default): Connects to `localhost:8123` (or 9123 with JWT), database `local_dev_db`, format `PSQL` + +## Testing Considerations + +- Unit tests are in-module (`#[cfg(test)] mod tests`) +- Integration tests in `tests/cli.rs` use `CARGO_BIN_EXE_fb` to test the compiled binary +- SQL parsing tests extensively cover edge cases: nested comments, strings with semicolons, escape sequences +- Auth tests are minimal due to external service dependencies + +## Important Behavioral Details + +- **URL encoding**: Parameters are encoded once during `normalize_extras(encode: true)`. Subsequent calls with `encode: false` prevent double-encoding. +- **REPL multi-line**: Press Ctrl+O to insert newline. Queries must end with semicolon. +- **Ctrl+C in REPL**: Cancels current input but doesn't exit +- **Ctrl+D in REPL**: Exits (EOF) +- **Ctrl+V in REPL**: Inserts `\view` command (press Enter to execute and open csvlens viewer for last result) +- **Spinner**: Shown during query execution unless `--no-spinner` or `--concise` +- **History**: Saved to `~/.firebolt/fb_history` (max 10,000 entries), supports Ctrl+R search diff --git a/Cargo.lock b/Cargo.lock index 3432a9e..d0eba49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,1740 +3,3675 @@ version = 4 [[package]] -name = "addr2line" -version = "0.21.0" +name = "ahash" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "gimli", + "cfg-if", + "const-random", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.1.0" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "backtrace" -version = "0.3.69" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "addr2line", - "cc", - "cfg-if", "libc", - "miniz_oxide", - "object", - "rustc-demangle", ] [[package]] -name = "base64" -version = "0.21.5" +name = "ansi-to-tui" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "67555e1f1ece39d737e28c8a017721287753af3f93225e4a445b29ccb0f5912c" +dependencies = [ + "nom 7.1.3", + "ratatui", + "simdutf8", + "smallvec", + "thiserror 1.0.69", +] [[package]] -name = "bitflags" -version = "1.3.2" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] [[package]] -name = "bitflags" -version = "2.4.1" +name = "anstyle" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] -name = "block-buffer" -version = "0.10.4" +name = "anstyle-parse" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ - "generic-array", + "utf8parse", ] [[package]] -name = "bumpalo" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" - -[[package]] -name = "bytes" -version = "1.5.0" +name = "anstyle-query" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "cc" -version = "1.0.83" +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ - "libc", + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "anyhow" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] -name = "clipboard-win" -version = "4.5.0" +name = "arboard" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ - "error-code", - "str-buf", - "winapi", + "clipboard-win 5.4.1", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "wl-clipboard-rs", + "x11rb", ] [[package]] -name = "core-foundation" -version = "0.9.3" +name = "arrow" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "6e833808ff2d94ed40d9379848a950d995043c7fb3e81a30b383f4c6033821cc" dependencies = [ - "core-foundation-sys", - "libc", + "arrow-arith", + "arrow-array", + "arrow-buffer", + "arrow-cast", + "arrow-csv", + "arrow-data", + "arrow-ord", + "arrow-row", + "arrow-schema", + "arrow-select", + "arrow-string", ] [[package]] -name = "core-foundation-sys" -version = "0.8.4" +name = "arrow-arith" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "ad08897b81588f60ba983e3ca39bda2b179bdd84dced378e7df81a5313802ef8" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "num", +] [[package]] -name = "cpufeatures" -version = "0.2.17" +name = "arrow-array" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "8548ca7c070d8db9ce7aa43f37393e4bfcf3f2d3681df278490772fd1673d08d" dependencies = [ - "libc", + "ahash", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "chrono", + "half", + "hashbrown 0.16.1", + "num", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "arrow-buffer" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "e003216336f70446457e280807a73899dd822feaf02087d31febca1363e2fccc" dependencies = [ - "generic-array", - "typenum", + "bytes", + "half", + "num", ] [[package]] -name = "digest" -version = "0.10.7" +name = "arrow-cast" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "919418a0681298d3a77d1a315f625916cb5678ad0d74b9c60108eb15fd083023" dependencies = [ - "block-buffer", - "crypto-common", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "atoi", + "base64", + "chrono", + "half", + "lexical-core", + "num", + "ryu", ] [[package]] -name = "dirs" -version = "5.0.1" +name = "arrow-csv" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "bfa9bf02705b5cf762b6f764c65f04ae9082c7cfc4e96e0c33548ee3f67012eb" dependencies = [ - "dirs-sys", + "arrow-array", + "arrow-cast", + "arrow-schema", + "chrono", + "csv", + "csv-core", + "regex", ] [[package]] -name = "dirs-sys" -version = "0.4.1" +name = "arrow-data" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "a5c64fff1d142f833d78897a772f2e5b55b36cb3e6320376f0961ab0db7bd6d0" dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", + "arrow-buffer", + "arrow-schema", + "half", + "num", ] [[package]] -name = "encoding_rs" -version = "0.8.33" +name = "arrow-ord" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "3c8f82583eb4f8d84d4ee55fd1cb306720cddead7596edce95b50ee418edf66f" dependencies = [ - "cfg-if", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", ] [[package]] -name = "endian-type" -version = "0.1.2" +name = "arrow-row" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "9d07ba24522229d9085031df6b94605e0f4b26e099fb7cdeec37abd941a73753" +dependencies = [ + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "half", +] [[package]] -name = "equivalent" -version = "1.0.1" +name = "arrow-schema" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "b3aa9e59c611ebc291c28582077ef25c97f1975383f1479b12f3b9ffee2ffabe" [[package]] -name = "errno" -version = "0.3.5" +name = "arrow-select" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "8c41dbbd1e97bfcaee4fcb30e29105fb2c75e4d82ae4de70b792a5d3f66b2e7a" dependencies = [ - "libc", - "windows-sys", + "ahash", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "num", ] [[package]] -name = "error-code" -version = "2.3.1" +name = "arrow-string" +version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +checksum = "53f5183c150fbc619eede22b861ea7c0eebed8eaac0333eaa7f6da5205fd504d" dependencies = [ - "libc", - "str-buf", + "arrow-array", + "arrow-buffer", + "arrow-data", + "arrow-schema", + "arrow-select", + "memchr", + "num", + "regex", + "regex-syntax", ] [[package]] -name = "fastrand" -version = "2.0.1" +name = "atoi" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "fb" -version = "0.2.3" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ - "dirs", - "gumdrop", - "once_cell", - "openssl", - "pest", - "pest_derive", - "regex", - "reqwest", - "rustyline", - "serde", - "serde_json", - "serde_yaml", - "tokio", - "tokio-util", - "toml", - "urlencoding", + "num-traits", ] [[package]] -name = "fd-lock" -version = "3.0.13" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" -dependencies = [ - "cfg-if", - "rustix", - "windows-sys", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "fnv" -version = "1.0.7" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "form_urlencoded" -version = "1.2.0" +name = "bitflags" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" -dependencies = [ - "percent-encoding", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] -name = "futures-channel" -version = "0.3.29" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "futures-core", + "generic-array", ] [[package]] -name = "futures-core" -version = "0.3.29" +name = "bumpalo" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] -name = "futures-sink" -version = "0.3.29" +name = "bytecount" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] -name = "futures-task" -version = "0.3.29" +name = "bytes" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] -name = "futures-util" -version = "0.3.29" +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", + "rustversion", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "cc" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ - "typenum", - "version_check", + "find-msvc-tools", + "shlex", ] [[package]] -name = "getrandom" -version = "0.2.10" +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ - "cfg-if", - "libc", - "wasi", + "iana-time-zone", + "num-traits", + "windows-link", ] [[package]] -name = "gimli" -version = "0.28.0" +name = "clap" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +dependencies = [ + "clap_builder", + "clap_derive", +] [[package]] -name = "gumdrop" -version = "0.8.1" +name = "clap_builder" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ - "gumdrop_derive", + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size 0.4.3", ] [[package]] -name = "gumdrop_derive" -version = "0.8.1" +name = "clap_derive" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ + "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.114", ] [[package]] -name = "h2" -version = "0.4.10" +name = "clap_lex" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "error-code 2.3.1", + "str-buf", + "winapi", ] [[package]] -name = "hashbrown" -version = "0.14.2" +name = "clipboard-win" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code 3.3.2", +] [[package]] -name = "hermit-abi" -version = "0.3.3" +name = "colorchoice" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] -name = "home" -version = "0.5.5" +name = "comfy-table" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" dependencies = [ - "windows-sys", + "crossterm 0.26.1", + "strum 0.24.1", + "strum_macros 0.24.3", + "unicode-width 0.1.14", ] [[package]] -name = "http" -version = "1.1.0" +name = "compact_str" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ - "bytes", - "fnv", + "castaway", + "cfg-if", "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "http-body" -version = "1.0.0" +name = "const-random" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ - "bytes", - "http", + "const-random-macro", ] [[package]] -name = "http-body-util" -version = "0.1.1" +name = "const-random-macro" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", + "getrandom 0.2.17", + "once_cell", + "tiny-keccak", ] [[package]] -name = "httparse" -version = "1.8.0" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "filedescriptor", + "mio 1.1.1", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "csv" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde_core", +] + +[[package]] +name = "csv-core" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" +dependencies = [ + "memchr", +] + +[[package]] +name = "csvlens" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721f1b47a845e16645f6ac0fac2227e04a7896a484116f9d831305ceb4d07a7c" +dependencies = [ + "ansi-to-tui", + "anyhow", + "arboard", + "arrow", + "clap", + "crossterm 0.28.1", + "csv", + "qsv-sniffer", + "ratatui", + "regex", + "sorted-vec", + "tempfile", + "terminal-colorsaurus", + "thiserror 2.0.18", + "tui-input", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fb" +version = "0.2.3" +dependencies = [ + "comfy-table", + "csvlens", + "dirs", + "gumdrop", + "home 0.4.2", + "once_cell", + "openssl", + "pest", + "pest_derive", + "regex", + "reqwest", + "rustyline", + "serde", + "serde_json", + "serde_yaml", + "terminal_size 0.3.0", + "tokio", + "tokio-util", + "toml", + "urlencoding", +] + +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.3", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "home" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "013e4e6e9134211bb4d6bf53dd8cfb75d9e2715cc33614b9c0827718c6fbe0b8" +dependencies = [ + "scopeguard", + "winapi", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] name = "hyper" -version = "1.2.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "instability" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357b7205c6cd18dd2c86ed312d1e70add149aea98e7ef72b9fdf0270e555c11d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lexical-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8d125a277f807e55a77304455eb7b1cb52f2b18c143b60e766c120bd64a594" +dependencies = [ + "lexical-parse-float", + "lexical-parse-integer", + "lexical-util", + "lexical-write-float", + "lexical-write-integer", +] + +[[package]] +name = "lexical-parse-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a9f232fbd6f550bc0137dcb5f99ab674071ac2d690ac69704593cb4abbea56" +dependencies = [ + "lexical-parse-integer", + "lexical-util", +] + +[[package]] +name = "lexical-parse-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7a039f8fb9c19c996cd7b2fcce303c1b2874fe1aca544edc85c4a5f8489b34" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "lexical-util" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2604dd126bb14f13fb5d1bd6a66155079cb9fa655b37f875b3a742c705dbed17" + +[[package]] +name = "lexical-write-float" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c438c87c013188d415fbabbb1dceb44249ab81664efbd31b14ae55dabb6361" +dependencies = [ + "lexical-util", + "lexical-write-integer", +] + +[[package]] +name = "lexical-write-integer" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "409851a618475d2d5796377cad353802345cba92c867d9fbcde9cf4eac4e14df" +dependencies = [ + "lexical-util", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-graphics", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-src" +version = "300.5.5+3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1787d533e03597a7934fd0a765f0d28e94ecc5fb7789f8053b1e699a56f709" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qsv-dateparser" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6030a42cfbad8f656c7c16b027e0957d85dc0b43365a88d830834de582d7a603" +dependencies = [ + "anyhow", + "chrono", + "fast-float2", + "regex", +] + +[[package]] +name = "qsv-sniffer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b25b79fc637d5ec0a9d72612207f48676b39c8b5c48ab32cfa0d47915fd664a" +dependencies = [ + "bitflags 2.10.0", + "bytecount", + "csv", + "csv-core", + "hashbrown 0.15.5", + "memchr", + "qsv-dateparser", + "regex", + "simdutf8", + "tabwriter", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.10.0", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum 0.26.3", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "regex" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "hyper-util" -version = "0.1.3" +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ + "base64", "bytes", - "futures-channel", - "futures-util", + "encoding_rs", + "futures-core", + "h2", "http", "http-body", + "http-body-util", "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", "pin-project-lite", - "socket2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", "tokio", + "tokio-native-tls", "tower", + "tower-http", "tower-service", - "tracing", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "idna" -version = "0.4.0" +name = "ring" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] -name = "indexmap" -version = "2.1.0" +name = "rustix" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "equivalent", - "hashbrown", + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] -name = "ipnet" -version = "2.9.0" +name = "rustix" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] [[package]] -name = "itoa" -version = "1.0.9" +name = "rustls" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] [[package]] -name = "js-sys" -version = "0.3.65" +name = "rustls-pki-types" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ - "wasm-bindgen", + "zeroize", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "libc" -version = "0.2.171" +name = "rustyline" +version = "12.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "clipboard-win 4.5.0", + "fd-lock", + "home 0.5.12", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "regex", + "scopeguard", + "unicode-segmentation", + "unicode-width 0.1.14", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] -name = "linux-raw-sys" -version = "0.4.10" +name = "schannel" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "lock_api" -version = "0.4.11" +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio 0.8.11", + "mio 1.1.1", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ - "autocfg", - "scopeguard", + "libc", + "windows-sys 0.60.2", ] [[package]] -name = "log" -version = "0.4.20" +name = "sorted-vec" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "19f58d7b0190c7f12df7e8be6b79767a0836059159811b869d5ab55721fe14d0" [[package]] -name = "memchr" -version = "2.6.4" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "mime" -version = "0.3.17" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "str-buf" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" [[package]] -name = "mio" -version = "0.8.11" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "wasi", - "windows-sys", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "native-tls" -version = "0.2.11" +name = "strum" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "strum_macros 0.26.4", ] [[package]] -name = "nibble_vec" -version = "0.1.0" +name = "strum_macros" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ - "smallvec", + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", ] [[package]] -name = "nix" +name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.114", ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "object" -version = "0.32.1" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "memchr", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "once_cell" -version = "1.18.0" +name = "syn" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] [[package]] -name = "openssl" -version = "0.10.72" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "futures-core", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.2.3+3.2.1" +name = "system-configuration" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "cc", + "bitflags 2.10.0", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "openssl-sys" -version = "0.9.109" +name = "system-configuration-sys" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ - "cc", + "core-foundation-sys", "libc", - "openssl-src", - "pkg-config", - "vcpkg", ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "tabwriter" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "fce91f2f0ec87dff7e6bcbbeb267439aa1188703003c6055193c821487400432" +dependencies = [ + "unicode-width 0.2.0", +] [[package]] -name = "parking_lot" -version = "0.12.1" +name = "tempfile" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ - "lock_api", - "parking_lot_core", + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", ] [[package]] -name = "parking_lot_core" -version = "0.9.9" +name = "terminal-colorsaurus" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "7a46bb5364467da040298c573c8a95dbf9a512efc039630409a03126e3703e90" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets", + "memchr", + "mio 1.1.1", + "terminal-trx", + "windows-sys 0.61.2", + "xterm-color", ] [[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pest" -version = "2.8.0" +name = "terminal-trx" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "3b3f27d9a8a177e57545481faec87acb45c6e854ed1e5a3658ad186c106f38ed" dependencies = [ - "memchr", - "thiserror 2.0.12", - "ucd-trie", + "cfg-if", + "libc", + "windows-sys 0.61.2", ] [[package]] -name = "pest_derive" -version = "2.8.0" +name = "terminal_size" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "pest", - "pest_generator", + "rustix 0.38.44", + "windows-sys 0.48.0", ] [[package]] -name = "pest_generator" -version = "2.8.0" +name = "terminal_size" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.100", + "rustix 1.1.3", + "windows-sys 0.60.2", ] [[package]] -name = "pest_meta" -version = "2.8.0" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "once_cell", - "pest", - "sha2", + "thiserror-impl 1.0.69", ] [[package]] -name = "pin-project" -version = "1.1.5" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "pin-project-internal", + "thiserror-impl 2.0.18", ] [[package]] -name = "pin-project-internal" -version = "1.1.5" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] -name = "pin-utils" -version = "0.1.0" +name = "tiny-keccak" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] [[package]] -name = "pkg-config" -version = "0.3.27" +name = "tinystr" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] -name = "proc-macro2" -version = "1.0.94" +name = "tokio" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ - "unicode-ident", + "bytes", + "libc", + "mio 1.1.1", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", ] [[package]] -name = "quote" -version = "1.0.40" +name = "tokio-macros" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "radix_trie" -version = "0.2.1" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "endian-type", - "nibble_vec", + "native-tls", + "tokio", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "bitflags 1.3.2", + "rustls", + "tokio", ] [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "tokio-util" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "redox_users" -version = "0.4.3" +name = "toml" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror 1.0.50", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] [[package]] -name = "regex" -version = "1.10.2" +name = "toml_datetime" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "serde", ] [[package]] -name = "regex-automata" -version = "0.4.3" +name = "toml_edit" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", ] [[package]] -name = "regex-syntax" -version = "0.8.2" +name = "toml_write" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] -name = "reqwest" -version = "0.12.0" +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b48d98d932f4ee75e541614d32a7f44c889b72bd9c2e04d95edd135989df88" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ - "base64", - "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-native-tls", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", ] [[package]] -name = "rustc-demangle" -version = "0.1.23" +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "rustix" -version = "0.38.21" +name = "tracing" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "tracing-core" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ - "base64", + "once_cell", ] [[package]] -name = "rustyline" -version = "12.0.0" +name = "tree_magic_mini" +version = "3.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" +checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "clipboard-win", - "fd-lock", - "home", - "libc", - "log", "memchr", - "nix", - "radix_trie", - "regex", - "scopeguard", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "winapi", + "nom 8.0.0", + "petgraph", ] [[package]] -name = "ryu" -version = "1.0.15" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "schannel" -version = "0.1.22" +name = "tui-input" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "74f679521b7fd35e17fbca58ec5aac64c5d331e54a9034510ec26b193ffd7597" dependencies = [ - "windows-sys", + "crossterm 0.28.1", + "ratatui", + "unicode-width 0.2.0", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "typenum" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] -name = "security-framework" -version = "2.9.2" +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "security-framework-sys" -version = "2.9.1" +name = "unicode-ident" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] -name = "serde" -version = "1.0.190" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" -dependencies = [ - "serde_derive", -] +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] -name = "serde_derive" -version = "1.0.190" +name = "unicode-truncate" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", ] [[package]] -name = "serde_json" -version = "1.0.108" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" -dependencies = [ - "itoa", - "ryu", - "serde", -] +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "serde_spanned" -version = "0.6.4" +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", "serde", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "urlencoding" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "try-lock", ] [[package]] -name = "serde_yaml" -version = "0.9.27" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", + "wit-bindgen", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "wasm-bindgen" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "signal-hook-registry" -version = "1.4.1" +name = "wasm-bindgen-futures" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ - "libc", + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "slab" -version = "0.4.9" +name = "wasm-bindgen-macro" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ - "autocfg", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "smallvec" -version = "1.13.2" +name = "wasm-bindgen-macro-support" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] [[package]] -name = "socket2" -version = "0.5.5" +name = "wasm-bindgen-shared" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ - "libc", - "windows-sys", + "unicode-ident", ] [[package]] -name = "str-buf" -version = "1.0.6" +name = "wayland-backend" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.3", + "smallvec", + "wayland-sys", +] [[package]] -name = "syn" -version = "1.0.109" +name = "wayland-client" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "bitflags 2.10.0", + "rustix 1.1.3", + "wayland-backend", + "wayland-scanner", ] [[package]] -name = "syn" -version = "2.0.100" +name = "wayland-protocols" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", ] [[package]] -name = "sync_wrapper" -version = "0.1.2" +name = "wayland-protocols-wlr" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] [[package]] -name = "system-configuration" -version = "0.5.1" +name = "wayland-scanner" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", + "proc-macro2", + "quick-xml", + "quote", ] [[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "wayland-sys" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ - "core-foundation-sys", - "libc", + "pkg-config", ] [[package]] -name = "tempfile" -version = "3.8.1" +name = "web-sys" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.4.1", - "rustix", - "windows-sys", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "thiserror" -version = "1.0.50" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "thiserror-impl 1.0.50", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "thiserror" -version = "2.0.12" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "thiserror-impl 2.0.12", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "thiserror-impl" -version = "1.0.50" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] -name = "thiserror-impl" -version = "2.0.12" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.114", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "tinyvec_macros" -version = "0.1.1" +name = "windows-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] [[package]] -name = "tokio" -version = "1.38.2" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68722da18b0fc4a05fdc1120b302b82051265792a1e1b399086e9b204b10ad3d" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] -name = "tokio-macros" -version = "2.3.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "windows-targets 0.48.5", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "native-tls", - "tokio", + "windows-targets 0.52.6", ] [[package]] -name = "tokio-util" -version = "0.7.10" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", + "windows-targets 0.52.6", ] [[package]] -name = "toml" -version = "0.8.8" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "windows-targets 0.53.5", ] [[package]] -name = "toml_datetime" -version = "0.6.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "serde", + "windows-link", ] [[package]] -name = "toml_edit" -version = "0.21.0" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] -name = "tower" -version = "0.4.13" +name = "windows-targets" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] -name = "tower-layer" -version = "0.3.2" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] [[package]] -name = "tower-service" -version = "0.3.2" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "tracing" -version = "0.1.40" +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "log", - "pin-project-lite", - "tracing-core", -] +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "tracing-core" -version = "0.1.32" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] -name = "try-lock" -version = "0.2.4" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "typenum" -version = "1.18.0" +name = "windows_aarch64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "ucd-trie" -version = "0.1.7" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] -name = "unicode-bidi" -version = "0.3.13" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "unicode-ident" -version = "1.0.12" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "windows_i686_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] -name = "unicode-segmentation" -version = "1.10.1" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "unicode-width" -version = "0.1.11" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "url" -version = "2.4.1" +name = "windows_i686_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "urlencoding" -version = "2.1.3" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] -name = "utf8parse" -version = "0.2.1" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "version_check" -version = "0.9.5" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] -name = "want" -version = "0.3.1" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "wasm-bindgen" -version = "0.2.88" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] -name = "wasm-bindgen-backend" -version = "0.2.88" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.100", - "wasm-bindgen-shared", -] +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "wasm-bindgen-futures" -version = "0.4.38" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "wasm-bindgen-macro" -version = "0.2.88" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.88" +name = "winnow" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "memchr", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.88" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] -name = "web-sys" -version = "0.3.65" +name = "wl-clipboard-rs" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3" dependencies = [ - "js-sys", - "wasm-bindgen", + "libc", + "log", + "os_pipe", + "rustix 1.1.3", + "thiserror 2.0.18", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "writeable" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "gethostname", + "rustix 1.1.3", + "x11rb-protocol", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "x11rb-protocol" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "xterm-color" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "7008a9d8ba97a7e47d9b2df63fcdb8dade303010c5a7cd5bf2469d4da6eba673" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "yoke" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "windows-targets", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "yoke-derive" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "zerocopy" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "zerocopy-derive" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "zerofrom" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "zerofrom-derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", + "synstructure", +] [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "zeroize" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "zerotrie" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "zerovec" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] [[package]] -name = "winnow" -version = "0.5.19" +name = "zerovec-derive" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ - "memchr", + "proc-macro2", + "quote", + "syn 2.0.114", ] [[package]] -name = "winreg" -version = "0.50.0" +name = "zmij" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys", -] +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/Cargo.toml b/Cargo.toml index ac2a217..3283219 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,7 @@ toml = "0.8" urlencoding = "2.1" pest = "2.7" pest_derive = "2.7" +terminal_size = "0.3" +comfy-table = "6.2" +home = "< 0.5.12" +csvlens = "0.14" diff --git a/README.md b/README.md index 6c40fe5..74c334f 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,52 @@ Time: 87.747ms Also support history + search in it (`CTRL+R`). +## Output Formats + +### Client-Side Rendering + +Use `--format client:auto` (default in interactive mode) for pretty table output with smart formatting: + +``` +=> select * from information_schema.engine_query_history limit 3; ++--------------------+-------------+--------+ +| query_id | query_label | status | ++===============================================+ +| abc123... | NULL | ENDED | +| def456... | my_query | ENDED | +| ghi789... | NULL | ENDED | ++--------------------+-------------+--------+ +Time: 15.2ms +Scanned: 3 rows, 1.5 KB (1.2 KB local, 300 B remote) +Request Id: xyz... +``` + +Available client modes: +- `client:auto` - Smart switching between horizontal/vertical layout +- `client:vertical` - Two-column vertical layout for wide tables +- `client:horizontal` - Standard horizontal table + +### Interactive Result Exploration + +Press `Ctrl+V` then `Enter` (or type `\view`) to open the last query result in an interactive viewer powered by [csvlens](https://github.com/YS-L/csvlens). **Note:** Requires client-side output formats (`client:auto`, `client:vertical`, or `client:horizontal`). + +``` +=> select * from information_schema.engine_query_history; +[... table output ...] + +=> \view +[Opens interactive csvlens viewer with sorting, filtering, and navigation] +``` + +### Server-Side Rendering + +Use format names without prefix for server-rendered output (default in non-interactive/piped mode): +- `PSQL` - PostgreSQL-style format +- `JSON` - JSON output +- `CSV` - CSV format +- `TabSeparatedWithNames` - TSV with headers +- And more... + ## Help ``` @@ -49,7 +95,7 @@ Optional arguments: -C, --core Preset of settings to connect to Firebolt Core -h, --host HOSTNAME Hostname (and port) to connect to -d, --database DATABASE Database name to use - -f, --format FORMAT Output format (e.g., TabSeparatedWithNames, PSQL, JSONLines_Compact, Vertical, ...) + -f, --format FORMAT Output format (client:auto, client:vertical, client:horizontal, TabSeparatedWithNames, PSQL, JSONLines_Compact, ...) -e, --extra EXTRA Extra settings in the form --extra = -l, --label LABEL Query label for tracking or identification -j, --jwt JWT JWT for authentication @@ -96,7 +142,8 @@ Most of them from https://github.com/kkawakam/rustyline: Some of them specific to `fb`: | Keystroke | Action | | --------------------- | --------------------------------------------------------------------------- | -| Ctrl-C | Cancel current input. | +| Ctrl-V then Enter | Open last query result in interactive csvlens viewer | +| Ctrl-C | Cancel current input | | Ctrl-O | Insert a newline | diff --git a/src/args.rs b/src/args.rs index 4e4d4c7..2d2affb 100644 --- a/src/args.rs +++ b/src/args.rs @@ -2,6 +2,7 @@ use gumdrop::Options; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fs; +use std::io::IsTerminal; use crate::utils::{config_path, init_root_path}; @@ -20,6 +21,15 @@ impl Or for String { } } +// Default value functions for serde +fn default_min_col_width() -> usize { + 15 +} + +fn default_max_cell_length() -> usize { + 1000 +} + #[derive(Clone, Debug, Options, Deserialize, Serialize)] pub struct Args { #[options(help = "Run a single command and exit")] @@ -38,7 +48,7 @@ pub struct Args { #[serde(skip_serializing, skip_deserializing)] pub database: String, - #[options(help = "Output format (e.g., TabSeparatedWithNames, PSQL, JSONLines_Compact, Vertical, ...)")] + #[options(help = "Output format (client:auto, client:vertical, client:horizontal, PSQL, JSON, CSV, ...)")] #[serde(default)] pub format: String, @@ -91,6 +101,14 @@ pub struct Args { #[serde(default)] pub no_spinner: bool, + #[options(no_short, help = "Minimum characters per column before switching to vertical mode", default = "15")] + #[serde(default = "default_min_col_width")] + pub min_col_width: usize, + + #[options(no_short, help = "Maximum cell content length before truncation", default = "1000")] + #[serde(default = "default_max_cell_length")] + pub max_cell_length: usize, + #[options(no_short, help = "Update default configuration values")] #[serde(skip_serializing, skip_deserializing)] pub update_defaults: bool, @@ -108,6 +126,35 @@ pub struct Args { pub query: Vec, } +impl Args { + pub fn should_render_table(&self) -> bool { + // Client rendering when format starts with "client:" + self.format.starts_with("client:") + } + + /// Extract display mode from client: prefix + /// "client:auto" → "auto", "client:vertical" → "vertical", "PSQL" → "" + pub fn get_display_mode(&self) -> &str { + if self.format.starts_with("client:") { + &self.format[7..] // Skip "client:" prefix + } else { + "" + } + } + + pub fn is_vertical_display(&self) -> bool { + self.get_display_mode().eq_ignore_ascii_case("vertical") + } + + pub fn is_horizontal_display(&self) -> bool { + self.get_display_mode().eq_ignore_ascii_case("horizontal") + } + + pub fn is_auto_display(&self) -> bool { + self.get_display_mode().eq_ignore_ascii_case("auto") + } +} + pub fn normalize_extras(extras: Vec, encode: bool) -> Result, Box> { let mut x: BTreeMap = BTreeMap::new(); @@ -187,12 +234,41 @@ pub fn get_args() -> Result> { args.concise = args.concise || defaults.concise; args.hide_pii = args.hide_pii || defaults.hide_pii; + // Use defaults for numeric settings if not specified + if args.min_col_width == default_min_col_width() { + args.min_col_width = defaults.min_col_width; + } + if args.max_cell_length == default_max_cell_length() { + args.max_cell_length = defaults.max_cell_length; + } + + args.database = args + .database + .or(args.core.then(|| String::from("firebolt")).unwrap_or(defaults.database)) + .or(String::from("local_dev_db")); + + // Detect if running in interactive mode + let is_interactive = std::io::stdout().is_terminal() && std::io::stdin().is_terminal(); + if args.core { args.host = args.host.or(String::from("localhost:3473")); args.jwt = String::from(""); args.format = args.format.or(String::from("PSQL")); } else { - args.format = args.format.or(defaults.format).or(String::from("PSQL")); + // Apply smart defaults based on mode if format is not already set + let default_format = if args.format.is_empty() && defaults.format.is_empty() { + if is_interactive { + // Interactive mode: default to client-side rendering with auto display + String::from("client:auto") + } else { + // Non-interactive mode: default to server-side rendering with PSQL + String::from("PSQL") + } + } else { + String::new() + }; + + args.format = args.format.or(defaults.format).or(default_format); args.host = args.host.or(defaults.host).or(default_host); } @@ -202,6 +278,16 @@ pub fn get_args() -> Result> { args.extra = normalize_extras(extras, false)?; } + // Warn if user specified a client format name without the "client:" prefix + if args.format.eq_ignore_ascii_case("auto") + || args.format.eq_ignore_ascii_case("vertical") + || args.format.eq_ignore_ascii_case("horizontal") { + eprintln!("Warning: Format '{}' is not supported by the server.", args.format); + eprintln!("Did you mean '--format client:{}'?", args.format.to_lowercase()); + eprintln!("Client-side formats require the 'client:' prefix (e.g., client:auto, client:vertical, client:horizontal)"); + eprintln!(); + } + Ok(args) } @@ -228,7 +314,13 @@ pub fn get_url(args: &Args) -> String { let is_localhost = args.host.starts_with("localhost"); let protocol = if is_localhost { "http" } else { "https" }; let output_format = if !args.format.is_empty() && !args.extra.iter().any(|e| e.starts_with("format=")) { - format!("&output_format={}", args.format) + if args.format.starts_with("client:") { + // Client-side rendering: always use JSONLines_Compact + format!("&output_format=JSONLines_Compact") + } else { + // Server-side rendering: use format as-is + format!("&output_format={}", &args.format) + } } else { String::new() }; @@ -337,4 +429,92 @@ mod tests { assert_eq!(result[1], "param2=value%20with%20spaces"); assert_eq!(result[2], "param3=%20%20value%20with%20spaces%20"); } + + #[test] + fn test_should_render_table_with_client_prefix() { + let mut args = Args::parse_args_default_or_exit(); + + // Server-side format: should not render + args.format = String::from("PSQL"); + assert!(!args.should_render_table()); + + args.format = String::from("JSON"); + assert!(!args.should_render_table()); + + // Client-side format: should render + args.format = String::from("client:auto"); + assert!(args.should_render_table()); + + args.format = String::from("client:vertical"); + assert!(args.should_render_table()); + + args.format = String::from("client:horizontal"); + assert!(args.should_render_table()); + } + + #[test] + fn test_get_display_mode() { + let mut args = Args::parse_args_default_or_exit(); + + // Client formats + args.format = String::from("client:auto"); + assert_eq!(args.get_display_mode(), "auto"); + + args.format = String::from("client:vertical"); + assert_eq!(args.get_display_mode(), "vertical"); + + args.format = String::from("client:horizontal"); + assert_eq!(args.get_display_mode(), "horizontal"); + + // Server formats + args.format = String::from("PSQL"); + assert_eq!(args.get_display_mode(), ""); + + args.format = String::from("JSON"); + assert_eq!(args.get_display_mode(), ""); + } + + #[test] + fn test_display_mode_helpers() { + let mut args = Args::parse_args_default_or_exit(); + + args.format = String::from("client:auto"); + assert!(args.is_auto_display()); + assert!(!args.is_vertical_display()); + assert!(!args.is_horizontal_display()); + + args.format = String::from("client:vertical"); + assert!(!args.is_auto_display()); + assert!(args.is_vertical_display()); + assert!(!args.is_horizontal_display()); + + args.format = String::from("client:horizontal"); + assert!(!args.is_auto_display()); + assert!(!args.is_vertical_display()); + assert!(args.is_horizontal_display()); + + args.format = String::from("PSQL"); + assert!(!args.is_auto_display()); + assert!(!args.is_vertical_display()); + assert!(!args.is_horizontal_display()); + } + + #[test] + fn test_format_without_client_prefix() { + // Test that formats "auto", "vertical", "horizontal" without "client:" prefix + // are recognized (they will trigger a warning at runtime, but are valid format strings) + let mut args = Args::parse_args_default_or_exit(); + + args.format = String::from("auto"); + assert!(!args.should_render_table()); // Should NOT render because no "client:" prefix + assert_eq!(args.get_display_mode(), ""); // Empty because no prefix + + args.format = String::from("vertical"); + assert!(!args.should_render_table()); + assert_eq!(args.get_display_mode(), ""); + + args.format = String::from("horizontal"); + assert!(!args.should_render_table()); + assert_eq!(args.get_display_mode(), ""); + } } diff --git a/src/context.rs b/src/context.rs index b9b868c..0f7239a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,5 @@ use crate::args::{get_url, Args}; +use crate::table_renderer::ParsedResult; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -16,12 +17,23 @@ pub struct Context { pub prompt1: Option, pub prompt2: Option, pub prompt3: Option, + pub last_result: Option, + pub last_stats: Option, } impl Context { pub fn new(args: Args) -> Self { let url = get_url(&args); - Self { args, url, sa_token: None, prompt1: None, prompt2: None, prompt3: None } + Self { + args, + url, + sa_token: None, + prompt1: None, + prompt2: None, + prompt3: None, + last_result: None, + last_stats: None, + } } pub fn update_url(&mut self) { @@ -56,5 +68,6 @@ mod tests { assert!(context.url.contains("localhost:8123")); assert!(context.url.contains("database=test_db")); assert!(context.sa_token.is_none()); + assert!(context.last_result.is_none()); } } diff --git a/src/main.rs b/src/main.rs index 45df4b4..e4b5aa8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,9 @@ mod auth; mod context; mod meta_commands; mod query; +mod table_renderer; mod utils; +mod viewer; use args::get_args; use auth::maybe_authenticate; @@ -14,6 +16,7 @@ use context::Context; use meta_commands::handle_meta_command; use query::{query, try_split_queries}; use utils::history_path; +use viewer::open_csvlens_viewer; pub const CLI_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const USER_AGENT: &str = concat!("fdb-cli/", env!("CARGO_PKG_VERSION")); @@ -57,8 +60,16 @@ async fn main() -> Result<(), Box> { rl.bind_sequence(KeyEvent(KeyCode::Char('o'), Modifiers::CTRL), EventHandler::Simple(Cmd::Newline)); - if is_tty { - eprintln!("Press Ctrl+D to exit."); + // Bind Ctrl-V to trigger viewer via special marker + // Using Cmd::AcceptLine alone won't work because we need to detect it was Ctrl-V + // Instead, we'll keep the two-step approach (Ctrl-V + Enter) which is explicit and clear + rl.bind_sequence( + KeyEvent(KeyCode::Char('v'), Modifiers::CTRL), + EventHandler::Simple(Cmd::Insert(1, "\\view".to_string())) + ); + + if is_tty && !context.args.concise { + eprintln!("Type \\help for available commands or press Ctrl+V then Enter to view last result. Ctrl+D to exit."); } let mut buffer: String = String::new(); let mut has_error = false; @@ -92,6 +103,51 @@ async fn main() -> Result<(), Box> { match readline { Ok(line) => { + // Check for special commands + let trimmed = line.trim(); + + if trimmed == "\\view" { + // Open csvlens viewer for last query result + if let Err(e) = open_csvlens_viewer(&context) { + eprintln!("Failed to open viewer: {}", e); + } + continue; + } else if trimmed == "\\help" { + // Show help for special commands + eprintln!("Special commands:"); + eprintln!(" \\view - Open last query result in csvlens viewer"); + eprintln!(" (requires client format: client:auto, client:vertical, or client:horizontal)"); + eprintln!(" \\help - Show this help message"); + eprintln!(); + eprintln!("SQL-style commands:"); + eprintln!(" set format = ; - Change output format"); + eprintln!(" unset format; - Reset format to default"); + eprintln!(); + eprintln!("Format values:"); + eprintln!(" Client-side rendering (prefix with 'client:'):"); + eprintln!(" client:auto - Smart switching between horizontal/vertical (default in interactive)"); + eprintln!(" client:horizontal - Force horizontal table layout"); + eprintln!(" client:vertical - Force vertical two-column layout"); + eprintln!(); + eprintln!(" Server-side rendering (no prefix):"); + eprintln!(" PSQL - PostgreSQL-style format (default in non-interactive)"); + eprintln!(" JSON - JSON format"); + eprintln!(" CSV - CSV format"); + eprintln!(" TabSeparatedWithNames - TSV with headers"); + eprintln!(" JSONLines_Compact - JSON Lines format"); + eprintln!(); + eprintln!("Examples:"); + eprintln!(" set format = client:vertical; # Use client-side vertical display"); + eprintln!(" set format = JSON; # Use server-side JSON output"); + eprintln!(); + eprintln!("Keyboard shortcuts:"); + eprintln!(" Ctrl+V then Enter - Open last query result in csvlens viewer (inserts \\view)"); + eprintln!(" Ctrl+O - Insert newline (for multi-line queries)"); + eprintln!(" Ctrl+D - Exit REPL"); + eprintln!(" Ctrl+C - Cancel current input"); + continue; + } + buffer += line.as_str(); if buffer.trim() == "quit" || buffer.trim() == "exit" { diff --git a/src/query.rs b/src/query.rs index d13043b..6181f53 100644 --- a/src/query.rs +++ b/src/query.rs @@ -9,10 +9,51 @@ use tokio_util::sync::CancellationToken; use crate::args::normalize_extras; use crate::auth::authenticate_service_account; use crate::context::Context; +use crate::table_renderer; use crate::utils::spin; use crate::FIREBOLT_PROTOCOL_VERSION; use crate::USER_AGENT; +// Format bytes with appropriate unit (B, KB, MB, GB, TB) +fn format_bytes(bytes: u64) -> String { + const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"]; + + if bytes == 0 { + return "0 B".to_string(); + } + + let bytes_f64 = bytes as f64; + let unit_index = (bytes_f64.log2() / 10.0).floor() as usize; + let unit_index = unit_index.min(UNITS.len() - 1); + + let value = bytes_f64 / (1024_f64.powi(unit_index as i32)); + + if value >= 100.0 { + format!("{:.0} {}", value, UNITS[unit_index]) + } else if value >= 10.0 { + format!("{:.1} {}", value, UNITS[unit_index]) + } else { + format!("{:.2} {}", value, UNITS[unit_index]) + } +} + +// Format number with thousand separators +fn format_number(n: u64) -> String { + let s = n.to_string(); + let mut result = String::new(); + let mut count = 0; + + for c in s.chars().rev() { + if count > 0 && count % 3 == 0 { + result.push(','); + } + result.push(c); + count += 1; + } + + result.chars().rev().collect() +} + // Set parameters via query pub fn set_args(context: &mut Context, query: &str) -> Result> { // set flag = value; @@ -196,7 +237,93 @@ pub async fn query(context: &mut Context, query_text: String) -> Result<(), Box< let body = resp.text().await?; // on stdout, on purpose - print!("{}", body); + if context.args.should_render_table() { + match table_renderer::parse_jsonlines_compact(&body) { + Ok(parsed) => { + // Store result for interactive viewing + context.last_result = Some(parsed.clone()); + + if let Some(errors) = parsed.errors { + // Display errors + for error in errors { + eprintln!("Error: {}", error.description); + } + } else if !parsed.columns.is_empty() { + // Get terminal width for intelligent display decisions + let terminal_width = terminal_size::terminal_size() + .map(|(terminal_size::Width(w), _)| w) + .unwrap_or(80); + + let table_output = if context.args.is_horizontal_display() { + // Force horizontal table layout + table_renderer::render_table(&parsed.columns, &parsed.rows, context.args.max_cell_length) + } else if context.args.is_vertical_display() { + // Force vertical two-column layout + table_renderer::render_table_vertical(&parsed.columns, &parsed.rows, terminal_width, context.args.max_cell_length) + } else if context.args.is_auto_display() { + // Auto mode - intelligently choose display mode + if table_renderer::should_use_vertical_mode(&parsed.columns, terminal_width, context.args.min_col_width) { + if context.args.verbose { + eprintln!("Note: Using vertical display mode (table too wide for horizontal display)"); + } + table_renderer::render_table_vertical(&parsed.columns, &parsed.rows, terminal_width, context.args.max_cell_length) + } else { + table_renderer::render_table(&parsed.columns, &parsed.rows, context.args.max_cell_length) + } + } else { + // Fallback to horizontal if format starts with client: but mode not recognized + table_renderer::render_table(&parsed.columns, &parsed.rows, context.args.max_cell_length) + }; + + println!("{}", table_output); + + // Store statistics for display later (after Time) + context.last_stats = if !context.args.concise && parsed.statistics.is_some() { + parsed.statistics.as_ref().and_then(|stats| { + stats.as_object().map(|obj| { + let scanned_cache = obj.get("scanned_bytes_cache") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let scanned_storage = obj.get("scanned_bytes_storage") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let rows_read = obj.get("rows_read") + .and_then(|v| v.as_u64()) + .unwrap_or(0); + + let total_scanned = scanned_cache + scanned_storage; + + // Format: "Scanned: x rows, y B (..B local, ..B remote)" + if rows_read > 0 || total_scanned > 0 { + Some(format!( + "Scanned: {} rows, {} ({} local, {} remote)", + format_number(rows_read), + format_bytes(total_scanned), + format_bytes(scanned_cache), + format_bytes(scanned_storage) + )) + } else { + None + } + }).flatten() + }) + } else { + None + }; + } + } + Err(e) => { + // Fallback to raw output on parse error + if context.args.verbose { + eprintln!("Failed to parse table format: {}", e); + } + print!("{}", body); + } + } + } else { + // Original behavior for other formats + print!("{}", body); + } if !status.is_success() { query_failed = true; @@ -215,10 +342,14 @@ pub async fn query(context: &mut Context, query_text: String) -> Result<(), Box< if !context.args.concise { let elapsed = format!("{:?}", elapsed / 100000 * 100000); eprintln!("Time: {elapsed}"); + // Print statistics if available (from client-side rendering) + if let Some(stats) = &context.last_stats { + eprintln!("{}", stats); + } if let Some(request_id) = maybe_request_id { eprintln!("Request Id: {request_id}"); } - eprintln!("") + eprintln!() } } }; @@ -726,6 +857,36 @@ mod tests { assert!(try_split_queries(input).is_none()); } + #[test] + fn test_format_bytes() { + assert_eq!(format_bytes(0), "0 B"); + assert_eq!(format_bytes(1), "1.00 B"); + assert_eq!(format_bytes(100), "100 B"); + assert_eq!(format_bytes(1023), "1023 B"); + assert_eq!(format_bytes(1024), "1.00 KB"); + assert_eq!(format_bytes(1536), "1.50 KB"); + assert_eq!(format_bytes(10240), "10.0 KB"); + assert_eq!(format_bytes(102400), "100 KB"); + assert_eq!(format_bytes(1048576), "1.00 MB"); + assert_eq!(format_bytes(1572864), "1.50 MB"); + assert_eq!(format_bytes(10485760), "10.0 MB"); + assert_eq!(format_bytes(104857600), "100 MB"); + assert_eq!(format_bytes(1073741824), "1.00 GB"); + assert_eq!(format_bytes(1099511627776), "1.00 TB"); + } + + #[test] + fn test_format_number() { + assert_eq!(format_number(0), "0"); + assert_eq!(format_number(1), "1"); + assert_eq!(format_number(999), "999"); + assert_eq!(format_number(1000), "1,000"); + assert_eq!(format_number(1234), "1,234"); + assert_eq!(format_number(123456), "123,456"); + assert_eq!(format_number(1234567), "1,234,567"); + assert_eq!(format_number(1234567890), "1,234,567,890"); + } + #[test] fn test_empty_strings() { // Raw strings diff --git a/src/table_renderer.rs b/src/table_renderer.rs new file mode 100644 index 0000000..95a7aec --- /dev/null +++ b/src/table_renderer.rs @@ -0,0 +1,826 @@ +use comfy_table::{Attribute, Cell, Color, ColumnConstraint, ContentArrangement, Table, Width as ComfyWidth}; +use serde::Deserialize; +use serde_json::Value; +use terminal_size::{terminal_size, Width}; + +#[derive(Debug, Deserialize)] +#[serde(tag = "message_type")] +pub enum JsonLineMessage { + #[serde(rename = "START")] + Start { + result_columns: Vec, + // The following fields are part of the Firebolt JSONLines_Compact protocol + // and required for deserialization, but not used by the client renderer. + #[allow(dead_code)] + query_id: String, + #[allow(dead_code)] + request_id: String, + #[allow(dead_code)] + query_label: Option, + }, + #[serde(rename = "DATA")] + Data { data: Vec> }, + #[serde(rename = "FINISH_SUCCESSFULLY")] + FinishSuccessfully { statistics: Option }, + #[serde(rename = "FINISH_WITH_ERRORS")] + FinishWithErrors { errors: Vec }, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ResultColumn { + pub name: String, + // Column type from Firebolt server (e.g., "bigint", "integer", "text"). + // Currently unused for rendering but available for future type-aware formatting. + // Required for deserialization. + #[allow(dead_code)] + #[serde(rename = "type")] + pub column_type: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ErrorDetail { + pub description: String, +} + +#[derive(Clone, Debug)] +pub struct ParsedResult { + pub columns: Vec, + pub rows: Vec>, + pub statistics: Option, + pub errors: Option>, +} + +pub fn parse_jsonlines_compact(text: &str) -> Result> { + let mut columns: Vec = Vec::new(); + let mut all_rows: Vec> = Vec::new(); + let mut statistics: Option = None; + let mut errors: Option> = None; + + for line in text.lines() { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + + let message: JsonLineMessage = serde_json::from_str(trimmed)?; + + match message { + JsonLineMessage::Start { result_columns, .. } => { + columns = result_columns; + } + JsonLineMessage::Data { data } => { + all_rows.extend(data); + } + JsonLineMessage::FinishSuccessfully { statistics: stats } => { + statistics = stats; + } + JsonLineMessage::FinishWithErrors { errors: errs } => { + errors = Some(errs); + } + } + } + + Ok(ParsedResult { + columns, + rows: all_rows, + statistics, + errors, + }) +} + +pub fn render_table(columns: &[ResultColumn], rows: &[Vec], max_value_length: usize) -> String { + let mut table = Table::new(); + + // Enable dynamic content arrangement for automatic wrapping + table.set_content_arrangement(ContentArrangement::Dynamic); + + // Detect terminal width and calculate equal column widths + let terminal_width = terminal_size() + .map(|(Width(w), _)| w) + .unwrap_or(80); + + table.set_width(terminal_width); + + // Calculate equal column width if we have columns + let num_columns = columns.len(); + if num_columns > 0 { + // Subtract 4 for outer table borders, then divide equally + let available_width = terminal_width.saturating_sub(4); + let col_width = available_width / num_columns as u16; + + // Set explicit column constraints for equal widths + let constraints: Vec = (0..num_columns) + .map(|_| ColumnConstraint::UpperBoundary(ComfyWidth::Fixed(col_width))) + .collect(); + table.set_constraints(constraints); + } + + // Add headers with styling + let header_cells: Vec = columns + .iter() + .map(|col| Cell::new(&col.name).fg(Color::Cyan).add_attribute(Attribute::Bold)) + .collect(); + + table.set_header(header_cells); + + // Add data rows + for row in rows { + let row_cells: Vec = row + .iter() + .map(|val| { + let value_str = format_value(val); + // Truncate strings exceeding max_value_length + let display_value = if value_str.len() > max_value_length { + format!("{}...", &value_str[..max_value_length.saturating_sub(3)]) + } else { + value_str + }; + // Color NULL values differently to distinguish from string "NULL" + if val.is_null() { + Cell::new(display_value).fg(Color::DarkGrey) + } else { + Cell::new(display_value) + } + }) + .collect(); + + table.add_row(row_cells); + } + + table.to_string() +} + +/// Format a serde_json::Value for table display. +/// +/// Handles Firebolt's JSONLines_Compact serialization format where different +/// Firebolt types are serialized to JSON in specific ways: +/// +/// **JSON Numbers** (rendered via `.to_string()`): +/// - INT, DOUBLE, REAL → JSON numbers (e.g., `42`, `3.14`) +/// +/// **JSON Strings** (rendered as-is without quotes): +/// - BIGINT → JSON strings to preserve precision (e.g., `"9223372036854775807"`) +/// - NUMERIC/DECIMAL → JSON strings for exact decimals (e.g., `"1.23"`) +/// - TEXT → JSON strings (e.g., `"regular text"`) +/// - DATE → ISO format strings (e.g., `"2026-02-06"`) +/// - TIMESTAMP → ISO-like strings with timezone (e.g., `"2026-02-06 15:35:34+00"`) +/// - BYTEA → Hex-encoded binary data (e.g., `"\\x48656c6c6f"`) +/// - GEOGRAPHY → WKB (Well-Known Binary) format in hex (e.g., `"0101000020E6..."`) +/// +/// **Other JSON Types**: +/// - ARRAY → JSON arrays (e.g., `[1,2,3]`) +/// - BOOLEAN → JSON booleans (e.g., `true`, `false`) +/// - NULL → JSON `null`, rendered as "NULL" string for SQL-style display +/// +/// Note: The `column_type` field in ResultColumn is available but currently unused. +/// It could be leveraged for future type-aware formatting (e.g., right-align numbers, +/// format dates differently). +fn format_value(value: &Value) -> String { + match value { + Value::Null => "NULL".to_string(), + Value::String(s) => s.clone(), + Value::Number(n) => n.to_string(), + Value::Bool(b) => b.to_string(), + Value::Array(_) | Value::Object(_) => { + let json_str = serde_json::to_string(value).unwrap_or_else(|_| "".to_string()); + // Truncate very long JSON (e.g., query_telemetry with hundreds of KB) + const MAX_JSON_LENGTH: usize = 1000; + if json_str.len() > MAX_JSON_LENGTH { + format!("{}... (truncated)", &json_str[..MAX_JSON_LENGTH]) + } else { + json_str + } + } + } +} + +/// Calculate the display width of a string, ignoring ANSI escape codes +/// Render table in vertical format (two-column table with column names and values) +/// Used when table is too wide for horizontal display in auto mode +pub fn render_table_vertical( + columns: &[ResultColumn], + rows: &[Vec], + terminal_width: u16, + max_value_length: usize, +) -> String { + let mut output = String::new(); + + for (row_idx, row) in rows.iter().enumerate() { + // Row header + output.push_str(&format!("Row {}:\n", row_idx + 1)); + + // Create a two-column table for this row + let mut table = Table::new(); + table.set_content_arrangement(ContentArrangement::Dynamic); + + // Set column constraints to allow wrapping + // First column (names): narrow, fixed + // Second column (values): wide, allows wrapping + let available_width = if terminal_width > 10 { terminal_width - 4 } else { 76 }; + table.set_constraints(vec![ + ColumnConstraint::UpperBoundary(ComfyWidth::Fixed(30)), // Column names + ColumnConstraint::UpperBoundary(ComfyWidth::Fixed(available_width.saturating_sub(30))), // Values + ]); + + // Add rows (no header - just column name | value pairs) + for (col_idx, col) in columns.iter().enumerate() { + if col_idx < row.len() { + let value = format_value(&row[col_idx]); + + // Truncate long values + let truncated_value = if value.len() > max_value_length { + format!("{}...", &value[..max_value_length]) + } else { + value + }; + + // Column name cell (cyan, bold) + let name_cell = Cell::new(&col.name) + .fg(Color::Cyan) + .add_attribute(Attribute::Bold); + + // Value cell - color NULL values differently to distinguish from string "NULL" + let value_cell = if row[col_idx].is_null() { + Cell::new(truncated_value).fg(Color::DarkGrey) + } else { + Cell::new(truncated_value) + }; + + table.add_row(vec![name_cell, value_cell]); + } + } + + output.push_str(&table.to_string()); + + // Blank line between rows (except after last row) + if row_idx < rows.len() - 1 { + output.push('\n'); + output.push('\n'); + } + } + + output +} + +pub fn should_use_vertical_mode(columns: &[ResultColumn], terminal_width: u16, min_col_width: usize) -> bool { + let num_columns = columns.len(); + + if num_columns == 0 { + return false; + } + + // Simple logic: switch to vertical if each column has less than min_col_width chars available + let chars_per_column = (terminal_width as usize) / num_columns; + chars_per_column < min_col_width +} + +/// Format a serde_json::Value for CSV export. +/// +/// Similar to format_value(), but with CSV-specific differences: +/// - `Null` → empty string "" (CSV standard for null values) +/// - Other types formatted identically to format_value() +/// +/// Maintains Firebolt's serialization: BIGINT strings, NUMERIC strings, etc. +fn format_value_csv(val: &Value) -> String { + match val { + Value::Null => String::new(), + Value::Bool(b) => b.to_string(), + Value::Number(n) => n.to_string(), + Value::String(s) => s.clone(), + Value::Array(_) | Value::Object(_) => val.to_string(), + } +} + +/// Escape a CSV field according to RFC 4180 +fn escape_csv_field(field: &str) -> String { + // Escape fields containing comma, quote, or newline + if field.contains(',') || field.contains('"') || field.contains('\n') { + format!("\"{}\"", field.replace('"', "\"\"")) + } else { + field.to_string() + } +} + +/// Convert ParsedResult to CSV format +pub fn write_result_as_csv( + writer: &mut W, + columns: &[ResultColumn], + rows: &[Vec], +) -> Result<(), Box> { + // Write CSV header + let header = columns + .iter() + .map(|col| escape_csv_field(&col.name)) + .collect::>() + .join(","); + writeln!(writer, "{}", header)?; + + // Write data rows + for row in rows { + let row_str = row + .iter() + .map(|val| escape_csv_field(&format_value_csv(val))) + .collect::>() + .join(","); + writeln!(writer, "{}", row_str)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_simple_jsonlines() { + let input = r#"{"message_type":"START","query_id":"123","request_id":"456","query_label":null,"result_columns":[{"name":"col1","type":"integer"},{"name":"col2","type":"text"}]} +{"message_type":"DATA","data":[[1,"hello"],[2,"world"]]} +{"message_type":"FINISH_SUCCESSFULLY","statistics":{"elapsed":0.123}}"#; + + let result = parse_jsonlines_compact(input).unwrap(); + assert_eq!(result.columns.len(), 2); + assert_eq!(result.rows.len(), 2); + assert!(result.errors.is_none()); + } + + #[test] + fn test_parse_multiple_data_messages() { + let input = r#"{"message_type":"START","query_id":"123","request_id":"456","query_label":null,"result_columns":[{"name":"id","type":"integer"}]} +{"message_type":"DATA","data":[[1],[2]]} +{"message_type":"DATA","data":[[3],[4]]} +{"message_type":"FINISH_SUCCESSFULLY","statistics":{}}"#; + + let result = parse_jsonlines_compact(input).unwrap(); + assert_eq!(result.rows.len(), 4); + } + + #[test] + fn test_parse_with_errors() { + let input = r#"{"message_type":"START","query_id":"123","request_id":"456","query_label":null,"result_columns":[]} +{"message_type":"FINISH_WITH_ERRORS","errors":[{"description":"Syntax error"}]}"#; + + let result = parse_jsonlines_compact(input).unwrap(); + assert!(result.errors.is_some()); + assert_eq!(result.errors.unwrap()[0].description, "Syntax error"); + } + + #[test] + fn test_format_value_firebolt_bigint() { + // BIGINT arrives as JSON string (not number) to preserve precision + let bigint_max = Value::String("9223372036854775807".to_string()); + assert_eq!(format_value(&bigint_max), "9223372036854775807"); + + let bigint_min = Value::String("-9223372036854775808".to_string()); + assert_eq!(format_value(&bigint_min), "-9223372036854775808"); + + // Should display as-is without quotes + let output = format_value(&bigint_max); + assert!(!output.starts_with('"'), "BIGINT should not have quotes in display"); + } + + #[test] + fn test_format_value_firebolt_numeric() { + // NUMERIC/DECIMAL arrives as JSON string for exact precision + let decimal = Value::String("1.23".to_string()); + assert_eq!(format_value(&decimal), "1.23"); + + let large_decimal = Value::String("12345678901234567890.123456789".to_string()); + assert_eq!(format_value(&large_decimal), "12345678901234567890.123456789"); + } + + #[test] + fn test_format_value_firebolt_integers() { + // INT arrives as JSON number + let int_val = Value::Number(42.into()); + assert_eq!(format_value(&int_val), "42"); + + let negative = Value::Number((-42).into()); + assert_eq!(format_value(&negative), "-42"); + + // INT min/max values + let int_max = Value::Number(2147483647.into()); + assert_eq!(format_value(&int_max), "2147483647"); + + let int_min = Value::Number((-2147483648).into()); + assert_eq!(format_value(&int_min), "-2147483648"); + } + + #[test] + fn test_format_value_firebolt_floats() { + // DOUBLE/REAL arrive as JSON numbers + let pi = serde_json::Number::from_f64(3.14159).unwrap(); + let formatted = format_value(&Value::Number(pi)); + assert!(formatted.starts_with("3.14"), "Got: {}", formatted); + + // Integer-valued float keeps decimal point + let one = serde_json::Number::from_f64(1.0).unwrap(); + assert_eq!(format_value(&Value::Number(one)), "1.0"); + } + + #[test] + fn test_format_value_firebolt_temporal() { + // DATE arrives as ISO format string + let date = Value::String("2026-02-06".to_string()); + assert_eq!(format_value(&date), "2026-02-06"); + + // TIMESTAMP arrives as ISO-like format with timezone + let timestamp = Value::String("2026-02-06 15:35:34.519403+00".to_string()); + assert_eq!(format_value(×tamp), "2026-02-06 15:35:34.519403+00"); + } + + #[test] + fn test_format_value_firebolt_arrays() { + // ARRAY arrives as JSON array + let int_array = serde_json::json!([1, 2, 3]); + let formatted = format_value(&int_array); + assert!(formatted.contains("1")); + assert!(formatted.contains("2")); + assert!(formatted.contains("3")); + + // Empty array + let empty = Value::Array(vec![]); + assert_eq!(format_value(&empty), "[]"); + } + + #[test] + fn test_format_value_firebolt_text() { + // Regular TEXT + let text = Value::String("regular text".to_string()); + assert_eq!(format_value(&text), "regular text"); + + // TEXT with JSON-like content (still a string, not parsed) + let json_text = Value::String("{\"key\": \"value\"}".to_string()); + assert_eq!(format_value(&json_text), "{\"key\": \"value\"}"); + + // Unicode text + let unicode = Value::String("🔥 emoji text".to_string()); + assert_eq!(format_value(&unicode), "🔥 emoji text"); + } + + #[test] + fn test_format_value_firebolt_bytea() { + // BYTEA arrives as hex-encoded string + let bytea = Value::String("\\x48656c6c6f".to_string()); + assert_eq!(format_value(&bytea), "\\x48656c6c6f"); + + // Should display as-is without interpretation + let output = format_value(&bytea); + assert!(output.starts_with("\\x"), "BYTEA should preserve hex encoding"); + } + + #[test] + fn test_format_value_firebolt_geography() { + // GEOGRAPHY arrives as WKB (Well-Known Binary) format in hex + let geo_point = Value::String("0101000020E6100000FEFFFFFFFFFFEF3F0000000000000040".to_string()); + assert_eq!(format_value(&geo_point), "0101000020E6100000FEFFFFFFFFFFEF3F0000000000000040"); + + // Should display as hex string without interpretation + let output = format_value(&geo_point); + assert!(output.len() > 20, "GEOGRAPHY hex strings are long"); + assert!(!output.starts_with('"'), "Should not have quotes in display"); + } + + #[test] + fn test_format_value_firebolt_null_and_bool() { + // NULL rendered as "NULL" string for clarity + assert_eq!(format_value(&Value::Null), "NULL"); + + // BOOLEAN rendered as lowercase + assert_eq!(format_value(&Value::Bool(true)), "true"); + assert_eq!(format_value(&Value::Bool(false)), "false"); + } + + #[test] + fn test_format_value_csv_null_handling() { + // In CSV, NULL should be empty string (not "NULL") + assert_eq!(format_value_csv(&Value::Null), ""); + + // But in table format, NULL should be "NULL" + assert_eq!(format_value(&Value::Null), "NULL"); + + // BIGINT strings should work in CSV too + let bigint = Value::String("9223372036854775807".to_string()); + assert_eq!(format_value_csv(&bigint), "9223372036854775807"); + } + + #[test] + fn test_format_value_null() { + assert_eq!(format_value(&Value::Null), "NULL"); + } + + #[test] + fn test_format_value_string() { + assert_eq!(format_value(&Value::String("test".to_string())), "test"); + } + + #[test] + fn test_render_vertical_single_row() { + let columns = vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "name".to_string(), + column_type: "text".to_string(), + }, + ResultColumn { + name: "status".to_string(), + column_type: "text".to_string(), + }, + ]; + let rows = vec![vec![ + Value::Number(1.into()), + Value::String("Alice".to_string()), + Value::String("active".to_string()), + ]]; + + let output = render_table_vertical(&columns, &rows, 80, 1000); + + // Check for row header + assert!(output.contains("Row 1:")); + + // Check for table format (contains column names) + assert!(output.contains("id")); + assert!(output.contains("name")); + assert!(output.contains("status")); + + // Check for values + assert!(output.contains("1")); + assert!(output.contains("Alice")); + assert!(output.contains("active")); + + // Should have table borders + assert!(output.contains('+')); + assert!(output.contains('|')); + } + + #[test] + fn test_render_vertical_multiple_rows() { + let columns = vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "name".to_string(), + column_type: "text".to_string(), + }, + ]; + let rows = vec![ + vec![Value::Number(1.into()), Value::String("Alice".to_string())], + vec![Value::Number(2.into()), Value::String("Bob".to_string())], + ]; + + let output = render_table_vertical(&columns, &rows, 80, 1000); + + // Should have both row headers + assert!(output.contains("Row 1:")); + assert!(output.contains("Row 2:")); + + // Should have both names + assert!(output.contains("Alice")); + assert!(output.contains("Bob")); + + // Should have blank line between rows (two consecutive newlines between tables) + let parts: Vec<&str> = output.split("Row 2:").collect(); + assert!(parts.len() == 2); + // Check there's spacing before "Row 2:" + assert!(parts[0].ends_with("\n\n") || parts[0].ends_with("\n+")); + } + + #[test] + fn test_render_vertical_value_truncation() { + let columns = vec![ + ResultColumn { + name: "long_col".to_string(), + column_type: "text".to_string(), + }, + ]; + let long_value = "a".repeat(2000); // 2000 characters + let rows = vec![vec![Value::String(long_value)]]; + + let output = render_table_vertical(&columns, &rows, 80, 1000); + + // Should be truncated to 1000 chars + "..." + assert!(output.contains("...")); + + // Value should not exceed max_value_length + let lines: Vec<&str> = output.lines().collect(); + for line in lines { + // Each line in the table shouldn't be excessively long + assert!(line.len() < 1100); // Some margin for table borders + } + } + + #[test] + fn test_json_truncation() { + // Create a very large JSON array + let large_array = Value::Array(vec![Value::String("x".repeat(500)); 10]); + let rows = vec![vec![large_array]]; + + let formatted = format_value(&rows[0][0]); + + // Should be truncated + assert!(formatted.contains("truncated") || formatted.len() < 5000); + } + + #[test] + fn test_should_use_vertical_mode() { + let columns = vec![ + ResultColumn { + name: "col1".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "col2".to_string(), + column_type: "text".to_string(), + }, + ]; + + // Wide terminal, few columns -> horizontal + // 150 width / 2 columns = 75 chars per column >= 10, stay horizontal + assert!(!should_use_vertical_mode(&columns, 150, 10)); + + // Many columns -> vertical + // 150 width / 20 columns = 7.5 chars per column < 10, use vertical + let many_columns: Vec = (0..20) + .map(|i| ResultColumn { + name: format!("column_name_{}", i), + column_type: "int".to_string(), + }) + .collect(); + assert!(should_use_vertical_mode(&many_columns, 150, 10)); + + // Narrow terminal with few columns -> vertical + // 40 width / 5 columns = 8 chars per column < 10, use vertical + let five_columns = vec![ + ResultColumn { + name: "a".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "b".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "c".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "d".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "e".to_string(), + column_type: "int".to_string(), + }, + ]; + assert!(should_use_vertical_mode(&five_columns, 40, 10)); + + // Configurable threshold test + // 80 width / 10 columns = 8 chars per column + let ten_columns: Vec = (0..10) + .map(|i| ResultColumn { + name: format!("col{}", i), + column_type: "int".to_string(), + }) + .collect(); + // With threshold 8, should stay horizontal (8 >= 8) + assert!(!should_use_vertical_mode(&ten_columns, 80, 8)); + // With threshold 9, should switch to vertical (8 < 9) + assert!(should_use_vertical_mode(&ten_columns, 80, 9)); + } + + #[test] + fn test_vertical_mode_threshold() { + // Test that the decision is based purely on terminal_width / num_columns + let three_columns: Vec = (0..3) + .map(|i| ResultColumn { + name: format!("col{}", i), + column_type: "text".to_string(), + }) + .collect(); + + // 80 width / 3 columns = 26.6 chars per column >= 10, stay horizontal + assert!(!should_use_vertical_mode(&three_columns, 80, 10)); + + // But with a higher threshold of 30, should switch to vertical (26.6 < 30) + assert!(should_use_vertical_mode(&three_columns, 80, 30)); + + // Edge case: exactly at threshold + let eight_columns: Vec = (0..8) + .map(|i| ResultColumn { + name: format!("c{}", i), + column_type: "int".to_string(), + }) + .collect(); + // 80 width / 8 columns = 10 chars per column + // Should stay horizontal (10 >= 10) + assert!(!should_use_vertical_mode(&eight_columns, 80, 10)); + // Should switch to vertical (10 < 11) + assert!(should_use_vertical_mode(&eight_columns, 80, 11)); + } + + #[test] + fn test_write_result_as_csv() { + let columns = vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "name".to_string(), + column_type: "text".to_string(), + }, + ]; + let rows = vec![ + vec![Value::Number(1.into()), Value::String("Alice".to_string())], + vec![Value::Number(2.into()), Value::String("Bob".to_string())], + ]; + + let mut output = Vec::new(); + write_result_as_csv(&mut output, &columns, &rows).unwrap(); + + let csv_str = String::from_utf8(output).unwrap(); + assert!(csv_str.contains("id,name")); + assert!(csv_str.contains("1,Alice")); + assert!(csv_str.contains("2,Bob")); + } + + #[test] + fn test_csv_escaping() { + let columns = vec![ResultColumn { + name: "col".to_string(), + column_type: "text".to_string(), + }]; + let rows = vec![ + vec![Value::String("has,comma".to_string())], + vec![Value::String("has\"quote".to_string())], + vec![Value::String("has\nnewline".to_string())], + ]; + + let mut output = Vec::new(); + write_result_as_csv(&mut output, &columns, &rows).unwrap(); + + let csv_str = String::from_utf8(output).unwrap(); + assert!(csv_str.contains("\"has,comma\"")); + assert!(csv_str.contains("\"has\"\"quote\"")); + assert!(csv_str.contains("\"has\nnewline\"")); + } + + #[test] + fn test_null_rendering() { + // Test that NULL values and string "NULL" are both rendered correctly + // (Color distinction can be verified manually; tests just ensure no crashes) + let columns = vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "name".to_string(), + column_type: "text".to_string(), + }, + ]; + let rows = vec![ + vec![Value::Number(1.into()), Value::Null], // Real NULL + vec![Value::Number(2.into()), Value::String("NULL".to_string())], // String "NULL" + vec![Value::Number(3.into()), Value::String("test".to_string())], // Regular string + ]; + + // Should not crash and should contain NULL text + let output = render_table(&columns, &rows, 1000); + assert!(output.contains("NULL")); + assert!(output.contains("test")); + assert!(output.contains('1')); + assert!(output.contains('2')); + assert!(output.contains('3')); + } + + #[test] + fn test_null_rendering_vertical() { + // Test that NULL values are rendered in vertical mode without crashes + let columns = vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "value".to_string(), + column_type: "text".to_string(), + }, + ]; + let rows = vec![ + vec![Value::Number(1.into()), Value::Null], + vec![Value::Number(2.into()), Value::String("NULL".to_string())], + ]; + + // Should not crash and should contain NULL text + let output = render_table_vertical(&columns, &rows, 80, 1000); + assert!(output.contains("NULL")); + assert!(output.contains("id")); + assert!(output.contains("value")); + } +} diff --git a/src/utils.rs b/src/utils.rs index 0a7c785..e9bb4a9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use std::env; use std::fs; use std::io::stderr; use std::io::Write; @@ -32,6 +33,13 @@ pub fn sa_token_path() -> Result> { Ok(init_root_path()?.join("fb_sa_token")) } +/// Get path for temporary CSV file in system temp directory +pub fn temp_csv_path() -> Result> { + let mut path = env::temp_dir(); + path.push(format!("fb_result_{}.csv", std::process::id())); + Ok(path) +} + // Format remaining time for token validity pub fn format_remaining_time(time: SystemTime, maybe_more: String) -> Result> { let remaining = time.duration_since(SystemTime::now())?.as_secs(); @@ -83,4 +91,12 @@ mod tests { let sa_token = sa_token_path().unwrap(); assert!(sa_token.ends_with("fb_sa_token")); } + + #[test] + fn test_temp_csv_path() { + let path = temp_csv_path().unwrap(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + assert!(file_name.starts_with("fb_result_")); + assert!(file_name.ends_with(".csv")); + } } diff --git a/src/viewer.rs b/src/viewer.rs new file mode 100644 index 0000000..83f95ee --- /dev/null +++ b/src/viewer.rs @@ -0,0 +1,180 @@ +use crate::context::Context; +use crate::table_renderer::write_result_as_csv; +use crate::utils::temp_csv_path; +use std::fs::File; + +/// Open csvlens viewer for the last query result +pub fn open_csvlens_viewer(context: &Context) -> Result<(), Box> { + // csvlens only works with client-side rendering (when last_result is populated) + if !context.args.format.starts_with("client:") { + return Err("csvlens viewer requires client-side rendering. Use --format client:auto or similar.".into()); + } + + // Check if we have a result to display + let result = match &context.last_result { + Some(r) => r, + None => { + eprintln!("No query results to display. Run a query first."); + return Ok(()); + } + }; + + // Check for errors in last result + if let Some(ref errors) = result.errors { + eprintln!("Cannot display results - last query had errors:"); + for error in errors { + eprintln!(" {}", error.description); + } + return Ok(()); + } + + // Check if result is empty + if result.columns.is_empty() { + eprintln!("No data to display (no columns in result)."); + return Ok(()); + } + + if result.rows.is_empty() { + eprintln!("Query returned 0 rows. Nothing to display."); + return Ok(()); + } + + // Write result to temporary CSV file + let csv_path = temp_csv_path()?; + let mut file = File::create(&csv_path)?; + write_result_as_csv(&mut file, &result.columns, &result.rows)?; + drop(file); // Ensure file is flushed and closed + + if context.args.verbose { + eprintln!("Wrote result to: {:?}", csv_path); + eprintln!("Opening csvlens viewer... (press 'q' or ESC to exit)"); + } + + // Launch csvlens viewer + let csv_path_str = csv_path.to_string_lossy().to_string(); + let options = csvlens::CsvlensOptions { + filename: Some(csv_path_str), + ..Default::default() + }; + + match csvlens::run_csvlens_with_options(options) { + Ok(_) => Ok(()), + Err(e) => { + eprintln!("Error opening csvlens: {}", e); + Err(Box::new(e)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::table_renderer::{ErrorDetail, ParsedResult, ResultColumn}; + use serde_json::Value; + + #[test] + fn test_no_result_error() { + let mut args = crate::args::get_args().unwrap(); + args.format = String::from("client:auto"); + let context = Context::new(args); + + // Should not panic, should return Ok with error message + let result = open_csvlens_viewer(&context); + assert!(result.is_ok()); + } + + #[test] + fn test_error_result() { + let mut args = crate::args::get_args().unwrap(); + args.format = String::from("client:auto"); + let mut context = Context::new(args); + context.last_result = Some(ParsedResult { + columns: vec![], + rows: vec![], + statistics: None, + errors: Some(vec![ErrorDetail { + description: "Test error".to_string(), + }]), + }); + + let result = open_csvlens_viewer(&context); + assert!(result.is_ok()); + } + + #[test] + fn test_empty_columns() { + let mut args = crate::args::get_args().unwrap(); + args.format = String::from("client:auto"); + let mut context = Context::new(args); + context.last_result = Some(ParsedResult { + columns: vec![], + rows: vec![], + statistics: None, + errors: None, + }); + + let result = open_csvlens_viewer(&context); + assert!(result.is_ok()); + } + + #[test] + fn test_empty_rows() { + let mut args = crate::args::get_args().unwrap(); + args.format = String::from("client:auto"); + let mut context = Context::new(args); + context.last_result = Some(ParsedResult { + columns: vec![ResultColumn { + name: "col1".to_string(), + column_type: "int".to_string(), + }], + rows: vec![], + statistics: None, + errors: None, + }); + + let result = open_csvlens_viewer(&context); + assert!(result.is_ok()); + } + + #[test] + fn test_csv_file_creation() { + let mut args = crate::args::get_args().unwrap(); + args.format = String::from("client:auto"); + let mut context = Context::new(args); + context.last_result = Some(ParsedResult { + columns: vec![ + ResultColumn { + name: "id".to_string(), + column_type: "int".to_string(), + }, + ResultColumn { + name: "name".to_string(), + column_type: "text".to_string(), + }, + ], + rows: vec![ + vec![Value::Number(1.into()), Value::String("Alice".to_string())], + vec![Value::Number(2.into()), Value::String("Bob".to_string())], + ], + statistics: None, + errors: None, + }); + + // This test verifies that the CSV file is created and written correctly + // We can't test the actual csvlens launch without a terminal, but we can + // verify the file creation part + let csv_path = temp_csv_path().unwrap(); + let mut file = File::create(&csv_path).unwrap(); + write_result_as_csv(&mut file, &context.last_result.as_ref().unwrap().columns, &context.last_result.as_ref().unwrap().rows).unwrap(); + drop(file); + + // Verify file exists and has content + let content = std::fs::read_to_string(&csv_path).unwrap(); + assert!(content.contains("id,name")); + assert!(content.contains("1,Alice")); + assert!(content.contains("2,Bob")); + + // Clean up + std::fs::remove_file(&csv_path).ok(); + } +} diff --git a/tests/cli.rs b/tests/cli.rs index 35166a4..74efbaa 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -288,3 +288,103 @@ fn test_exit_code_on_query_error_interactive() { "Exit code should be non-zero when any query in session fails" ); } + +#[test] +fn test_auto_format() { + let (success, stdout, _) = run_fb(&["--core", "--format=client:auto", "SELECT 1 as id, 'test' as name"]); + assert!(success); + assert!(stdout.contains("id")); + assert!(stdout.contains("name")); + assert!(stdout.contains("test")); +} + +#[test] +fn test_expanded_format() { + let (success, stdout, _) = run_fb(&["--core", "--format=client:vertical", "SELECT 1 as id, 'test' as name"]); + assert!(success); + assert!(stdout.contains("Row 1:")); + assert!(stdout.contains("id")); + assert!(stdout.contains("name")); + assert!(stdout.contains("test")); +} + +#[test] +fn test_wide_table_auto_expanded() { + // Query with many columns should automatically use vertical mode + let (success, stdout, _) = run_fb(&[ + "--core", + "--format=client:auto", + "SELECT 1 as a, 2 as b, 3 as c, 4 as d, 5 as e, 6 as f, \ + 7 as g, 8 as h, 9 as i, 10 as j, 11 as k, 12 as l, 13 as m", + ]); + assert!(success); + assert!(stdout.contains("Row 1:")); // Should auto-switch to vertical +} + +#[test] +fn test_narrow_table_stays_horizontal() { + // Query with few columns should stay horizontal + let (success, stdout, _) = run_fb(&["--core", "--format=client:auto", "SELECT 1 as id, 'test' as name"]); + assert!(success); + assert!(!stdout.contains("Row 1:")); // Should NOT use vertical + assert!(stdout.contains("id")); // But still contains data +} + +#[test] +fn test_client_format_horizontal() { + let (success, stdout, _) = run_fb(&["--core", "--format=client:horizontal", "SELECT 1 as id, 'test' as name"]); + assert!(success); + + // Should have horizontal table format + assert!(stdout.contains("id")); + assert!(stdout.contains("name")); + assert!(stdout.contains("test")); + assert!(stdout.contains('+')); // Has borders + assert!(stdout.contains('|')); // Has column separators + + // Should NOT use vertical format + assert!(!stdout.contains("Row 1")); +} + +#[test] +fn test_client_format_vertical() { + let (success, stdout, _) = run_fb(&["--core", "--format=client:vertical", "SELECT 1 as id, 'test' as name"]); + assert!(success); + + // Should have vertical format + assert!(stdout.contains("Row 1")); + assert!(stdout.contains("id")); + assert!(stdout.contains("name")); +} + +#[test] +fn test_client_format_auto() { + // Auto should choose based on terminal width + let (success, stdout, _) = run_fb(&["--core", "--format=client:auto", "SELECT 1 as id"]); + assert!(success); + + // Should have table format + assert!(stdout.contains('+')); // Has table borders + assert!(stdout.contains("id")); +} + +#[test] +fn test_server_format_json() { + // Server-side format (no client: prefix) + let (success, stdout, _) = run_fb(&["--core", "--format=JSON_Compact", "SELECT 1 as id"]); + assert!(success); + + // Should have JSON format from server + assert!(stdout.contains('{')); // JSON + assert!(!stdout.contains('+')); // Not a table +} + +#[test] +fn test_server_format_psql() { + let (success, stdout, _) = run_fb(&["--core", "--format=PSQL", "SELECT 1 as id"]); + assert!(success); + + // Should have PSQL format from server + assert!(!stdout.contains('+')); // No table borders (PSQL style is different) + assert!(stdout.contains("id")); +}