From fd352db1998f9e0af8f2f1cf5e914c6158e352b2 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 2 Feb 2025 09:28:34 +0100 Subject: [PATCH 01/15] clarify developer documentation --- CHANGELOG.md | 3 +++ DEVELOPMENT.md | 20 +++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 929698f..6a3be28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed +- Clarify developer documentation + ## [0.2.9] - 2025-02-02 ### Fixed diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 80a7f72..80a1d7a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -47,7 +47,7 @@ git config core.hooksPath hooks ./hooks/pre-push ``` -1. Do a dry-run with +1. Dry-run the release process with ```bash cargo release [LEVEL|VERSION] @@ -61,18 +61,24 @@ git config core.hooksPath hooks cargo release [LEVEL|VERSION] --execute --no-publish ``` -1. Create a pull request for the `development` branch into `main`. If all pre-checks succeed, conclude the pull request. A release draft will is created from [CHANGELOG.md](CHANGELOG.md). +1. Create a pull request (PR) for the `development` branch into `main`. A release draft will be created from [CHANGELOG.md](CHANGELOG.md). + +1. If all pre-checks succeed, conclude the pull request by commenting + ``` + /fast-forward + ``` + in the PR conversation, see [below](#merge-onto-main) for an explanation. 1. Review the release draft under "Releases" and publish the release. -## Merge onto `main` +### Merge onto `main` -GitHub.com does not allow for fast-forward merges on the web UI. This means that the tags created on the `development` branch will not point to the main branch after merging. To fix this we need to disable the default merging and create our own way. +GitHub.com [does not allow for fast-forward merges on the web UI](https://ole-mn.medium.com/752f900f45e8). This means that the tags created on the `development` branch will not point to commits on the main branch after merging. To fix this we need to disable the default merging and create our own way. -### Setup +#### Setup 1. Abuse "Setting -> General -> Pull Requests" and "Settings -> Rules -> Ruleset (main)" to create conflicting requirements for the `main` branch. This disables merging on the web UI. See: https://github.com/orgs/community/discussions/4618#discussioncomment-11652479 1. Instead, use GitHub actions to fast-forward merge. See https://github.com/sequoia-pgp/fast-forward for details. -### Usage -After all tests have passed, comment `/fast-forward` in the PR discussions. +#### Usage +After all tests have passed, comment `/fast-forward` in the PR conversation. From 8ca402aff0bf17916ba4df4675ceaef6922328f2 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 2 Feb 2025 11:27:12 +0100 Subject: [PATCH 02/15] fix image ALT text --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56cee65..3985405 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Find installable packages at lightning speed and sort the result by relevance, s ... in configurable individual colors, optionally separated by a newline. Have a look: -![Color output of nps neovim](https://i.imgur.com/wNnWdxC.png "nps avahi") +![The command `nps avahi` lists all nixpkgs matching `avahi`, sorted by relevance.](https://i.imgur.com/wNnWdxC.png "nps avahi") ## Installation ### Try It Without Installing From 669634fd1beaa079497e4ff36e985c9583730143 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Fri, 21 Mar 2025 12:08:10 +0100 Subject: [PATCH 03/15] fix minor typos --- src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index c452b6b..2c8b0af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -61,7 +61,7 @@ const DEFAULTS: Defaults = Defaults { struct Cli { // default_value_t: value if flag (or env var) not present // default_missing_value: value if flag is present, but has no value - // needs .num_args(0..N) and .require_equals(true) + // needs `.num_args(0..N)` and `.require_equals(true)` // require_equals: force `--option=val` syntax // env: read env var if flag not present // takes_values: accept values from command line @@ -171,7 +171,7 @@ struct Cli { )] separate: bool, - /// Search for any SEARCH_TERM in package names, description or versions + /// Search for any SEARCH_TERM in package names, description, or versions #[arg( required_unless_present_any = ["refresh"] )] @@ -480,7 +480,7 @@ fn sort_and_pad_matches(cli: &Cli, raw_matches: String) -> Result Result = line.splitn(3, ' ').collect(); - #[allow(clippy::get_first)] // supress clippy warning for this block + #[allow(clippy::get_first)] // suppress clippy warning for this block let name = split_line.get(0).unwrap_or(&""); let version = split_line.get(1).unwrap_or(&""); let description = split_line.get(2).unwrap_or(&""); @@ -510,7 +510,7 @@ fn sort_and_pad_matches(cli: &Cli, raw_matches: String) -> Result { if converted_name == converted_search_term { @@ -899,7 +899,7 @@ fn main() -> ExitCode { log::debug!("Log level set to: {}", log_level); - // Set a supports-color override based on the variable passed in. + // Set a "supports-color" override based on the variable passed in. let color_choice = match cli.color { clap::ColorChoice::Always => { log::debug!("clap::ColorChoice set to Always"); From ce3aa2811eaa25eb9f2306a472dcffbbc9142386 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Fri, 21 Mar 2025 21:25:04 +0100 Subject: [PATCH 04/15] add -m flag for multi-line output --- CHANGELOG.md | 6 +- Cargo.lock | 164 +++--- Cargo.toml | 12 +- src/cache_refresh.rs | 295 +++++++++++ src/cli.rs | 419 +++++++++++++++ src/main.rs | 1162 ++---------------------------------------- src/matches.rs | 717 ++++++++++++++++++++++++++ tests/integration.rs | 18 +- 8 files changed, 1589 insertions(+), 1204 deletions(-) create mode 100644 src/cache_refresh.rs create mode 100644 src/cli.rs create mode 100644 src/matches.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a3be28..3687304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Added +- Flag `-m` for multi-line output +- Tests for new flag + ### Fixed -- Clarify developer documentation +- Fixes to README.md and DEVELOPMENT.md ## [0.2.9] - 2025-02-02 diff --git a/Cargo.lock b/Cargo.lock index 0fac1df..a67d0dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bstr" @@ -108,9 +108,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" dependencies = [ "clap_builder", "clap_derive", @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" dependencies = [ "anstream", "anstyle", @@ -130,9 +130,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.24" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -194,14 +194,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -232,21 +232,21 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", - "windows-targets", ] [[package]] name = "globset" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", @@ -349,12 +349,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -363,21 +357,45 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "libc" -version = "0.2.169" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" [[package]] name = "lock_api" @@ -391,9 +409,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.25" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" @@ -446,9 +464,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" [[package]] name = "parking_lot" @@ -473,6 +491,21 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "predicates" version = "3.1.3" @@ -505,27 +538,33 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] @@ -561,9 +600,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "0.38.44" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" dependencies = [ "bitflags", "errno", @@ -574,9 +613,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "scopeguard" @@ -586,18 +625,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -606,9 +645,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -618,9 +657,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "strsim" @@ -630,9 +669,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.98" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -650,11 +689,10 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.16.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand", "getrandom", "once_cell", @@ -679,9 +717,9 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "unicode-ident" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "utf8parse" @@ -691,18 +729,18 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -791,9 +829,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] diff --git a/Cargo.toml b/Cargo.toml index dd2c931..de71edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,15 +11,15 @@ readme = "README.md" edition = "2021" [dependencies] -clap = { version = "4.5.27", features = ["derive", "env", "string"] } -env_logger = "0.11.6" +clap = { version = "4.5.32", features = ["derive", "env", "string"] } +env_logger = "0.11.7" grep = "0.3.2" home = "0.5.11" -log = "0.4.25" +log = "0.4.26" regex = "1.11.1" -serde = { version = "1.0.217", features = ["derive"] } -serde_json = "1.0.138" -tempfile = "3.16.0" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +tempfile = "3.19.1" termcolor = "1.4.1" [dev-dependencies] diff --git a/src/cache_refresh.rs b/src/cache_refresh.rs new file mode 100644 index 0000000..77583a3 --- /dev/null +++ b/src/cache_refresh.rs @@ -0,0 +1,295 @@ +use serde::Deserialize; +use std::{ + collections::HashMap, + error::Error, + fs, + io::{self, Write}, + path::PathBuf, + process::{Command, Stdio}, + str, +}; +use tempfile::NamedTempFile; + +/// Check if flakes are enabled +fn check_flakes_enabled() -> Result> { + let probe_for_flakes = Command::new("nix") + .arg("--extra-experimental-features") + .arg("nix-command") + .arg("config") + .arg("show") + .stdout(Stdio::piped()) + .spawn() + .map_err(|err| format!("Can't execute `nix` command: {err}"))?; + let find_experimental_features = Command::new("grep") + .arg("^experimental-features") + .stdin(Stdio::from(probe_for_flakes.stdout.unwrap())) + .stdout(Stdio::piped()) + .spawn() + .map_err(|err| format!("Can't execute `grep` command: {err}"))?; + let find_flakes = Command::new("grep") + .arg("flakes") + .stdin(Stdio::from(find_experimental_features.stdout.unwrap())) + .stdout(Stdio::piped()) + .status() + .map_err(|err| format!("Can't execute `grep` command: {err}"))?; + + Ok(find_flakes.success()) +} + +/// Check if requested `nps` features match system features +/// +/// Give helpful warnings if there is a mismatch. +fn check_for_features( + flakes_enabled: bool, + experimental: bool, + quiet: bool, +) -> Result<(), Box> { + if flakes_enabled && !experimental { + let flakes_messages = [ + "Feature mismatch:", + "> Your system seems to be based on flakes.", + "> You may want to use `nps -e=true ...` instead to enable querying flake-based packages.", + ]; + for flake_message in flakes_messages { + message(flake_message, quiet)?; + log::warn!("{}", flake_message); + } + } + if !flakes_enabled && experimental { + let channels_messages = [ + "Feature mismatch:", + "> Your system seems to be based on channels.", + "> You may want to use `nps -e=false ...` instead to query packages from channels.", + ]; + for channel_message in channels_messages { + message(channel_message, quiet)?; + log::warn!("{}", channel_message); + } + } + + Ok(()) +} + +/// Print messages if quiet==false +fn message(message_string: &str, quiet: bool) -> Result<(), Box> { + if !quiet { + writeln!(io::stdout(), "{}", message_string) + .map_err(|err| format!("Can't write to stdout: {err}"))?; + } + Ok(()) +} + +/// Format to parse JSON package info into +#[derive(Debug, Deserialize)] +struct Package { + // we are not using `pname` + version: String, + description: String, +} + +/// Parse package info from JSON to (NAME VERSION DESCRIPTION) lines +fn parse_json_to_lines(raw_output: &str) -> Result> { + // Load JSON package info into a HashMap + let parsed: HashMap = + serde_json::from_str(raw_output).map_err(|err| format!("Can't parse JSON: {err}"))?; + + let mut lines = vec![]; + for (name_string, package) in parsed.into_iter() { + // `name_string` is, for example, "legacyPackages.x86_64-linux.auctex" + // Keep everything after the second '.' to get the package "name". + // This is different from package.pname, which contains the name + // of the executable, which can be different from the package name. + let name_vec: Vec<&str> = name_string.splitn(3, '.').collect(); + let name = name_vec.get(2).ok_or("Can't get package name from JSON.")?; + lines.push(format!( + "{} {} {}", + name, package.version, package.description + )); + } + lines.sort(); + Ok(lines.join("\n")) +} + +/// Fetch new package info and write to cache file +pub fn refresh(experimental: bool, file_path: &PathBuf, quiet: bool) -> Result<(), Box> { + let flakes_enabled = check_flakes_enabled()?; + // Print helpful warnings if there is a feature mismatch + // between the system setup and the `nps` usage. + check_for_features(flakes_enabled, experimental, quiet)?; + + let cache_start_message = "Refreshing cache. This might take a while..."; + log::info!("{}", cache_start_message); + message(cache_start_message, quiet)?; + + let cache_folder = file_path + .parent() + .ok_or("Can't get cache folder from file path")?; + log::trace!("file_path: {:?}", file_path); + + let output = match experimental { + true => Command::new("nix") + .arg("--extra-experimental-features") + .arg("nix-command flakes") + .arg("search") + .arg("nixpkgs") + .arg("^") + .arg("--json") + .output() + .map_err(|err| format!("`nix search` failed: {err}"))?, + false => Command::new("nix-env") + .arg("-qaP") + .arg("--description") + .output() + .map_err(|err| format!("`nix-env` failed: {err}"))?, + }; + + log::trace!("finished cli command"); + + let (stdout, stderr) = ( + str::from_utf8(&output.stdout) + .map_err(|err| format!("Can't convert stdout to UTF8: {err}"))?, + str::from_utf8(&output.stderr) + .map_err(|err| format!("Can't convert stderr to UTF8: {err}"))?, + ); + + log::trace!("stdout.len(): {}", stdout.len()); + log::trace!("stderr.len(): {}", stderr.len()); + + // Report warnings if stderr looks bad + let mut first_error = true; + for line in stderr.lines() { + // ignore standard logging to stderr + if !line.starts_with("evaluating") { + if first_error { + log::warn!("These warnings were encountered during cache refresh (START)"); + first_error = false; + } + log::warn!("> {}", line); + } + } + if !first_error { + log::warn!("These warnings were encountered during cache refresh (END)"); + } + + // Throw error if cache is too small + if stdout.len() < 10_000 { + log::warn!("Cache seems too small:"); + log::warn!("> Query returned only {} lines.", stdout.len()); + if !flakes_enabled { + log::info!( + "> Did you set up your channels yet? See: https://nixos.wiki/wiki/Nix_channels" + ); + log::info!( + "> You can also set up your system for flakes instead. See: https://nixos.wiki/wiki/Flakes" + ); + } + log::info!("> Run with `-dddd` flag for even more information."); + return Err("Cache seems too small. Run with `-dd` flag for more information.".into()); + } + + let cache_content = match experimental { + true => parse_json_to_lines(stdout).map_err(|err| format!("Can't parse JSON: {err}"))?, + false => { + // Replace in every line the first two series of whitespaces with single spaces + let re = regex::RegexBuilder::new(r"^([^ ]+) +([^ ]+) +(.*)$") + .multi_line(true) + .build() + .unwrap(); + re.replace_all(stdout, "$1 $2 $3").to_string() + } + }; + + log::trace!("trying to create folder: {:?}", cache_folder); + // Create cache folder, if not exists + fs::create_dir_all(cache_folder).map_err(|err| format!("Can't create folder: {err}"))?; + log::trace!("folder created"); + + log::trace!("cache_folder: {:?}", cache_folder); + log::trace!("file_path: {:?}", &file_path); + + // Atomic Writing: Write first to a tmp file, then persist (move) it to destination + let tempfile = NamedTempFile::new_in(cache_folder) + .map_err(|err| format!("Can't create temp file: {err}"))?; + log::trace!("tempfile: {:?}", &tempfile); + log::trace!("trying to write tempfile"); + write!(&tempfile, "{}", cache_content) + .map_err(|err| format!("Can't write to temp file: {err}"))?; + log::trace!("tempfile written"); + + tempfile + .persist(file_path) + .map_err(|err| format!("Can't persist temp file: {err}"))?; + log::trace!("tempfile persisted"); + + let number_of_packages = cache_content.lines().count(); + let cache_file_path_string = format!("{:?}", file_path); + + let cache_end_message = + format!("Done. Cached info of {number_of_packages} packages in {cache_file_path_string}"); + log::info!("{}", &cache_end_message); + message(&cache_end_message, quiet)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + #[test] + fn test_parse_json_to_lines() -> Result<(), Box> { + init(); + + let json = "{\ + \"legacyPackages.x86_64-linux.mypackage\": {\ + \"description\":\"i describe\",\ + \"pname\":\"mypackagebinary\",\ + \"version\":\"old\"},\ + \ + \"legacyPackages.x86_64-linux.myotherpackage\": {\ + \"description\":\"i also describe\",\ + \"pname\":\"myotherpackagebinary\",\ + \"version\":\"fresh\"}\ + }"; + let desired_output = "\ + myotherpackage fresh i also describe\n\ + mypackage old i describe\ + "; + let parsed = parse_json_to_lines(json)?; + + assert_eq!(parsed, desired_output); + Ok(()) + } + + #[test] + fn test_check_flakes_enabled() { + init(); + + // Create a temporary directory for a nix.conf file + let tempdir = tempfile::TempDir::new().unwrap(); + let nix_conf_dir = &tempdir.path().join("nix"); + fs::create_dir_all(nix_conf_dir).unwrap(); + + let tempfile = NamedTempFile::new_in(&tempdir).unwrap(); + // Enable experimental features: "nix-command" and "flakes" + write!(&tempfile, "experimental-features = nix-command flakes").unwrap(); + tempfile.persist(nix_conf_dir.join("nix.conf")).unwrap(); + + temp_env::with_var("XDG_CONFIG_HOME", Some(&tempdir.path()), || { + assert!(check_flakes_enabled().unwrap()) + }); + + let tempfile = NamedTempFile::new_in(&tempdir).unwrap(); + // Disable all experimental features + write!(&tempfile, "experimental-features = ").unwrap(); + tempfile.persist(nix_conf_dir.join("nix.conf")).unwrap(); + + temp_env::with_var("XDG_CONFIG_HOME", Some(&tempdir.path()), || { + assert!(!check_flakes_enabled().unwrap()) + }); + } +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..1dd2a20 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,419 @@ +use clap::builder::styling::{AnsiColor, Effects, Styles}; +use clap::{ArgAction, Parser, ValueEnum}; + +use std::{path::PathBuf, str}; + +/// Find SEARCH_TERM in available nix packages and sort results by relevance. +/// +/// List up to three columns, the latter two being optional: +/// PACKAGE_NAME +/// +/// Matches are sorted by type. Show 'indirect' matches first, then 'direct' matches, and finally 'exact' matches. +/// +/// indirect fooSEARCH_TERMbar (SEARCH_TERM appears in any column) +/// direct SEARCH_TERMbar (PACKAGE_NAME starts with SEARCH_TERM) +/// exact SEARCH_TERM (PACKAGE_NAME is exactly SEARCH_TERM) +#[derive(Parser, Debug)] +#[command( + author, + version, + verbatim_doc_comment, + styles = styles(), + after_long_help = option_help_text(ENV_VAR_OPTIONS) +)] +pub struct Cli { + // default_value_t: value if flag (or env var) not present + // default_missing_value: value if flag is present, but has no value + // needs `.num_args(0..N)` and `.require_equals(true)` + // require_equals: force `--option=val` syntax + // env: read env var if flag not present + // takes_values: accept values from command line + // hide: hides the option from `-h`, those parameters are set via env vars + /// Highlight search matches in color + #[arg( + short, + long = "color", + require_equals = true, + visible_alias = "colour", + default_value_t = crate::DEFAULTS.color_mode, + default_missing_value = "clap::ColorChoice::Auto", + num_args = 0..=1, + env = "NIX_PACKAGE_SEARCH_COLOR_MODE" + )] + pub color: clap::ColorChoice, + + /// Choose columns to show + #[arg( + short = 'C', + long = "columns", + require_equals = true, + default_value_t = crate::DEFAULTS.columns, + default_missing_value = "ColumnsChoice::All", + value_enum, + num_args = 0..=1, + env = "NIX_PACKAGE_SEARCH_COLUMNS" + )] + pub columns: ColumnsChoice, + + /// Turn debugging information on + /// + /// Use up to four times for increased verbosity + #[arg( + short, + long, + action = ArgAction::Count + )] + pub debug: u8, + + /// Use experimental flakes + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.experimental, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_EXPERIMENTAL" + )] + pub experimental: bool, + + /// Flip the order of matches and sorting + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.flip, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_FLIP" + )] + pub flip: bool, + + /// Ignore case + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.ignore_case, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_IGNORE_CASE" + )] + pub ignore_case: bool, + + /// Multi line + /// + /// Print search matches on two lines, followed by a newline: + /// > PACKAGE_NAME PACKAGE_VERSION + /// > PACKAGE_DESCRIPTION + /// > + #[arg( + short, + long, + verbatim_doc_comment, + require_equals = true, + default_value_t = crate::DEFAULTS.multi_line, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_MULTI_LINE" + )] + pub multi_line: bool, + + /// Suppress non-debug messages + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.quiet, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_QUIET" + )] + pub quiet: bool, + + /// Refresh package cache and exit + #[arg(short, long)] + pub refresh: bool, + + /// Separate match types with a newline + /// + /// Only applicable when --multi-line=false + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.print_separator, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_PRINT_SEPARATOR" + )] + pub separate: bool, + + /// Search for any SEARCH_TERM in package names, description, or versions + #[arg( + required_unless_present_any = ["refresh"] + )] + pub search_term: Option, + + // hidden vars, to be set via env vars + /// Cache lives here + #[arg( + long, + require_equals = true, + hide = true, + default_value = home::home_dir() + .unwrap() // We previously made sure this works. + .join(crate::DEFAULTS.cache_folder) + .display() + .to_string(), + value_parser = clap::value_parser!(PathBuf), + env = "NIX_PACKAGE_SEARCH_CACHE_FOLDER_ABSOLUTE_PATH" + )] + pub cache_folder: PathBuf, + + /// Color of EXACT matches, match SEARCH_TERM + #[arg( + long, + require_equals = true, + hide = true, + default_value_t = crate::DEFAULTS.exact_color, + value_enum, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_EXACT_COLOR" + )] + pub exact_color: Colors, + + /// Color of DIRECT matches, match SEARCH_TERMbar + #[arg( + long, + require_equals = true, + hide = true, + default_value_t = crate::DEFAULTS.direct_color, + value_enum, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_DIRECT_COLOR" + )] + pub direct_color: Colors, + + /// Color of DIRECT matches, match fooSEARCH_TERMbar (or match other columns) + #[arg( + long, + require_equals = true, + hide = true, + default_value_t = crate::DEFAULTS.indirect_color, + value_enum, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_INDIRECT_COLOR" + )] + pub indirect_color: Colors, +} + +/// Help text for using environment variables for configuration. +/// +/// Contains template items that still need to be replaced. +static ENV_VAR_OPTIONS: &str = " +CONFIGURATION + +`nps` can be configured with environment variables. You can set these in +the configuration file of your shell, e.g. .bashrc/.zshrc + +NIX_PACKAGE_SEARCH_EXPERIMENTAL + Use the experimental 'nix search' command. + It pulls information from the nix flake registries instead of nix channels. + This is useful if no channels are in use, or channels are not updated + regularly. + [default: {DEFAULT_EXPERIMENTAL}] + [possible values: true, false] + +NIX_PACKAGE_SEARCH_FLIP + Flip the order of matches? By default most relevant matches appear below, + which is easier to read with long output. Flipping shows most relevant + matches on top. + [default: {DEFAULT_FLIP}] + [possible values: true, false] + +NIX_PACKAGE_SEARCH_CACHE_FOLDER_ABSOLUTE_PATH + Absolute path of the cache folder + [default: {DEFAULT_CACHE_FOLDER}] + [possible values: path] + +NIX_PACKAGE_SEARCH_COLUMNS + Choose columns to show: PACKAGE_NAME plus any of PACKAGE_VERSION or + PACKAGE_DESCRIPTION + [default: {DEFAULT_COLUMNS}] + [possible values: all, none, version, description] + +NIX_PACKAGE_SEARCH_EXACT_COLOR + Color of EXACT matches, match SEARCH_TERM in PACKAGE_NAME + [default: {DEFAULT_EXACT_COLOR}] + [possible values: black, blue, green, red, cyan, magenta, yellow, white] + +NIX_PACKAGE_SEARCH_DIRECT_COLOR + Color of DIRECT matches, match SEARCH_TERMbar in PACKAGE_NAME + [default: {DEFAULT_DIRECT_COLOR}] + [possible values: black, blue, green, red, cyan, magenta, yellow, white] + +NIX_PACKAGE_SEARCH_INDIRECT_COLOR + Color of INDIRECT matches, match fooSEARCH_TERMbar in any column + [default: {DEFAULT_INDIRECT_COLOR}] + [possible values: black, blue, green, red, cyan, magenta, yellow, white] + +NIX_PACKAGE_SEARCH_COLOR_MODE + Show search matches in color + auto: Only show color if stdout is in terminal, suppress if e.g. piped + [default: {DEFAULT_COLOR_MODE}] + [possible values: always, never, auto] + +NIX_PACKAGE_SEARCH_PRINT_SEPARATOR + Separate matches with a newline? + [default: {DEFAULT_PRINT_SEPARATOR}] + [possible values: true, false] + +NIX_PACKAGE_SEARCH_QUIET + Suppress non-debug messages? + [default: {DEFAULT_QUIET}] + [possible values: true, false] + +NIX_PACKAGE_SEARCH_IGNORE_CASE + Search ignore capitalization for the search? + [default: {DEFAULT_IGNORE_CASE}] + [possible values: true, false] + +NIX_PACKAGE_SEARCH_MULTI_LINE + Print search matches on multiple lines? + > PACKAGE_NAME PACKAGE_VERSION + > PACKAGE_DESCRIPTION + > + [default: {DEFAULT_MULTI_LINE}] + [possible values: true, false] +"; + +/// Supply Styles for colored help output. +fn styles() -> Styles { + Styles::styled() + .header(AnsiColor::Red.on_default() | Effects::BOLD) + .usage(AnsiColor::Red.on_default() | Effects::BOLD) + .literal(AnsiColor::Blue.on_default() | Effects::BOLD) + .placeholder(AnsiColor::Green.on_default()) +} + +/// Replace template items in long help text with default settings. +fn option_help_text(help_text: &str) -> String { + help_text + .replace( + "{DEFAULT_EXPERIMENTAL}", + &crate::DEFAULTS.experimental.to_string(), + ) + .replace( + "{DEFAULT_CACHE_FOLDER}", + &home::home_dir() + .unwrap() // We previously made sure this works. + .join(crate::DEFAULTS.cache_folder) + .display() + .to_string(), + ) + .replace("{DEFAULT_CACHE_FILE}", crate::DEFAULTS.cache_file) + .replace( + "{DEFAULT_EXPERIMENTAL_CACHE_FILE}", + crate::DEFAULTS.experimental_cache_file, + ) + .replace( + "{DEFAULT_COLOR_MODE}", + &crate::DEFAULTS.color_mode.to_string().to_lowercase(), + ) + .replace( + "{DEFAULT_COLUMNS}", + &format!("{:?}", crate::DEFAULTS.columns).to_lowercase(), + ) + .replace("{DEFAULT_FLIP}", &crate::DEFAULTS.flip.to_string()) + .replace( + "{DEFAULT_IGNORE_CASE}", + &crate::DEFAULTS.ignore_case.to_string(), + ) + .replace( + "{DEFAULT_MULTI_LINE}", + &crate::DEFAULTS.multi_line.to_string(), + ) + .replace( + "{DEFAULT_PRINT_SEPARATOR}", + &crate::DEFAULTS.print_separator.to_string(), + ) + .replace("{DEFAULT_QUIET}", &crate::DEFAULTS.quiet.to_string()) + .replace( + "{DEFAULT_EXACT_COLOR}", + &format!("{:?}", crate::DEFAULTS.exact_color).to_lowercase(), + ) + .replace( + "{DEFAULT_DIRECT_COLOR}", + &format!("{:?}", crate::DEFAULTS.direct_color).to_lowercase(), + ) + .replace( + "{DEFAULT_INDIRECT_COLOR}", + &format!("{:?}", crate::DEFAULTS.indirect_color).to_lowercase(), + ) +} + +/// Column name options +#[derive(Clone, Debug, ValueEnum)] +pub enum ColumnsChoice { + /// Show all columns + All, + /// Show only PACKAGE_NAME + None, + /// Also show PACKAGE_VERSION + Version, + /// Also show PACKAGE_DESCRIPTION + Description, +} + +/// Allowed values for coloring output. +#[derive(Debug, Clone, ValueEnum)] +pub enum Colors { + Black, + Blue, + Green, + Red, + Cyan, + Magenta, + Yellow, + White, +} + +/// Defines possible default settings. +pub struct Defaults<'a> { + pub cache_folder: &'a str, + pub cache_file: &'a str, + pub experimental: bool, + pub experimental_cache_file: &'a str, + pub color_mode: clap::ColorChoice, + pub columns: ColumnsChoice, + pub flip: bool, + pub ignore_case: bool, + pub multi_line: bool, + pub multi_line_indent: &'a str, + pub print_separator: bool, + pub quiet: bool, + + pub exact_color: Colors, + pub direct_color: Colors, + pub indirect_color: Colors, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_option_help_text() { + let replaced = option_help_text(ENV_VAR_OPTIONS); + + // Make sure we replace all possible placeholders with values + assert!(!replaced.contains("DEFAULT")); + } +} diff --git a/src/main.rs b/src/main.rs index 2c8b0af..56c1ee5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,876 +1,39 @@ -use clap::builder::styling::{AnsiColor, Effects, Styles}; -use clap::{ArgAction, Parser, ValueEnum}; +use clap::Parser; use env_logger::Builder; -use grep::{ - printer::{ColorSpecs, Standard, StandardBuilder, UserColorSpec}, - regex::RegexMatcherBuilder, - searcher::SearcherBuilder, -}; use log::LevelFilter; -use serde::Deserialize; use std::{ - collections::HashMap, - error::Error, fs, - io::{self, IsTerminal, Write}, + io::{self, IsTerminal}, path::PathBuf, - process::{Command, ExitCode, Stdio}, - str, + process::ExitCode, }; -use tempfile::NamedTempFile; -use termcolor::{Buffer, BufferWriter}; + +mod cache_refresh; +mod cli; +mod matches; /// Default settings for `nps`. /// /// They are also listed in the `-h`/`--help` commands. -const DEFAULTS: Defaults = Defaults { +const DEFAULTS: cli::Defaults = cli::Defaults { cache_folder: ".nix-package-search", // /home/USER/... cache_file: "nps.cache", // not user settable experimental: false, experimental_cache_file: "nps.experimental.cache", // not user settable color_mode: clap::ColorChoice::Auto, - columns: ColumnsChoice::All, + columns: cli::ColumnsChoice::All, flip: false, ignore_case: true, + multi_line: false, + multi_line_indent: " ", // not user settable print_separator: true, quiet: false, - exact_color: Colors::Magenta, - direct_color: Colors::Blue, - indirect_color: Colors::Green, + exact_color: cli::Colors::Magenta, + direct_color: cli::Colors::Blue, + indirect_color: cli::Colors::Green, }; -/// Find SEARCH_TERM in available nix packages and sort results by relevance. -/// -/// List up to three columns, the latter two being optional: -/// PACKAGE_NAME -/// -/// Matches are sorted by type. Show 'indirect' matches first, then 'direct' matches, and finally 'exact' matches. -/// -/// indirect fooSEARCH_TERMbar (SEARCH_TERM appears in any column) -/// direct SEARCH_TERMbar (PACKAGE_NAME starts with SEARCH_TERM) -/// exact SEARCH_TERM (PACKAGE_NAME is exactly SEARCH_TERM) -#[derive(Parser, Debug)] -#[command( - author, - version, - verbatim_doc_comment, - styles = styles(), - after_long_help = option_help_text(ENV_VAR_OPTIONS) -)] -struct Cli { - // default_value_t: value if flag (or env var) not present - // default_missing_value: value if flag is present, but has no value - // needs `.num_args(0..N)` and `.require_equals(true)` - // require_equals: force `--option=val` syntax - // env: read env var if flag not present - // takes_values: accept values from command line - // hide: hides the option from `-h`, those parameters are set via env vars - /// Highlight search matches in color - #[arg( - short, - long = "color", - require_equals = true, - visible_alias = "colour", - default_value_t = DEFAULTS.color_mode, - default_missing_value = "clap::ColorChoice::Auto", - num_args = 0..=1, - env = "NIX_PACKAGE_SEARCH_COLOR_MODE" - )] - color: clap::ColorChoice, - - /// Choose columns to show - #[arg( - short = 'C', - long = "columns", - require_equals = true, - default_value_t = DEFAULTS.columns, - default_missing_value = "ColumnsChoice::All", - value_enum, - num_args = 0..=1, - env = "NIX_PACKAGE_SEARCH_COLUMNS" - )] - columns: ColumnsChoice, - - /// Turn debugging information on - /// - /// Use up to four times for increased verbosity - #[arg( - short, - long, - action = ArgAction::Count - )] - debug: u8, - - /// Use experimental flakes - #[arg( - short, - long, - require_equals = true, - default_value_t = DEFAULTS.experimental, - default_missing_value = "true", - num_args = 0..=1, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_EXPERIMENTAL" - )] - experimental: bool, - - /// Flip the order of matches and sorting - #[arg( - short, - long, - require_equals = true, - default_value_t = DEFAULTS.flip, - default_missing_value = "true", - num_args = 0..=1, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_FLIP" - )] - flip: bool, - - /// Ignore case - #[arg( - short, - long, - require_equals = true, - default_value_t = DEFAULTS.ignore_case, - default_missing_value = "true", - num_args = 0..=1, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_IGNORE_CASE" - )] - ignore_case: bool, - - /// Suppress non-debug messages - #[arg( - short, - long, - require_equals = true, - default_value_t = DEFAULTS.quiet, - default_missing_value = "true", - num_args = 0..=1, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_QUIET" - )] - quiet: bool, - - /// Refresh package cache and exit - #[arg(short, long)] - refresh: bool, - - /// Separate match types with a newline - #[arg( - short, - long, - require_equals = true, - default_value_t = DEFAULTS.print_separator, - default_missing_value = "true", - num_args = 0..=1, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_PRINT_SEPARATOR" - )] - separate: bool, - - /// Search for any SEARCH_TERM in package names, description, or versions - #[arg( - required_unless_present_any = ["refresh"] - )] - search_term: Option, - - // hidden vars, to be set via env vars - /// Cache lives here - #[arg( - long, - require_equals = true, - hide = true, - default_value = home::home_dir() - .unwrap() // We previously made sure this works. - .join(DEFAULTS.cache_folder) - .display() - .to_string(), - value_parser = clap::value_parser!(PathBuf), - env = "NIX_PACKAGE_SEARCH_CACHE_FOLDER_ABSOLUTE_PATH" - )] - cache_folder: PathBuf, - - /// Color of EXACT matches, match SEARCH_TERM - #[arg( - long, - require_equals = true, - hide = true, - default_value_t = DEFAULTS.exact_color, - value_enum, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_EXACT_COLOR" - )] - exact_color: Colors, - - /// Color of DIRECT matches, match SEARCH_TERMbar - #[arg( - long, - require_equals = true, - hide = true, - default_value_t = DEFAULTS.direct_color, - value_enum, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_DIRECT_COLOR" - )] - direct_color: Colors, - - /// Color of DIRECT matches, match fooSEARCH_TERMbar (or match other columns) - #[arg( - long, - require_equals = true, - hide = true, - default_value_t = DEFAULTS.indirect_color, - value_enum, - action = ArgAction::Set, - env = "NIX_PACKAGE_SEARCH_INDIRECT_COLOR" - )] - indirect_color: Colors, -} - -/// Help text for using environment variables for configuration. -/// -/// Contains template items that still need to be replaced. -static ENV_VAR_OPTIONS: &str = " -CONFIGURATION - -`nps` can be configured with environment variables. You can set these in -the configuration file of your shell, e.g. .bashrc/.zshrc - -NIX_PACKAGE_SEARCH_EXPERIMENTAL - Use the experimental 'nix search' command. - It pulls information from the nix flake registries instead of nix channels. - This is useful if no channels are in use, or channels are not updated - regularly. - [default: {DEFAULT_EXPERIMENTAL}] - [possible values: true, false] - -NIX_PACKAGE_SEARCH_FLIP - Flip the order of matches? By default most relevant matches appear below, - which is easier to read with long output. Flipping shows most relevant - matches on top. - [default: {DEFAULT_FLIP}] - [possible values: true, false] - -NIX_PACKAGE_SEARCH_CACHE_FOLDER_ABSOLUTE_PATH - Absolute path of the cache folder - [default: {DEFAULT_CACHE_FOLDER}] - [possible values: path] - -NIX_PACKAGE_SEARCH_COLUMNS - Choose columns to show: PACKAGE_NAME plus any of PACKAGE_VERSION or - PACKAGE_DESCRIPTION - [default: {DEFAULT_COLUMNS}] - [possible values: all, none, version, description] - -NIX_PACKAGE_SEARCH_EXACT_COLOR - Color of EXACT matches, match SEARCH_TERM in PACKAGE_NAME - [default: {DEFAULT_EXACT_COLOR}] - [possible values: black, blue, green, red, cyan, magenta, yellow, white] - -NIX_PACKAGE_SEARCH_DIRECT_COLOR - Color of DIRECT matches, match SEARCH_TERMbar in PACKAGE_NAME - [default: {DEFAULT_DIRECT_COLOR}] - [possible values: black, blue, green, red, cyan, magenta, yellow, white] - -NIX_PACKAGE_SEARCH_INDIRECT_COLOR - Color of INDIRECT matches, match fooSEARCH_TERMbar in any column - [default: {DEFAULT_INDIRECT_COLOR}] - [possible values: black, blue, green, red, cyan, magenta, yellow, white] - -NIX_PACKAGE_SEARCH_COLOR_MODE - Show search matches in color - auto: Only show color if stdout is in terminal, suppress if e.g. piped - [default: {DEFAULT_COLOR_MODE}] - [possible values: always, never, auto] - -NIX_PACKAGE_SEARCH_PRINT_SEPARATOR - Separate matches with a newline? - [default: {DEFAULT_PRINT_SEPARATOR}] - [possible values: true, false] - -NIX_PACKAGE_SEARCH_QUIET - Suppress non-debug messages? - [default: {DEFAULT_QUIET}] - [possible values: true, false] - -NIX_PACKAGE_SEARCH_IGNORE_CASE - Search ignore capitalization for the search? - [default: {DEFAULT_IGNORE_CASE}] - [possible values: true, false] -"; - -/// Column name options -#[derive(Clone, Debug, ValueEnum)] -enum ColumnsChoice { - /// Show all columns - All, - /// Show only PACKAGE_NAME - None, - /// Also show PACKAGE_VERSION - Version, - /// Also show PACKAGE_DESCRIPTION - Description, -} - -/// Allowed values for coloring output. -#[derive(Debug, Clone, ValueEnum)] -enum Colors { - Black, - Blue, - Green, - Red, - Cyan, - Magenta, - Yellow, - White, -} - -/// Format to parse JSON package info into -#[derive(Debug, Deserialize)] -struct Package { - // we are not using `pname` - version: String, - description: String, -} - -/// Defines possible default settings. -struct Defaults<'a> { - cache_folder: &'a str, - cache_file: &'a str, - experimental: bool, - experimental_cache_file: &'a str, - color_mode: clap::ColorChoice, - columns: ColumnsChoice, - flip: bool, - ignore_case: bool, - print_separator: bool, - quiet: bool, - - exact_color: Colors, - direct_color: Colors, - indirect_color: Colors, -} - -/// Print messages if quiet==false -fn message(message_string: &str, quiet: bool) -> Result<(), Box> { - if !quiet { - writeln!(io::stdout(), "{}", message_string) - .map_err(|err| format!("Can't write to stdout: {err}"))?; - } - Ok(()) -} - -/// Supply Styles for colored help output. -fn styles() -> Styles { - Styles::styled() - .header(AnsiColor::Red.on_default() | Effects::BOLD) - .usage(AnsiColor::Red.on_default() | Effects::BOLD) - .literal(AnsiColor::Blue.on_default() | Effects::BOLD) - .placeholder(AnsiColor::Green.on_default()) -} - -/// Replace template items in long help text with default settings. -fn option_help_text(help_text: &str) -> String { - help_text - .replace("{DEFAULT_EXPERIMENTAL}", &DEFAULTS.experimental.to_string()) - .replace( - "{DEFAULT_CACHE_FOLDER}", - &home::home_dir() - .unwrap() // We previously made sure this works. - .join(DEFAULTS.cache_folder) - .display() - .to_string(), - ) - .replace("{DEFAULT_CACHE_FILE}", DEFAULTS.cache_file) - .replace( - "{DEFAULT_EXPERIMENTAL_CACHE_FILE}", - DEFAULTS.experimental_cache_file, - ) - .replace( - "{DEFAULT_COLOR_MODE}", - &DEFAULTS.color_mode.to_string().to_lowercase(), - ) - .replace( - "{DEFAULT_COLUMNS}", - &format!("{:?}", DEFAULTS.columns).to_lowercase(), - ) - .replace("{DEFAULT_FLIP}", &DEFAULTS.flip.to_string()) - .replace("{DEFAULT_IGNORE_CASE}", &DEFAULTS.ignore_case.to_string()) - .replace( - "{DEFAULT_PRINT_SEPARATOR}", - &DEFAULTS.print_separator.to_string(), - ) - .replace("{DEFAULT_QUIET}", &DEFAULTS.quiet.to_string()) - .replace( - "{DEFAULT_EXACT_COLOR}", - &format!("{:?}", DEFAULTS.exact_color).to_lowercase(), - ) - .replace( - "{DEFAULT_DIRECT_COLOR}", - &format!("{:?}", DEFAULTS.direct_color).to_lowercase(), - ) - .replace( - "{DEFAULT_INDIRECT_COLOR}", - &format!("{:?}", DEFAULTS.indirect_color).to_lowercase(), - ) -} - -/// Find matches from cache file -fn get_matches(cli: &Cli, content: &str) -> Result> { - let search_term = cli - .search_term - .as_ref() - .ok_or("Can't get search term as ref")?; - - // Matcher to find search term in rows - let matcher = RegexMatcherBuilder::new() - .case_insensitive(cli.ignore_case) - .build(search_term) - .map_err(|err| format!("Can't build regex: {err}"))?; - // Printer collects matching rows in a Vec - let mut printer = Standard::new_no_color(vec![]); - - // Execute search and collect output - SearcherBuilder::new() - .line_number(false) - .build() - .search_slice(&matcher, content.as_bytes(), printer.sink(&matcher)) - .map_err(|err| format!("Can't build searcher: {err}"))?; - - // into_inner gives us back the underlying writer we provided to - // new_no_color, which is wrapped in a termcolor::NoColor. Thus, a second - // into_inner gives us back the actual buffer. - let output = String::from_utf8(printer.into_inner().into_inner()) - .map_err(|err| format!("Can't parse printer string: {err}"))?; - - Ok(output) -} - -/// Case converter for case-insensitive searches -fn convert_case(string: &str, ignore_case: bool) -> String { - match ignore_case { - true => string.to_lowercase(), - false => string.to_string(), - } -} - -type MatchVecs = (Vec, Vec, Vec); - -/// Sort matches into match types and pad the lines to aligned columns -fn sort_and_pad_matches(cli: &Cli, raw_matches: String) -> Result> { - let search_term = cli - .search_term - .as_ref() - .ok_or("Can't get search term as ref")?; - - let mut name_lengths: Vec = vec![]; - let mut version_lengths: Vec = vec![]; - - for line in raw_matches.lines() { - let split_line: Vec<&str> = line.splitn(3, ' ').collect(); - - // Try to get a split_line element: `.get()`, - // use &"" if missing: `.unwrap_or(&"")`, - // and append lengths `.len()` to *_lengths vectors. - #[allow(clippy::get_first)] - name_lengths.push(split_line.get(0).unwrap_or(&"").len()); - version_lengths.push(split_line.get(1).unwrap_or(&"").len()); - } - - // Minimum cell size will be the largest contained string - let name_padding = *name_lengths.iter().max().unwrap_or(&0); - let version_padding = *version_lengths.iter().max().unwrap_or(&0); - - let mut padded_matches_exact: Vec = vec![]; - let mut padded_matches_direct: Vec = vec![]; - let mut padded_matches_indirect: Vec = vec![]; - - for line in raw_matches.lines() { - let split_line: Vec<&str> = line.splitn(3, ' ').collect(); - - #[allow(clippy::get_first)] // suppress clippy warning for this block - let name = split_line.get(0).unwrap_or(&""); - let version = split_line.get(1).unwrap_or(&""); - let description = split_line.get(2).unwrap_or(&""); - - let assembled_line = match &cli.columns { - ColumnsChoice::All => format!( - "{:name_padding$} {:version_padding$} {}", - name, version, description - ), - ColumnsChoice::Version => format!("{:name_padding$} {}", name, version), - ColumnsChoice::Description => format!("{:name_padding$} {}", name, description), - ColumnsChoice::None => format!("{} ", name), - }; - - // Handle case-insensitive, if requested - let converted_search_term = &convert_case(search_term, cli.ignore_case); - let converted_name = &convert_case(name, cli.ignore_case); - - // Package names from channels are prepended with "nixos." or "nixpkgs." - match cli.experimental { - true => { - if converted_name == converted_search_term { - padded_matches_exact.push(assembled_line); - } else if converted_name.starts_with(converted_search_term) { - padded_matches_direct.push(assembled_line); - } else { - padded_matches_indirect.push(assembled_line); - } - } - false => { - if converted_name == &("nixos.".to_owned() + converted_search_term) - || converted_name == &("nixpkgs.".to_owned() + converted_search_term) - { - padded_matches_exact.push(assembled_line); - } else if converted_name.starts_with(&("nixos.".to_owned() + converted_search_term)) - || converted_name.starts_with(&("nixpkgs.".to_owned() + converted_search_term)) - { - padded_matches_direct.push(assembled_line); - } else { - padded_matches_indirect.push(assembled_line); - } - } - } - } - - Ok(( - padded_matches_exact, - padded_matches_direct, - padded_matches_indirect, - )) -} - -/// Color the search term in different match types -fn color_matches( - cli: &Cli, - sorted_padded_matches: MatchVecs, - color_choice: termcolor::ColorChoice, -) -> Result<[Buffer; 3], Box> { - let (mut padded_matches_exact, mut padded_matches_direct, mut padded_matches_indirect) = - sorted_padded_matches; - let search_term = cli - .search_term - .as_ref() - .ok_or("Can't get search term as ref")?; - - // Defining different colors for different match types - let exact_color: UserColorSpec = format!("match:fg:{:?}", &cli.exact_color).parse()?; - let direct_color: UserColorSpec = format!("match:fg:{:?}", &cli.direct_color).parse()?; - let indirect_color: UserColorSpec = format!("match:fg:{:?}", &cli.indirect_color).parse()?; - - // Font styles for match types - let exact_style: UserColorSpec = "match:style:bold".parse()?; - let direct_style: UserColorSpec = "match:style:bold".parse()?; - let indirect_style: UserColorSpec = "match:style:bold".parse()?; - - // Combining colors and styles to ColorSpecs - let exact_color_specs = ColorSpecs::new(&[exact_color, exact_style]); - let direct_color_specs = ColorSpecs::new(&[direct_color, direct_style]); - let indirect_color_specs = ColorSpecs::new(&[indirect_color, indirect_style]); - - // Create buffers to write colored output into - let bufwtr = BufferWriter::stdout(color_choice); - let mut exact_buffer = bufwtr.buffer(); - let mut direct_buffer = bufwtr.buffer(); - let mut indirect_buffer = bufwtr.buffer(); - - // Printers print to the above buffers - let mut exact_printer = StandardBuilder::new() - .color_specs(exact_color_specs) - .build(&mut exact_buffer); - let mut direct_printer = StandardBuilder::new() - .color_specs(direct_color_specs) - .build(&mut direct_buffer); - let mut indirect_printer = StandardBuilder::new() - .color_specs(indirect_color_specs) - .build(&mut indirect_buffer); - - // Matcher to color `search_term` - let matcher = RegexMatcherBuilder::new() - .case_insensitive(cli.ignore_case) - .build(search_term) - .map_err(|err| format!("Can't build regex: {err}"))?; - - // Matcher to find _everything_, so lines without matches are still printed. - // This can happen if certain columns are missing. - let matcher_all = RegexMatcherBuilder::new().build(".*")?; - - // Let's have the top results at the bottom by default - if !cli.flip { - padded_matches_exact.reverse(); - padded_matches_direct.reverse(); - padded_matches_indirect.reverse(); - } - - // Coloring and printing to buffers - SearcherBuilder::new() - .line_number(false) - .build() - .search_slice( - &matcher_all, - padded_matches_exact.join("\n").as_bytes(), - exact_printer.sink(&matcher), - ) - .map_err(|err| format!("Can't build searcher: {err}"))?; - SearcherBuilder::new() - .line_number(false) - .build() - .search_slice( - &matcher_all, - padded_matches_direct.join("\n").as_bytes(), - direct_printer.sink(&matcher), - ) - .map_err(|err| format!("Can't build searcher: {err}"))?; - SearcherBuilder::new() - .line_number(false) - .build() - .search_slice( - &matcher_all, - padded_matches_indirect.join("\n").as_bytes(), - indirect_printer.sink(&matcher), - ) - .map_err(|err| format!("Can't build searcher: {err}"))?; - - Ok([exact_buffer, direct_buffer, indirect_buffer]) -} - -/// Print matches to screen in correct ordering -fn print_matches(cli: &Cli, colored_matches: [Buffer; 3]) -> Result<(), Box> { - // Assemble match type string segments - let mut out: Vec = vec![]; - for buffer in colored_matches.into_iter() { - let content = String::from_utf8(buffer.into_inner()) - .map_err(|err| format!("Can't get string from buffer: {err}"))?; - if !content.is_empty() { - out.push(content); - } - } - - if !cli.flip { - out.reverse(); - } - - // Use newlines as separators, if requested - let separator = match cli.separate { - true => "\n".to_string(), - false => "".to_string(), - }; - // BufferWriter introduces a newline that we need to trim for some reason - writeln!(io::stdout(), "{}", &out.join(&separator).trim()) - .map_err(|err| format!("Can't write to stdout: {err}"))?; - - Ok(()) -} - -/// Parse package info from JSON to (NAME VERSION DESCRIPTION) lines -fn parse_json_to_lines(raw_output: &str) -> Result> { - // Load JSON package info into a HashMap - let parsed: HashMap = - serde_json::from_str(raw_output).map_err(|err| format!("Can't parse JSON: {err}"))?; - - let mut lines = vec![]; - for (name_string, package) in parsed.into_iter() { - // `name_string` is, for example, "legacyPackages.x86_64-linux.auctex" - // Keep everything after the second '.' to get the package "name". - // This is different from package.pname, which contains the name - // of the executable, which can be different from the package name. - let name_vec: Vec<&str> = name_string.splitn(3, '.').collect(); - let name = name_vec.get(2).ok_or("Can't get package name from JSON.")?; - lines.push(format!( - "{} {} {}", - name, package.version, package.description - )); - } - lines.sort(); - Ok(lines.join("\n")) -} - -/// Check if flakes are enabled -fn check_flakes_enabled() -> Result> { - let probe_for_flakes = Command::new("nix") - .arg("--extra-experimental-features") - .arg("nix-command") - .arg("config") - .arg("show") - .stdout(Stdio::piped()) - .spawn() - .map_err(|err| format!("Can't execute `nix` command: {err}"))?; - let find_experimental_features = Command::new("grep") - .arg("^experimental-features") - .stdin(Stdio::from(probe_for_flakes.stdout.unwrap())) - .stdout(Stdio::piped()) - .spawn() - .map_err(|err| format!("Can't execute `grep` command: {err}"))?; - let find_flakes = Command::new("grep") - .arg("flakes") - .stdin(Stdio::from(find_experimental_features.stdout.unwrap())) - .stdout(Stdio::piped()) - .status() - .map_err(|err| format!("Can't execute `grep` command: {err}"))?; - - Ok(find_flakes.success()) -} - -/// Check if requested `nps` features match system features -/// -/// Give helpful warnings if there is a mismatch. -fn check_for_features( - flakes_enabled: bool, - experimental: bool, - quiet: bool, -) -> Result<(), Box> { - if flakes_enabled && !experimental { - let flakes_messages = [ - "Feature mismatch:", - "> Your system seems to be based on flakes.", - "> You may want to use `nps -e=true ...` instead to enable querying flake-based packages.", - ]; - for flake_message in flakes_messages { - message(flake_message, quiet)?; - log::warn!("{}", flake_message); - } - } - if !flakes_enabled && experimental { - let channels_messages = [ - "Feature mismatch:", - "> Your system seems to be based on channels.", - "> You may want to use `nps -e=false ...` instead to query packages from channels.", - ]; - for channel_message in channels_messages { - message(channel_message, quiet)?; - log::warn!("{}", channel_message); - } - } - - Ok(()) -} - -/// Fetch new package info and write to cache file -fn refresh(experimental: bool, file_path: &PathBuf, quiet: bool) -> Result<(), Box> { - let flakes_enabled = check_flakes_enabled()?; - // Print helpful warnings if there is a feature mismatch - // between the system setup and the `nps` usage. - check_for_features(flakes_enabled, experimental, quiet)?; - - let cache_start_message = "Refreshing cache. This might take a while..."; - log::info!("{}", cache_start_message); - message(cache_start_message, quiet)?; - - let cache_folder = file_path - .parent() - .ok_or("Can't get cache folder from file path")?; - log::trace!("file_path: {:?}", file_path); - - let output = match experimental { - true => Command::new("nix") - .arg("--extra-experimental-features") - .arg("nix-command flakes") - .arg("search") - .arg("nixpkgs") - .arg("^") - .arg("--json") - .output() - .map_err(|err| format!("`nix search` failed: {err}"))?, - false => Command::new("nix-env") - .arg("-qaP") - .arg("--description") - .output() - .map_err(|err| format!("`nix-env` failed: {err}"))?, - }; - - log::trace!("finished cli command"); - - let (stdout, stderr) = ( - str::from_utf8(&output.stdout) - .map_err(|err| format!("Can't convert stdout to UTF8: {err}"))?, - str::from_utf8(&output.stderr) - .map_err(|err| format!("Can't convert stderr to UTF8: {err}"))?, - ); - - log::trace!("stdout.len(): {}", stdout.len()); - log::trace!("stderr.len(): {}", stderr.len()); - - // Report warnings if stderr looks bad - let mut first_error = true; - for line in stderr.lines() { - // ignore standard logging to stderr - if !line.starts_with("evaluating") { - if first_error { - log::warn!("These warnings were encountered during cache refresh (START)"); - first_error = false; - } - log::warn!("> {}", line); - } - } - if !first_error { - log::warn!("These warnings were encountered during cache refresh (END)"); - } - - // Throw error if cache is too small - if stdout.len() < 10_000 { - log::warn!("Cache seems too small:"); - log::warn!("> Query returned only {} lines.", stdout.len()); - if !flakes_enabled { - log::info!( - "> Did you set up your channels yet? See: https://nixos.wiki/wiki/Nix_channels" - ); - log::info!( - "> You can also set up your system for flakes instead. See: https://nixos.wiki/wiki/Flakes" - ); - } - log::info!("> Run with `-dddd` flag for even more information."); - return Err("Cache seems too small. Run with `-dd` flag for more information.".into()); - } - - let cache_content = match experimental { - true => parse_json_to_lines(stdout).map_err(|err| format!("Can't parse JSON: {err}"))?, - false => { - // Replace in every line the first two series of whitespaces with single spaces - let re = regex::RegexBuilder::new(r"^([^ ]+) +([^ ]+) +(.*)$") - .multi_line(true) - .build() - .unwrap(); - re.replace_all(stdout, "$1 $2 $3").to_string() - } - }; - - log::trace!("trying to create folder: {:?}", cache_folder); - // Create cache folder, if not exists - fs::create_dir_all(cache_folder).map_err(|err| format!("Can't create folder: {err}"))?; - log::trace!("folder created"); - - log::trace!("cache_folder: {:?}", cache_folder); - log::trace!("file_path: {:?}", &file_path); - - // Atomic Writing: Write first to a tmp file, then persist (move) it to destination - let tempfile = NamedTempFile::new_in(cache_folder) - .map_err(|err| format!("Can't create temp file: {err}"))?; - log::trace!("tempfile: {:?}", &tempfile); - log::trace!("trying to write tempfile"); - write!(&tempfile, "{}", cache_content) - .map_err(|err| format!("Can't write to temp file: {err}"))?; - log::trace!("tempfile written"); - - tempfile - .persist(file_path) - .map_err(|err| format!("Can't persist temp file: {err}"))?; - log::trace!("tempfile persisted"); - - let number_of_packages = cache_content.lines().count(); - let cache_file_path_string = format!("{:?}", file_path); - - let cache_end_message = - format!("Done. Cached info of {number_of_packages} packages in {cache_file_path_string}"); - log::info!("{}", &cache_end_message); - message(&cache_end_message, quiet)?; - - Ok(()) -} - fn main() -> ExitCode { // Get home dir errors out of the way, since clap can't propagate errors // from `derive`. @@ -880,7 +43,7 @@ fn main() -> ExitCode { log::error!("Can't find home dir."); return ExitCode::FAILURE; } - let cli = Cli::parse(); + let cli = cli::Cli::parse(); let log_level = match cli.debug { 0 => LevelFilter::Error, @@ -942,7 +105,7 @@ fn main() -> ExitCode { // Refresh cache with new info? if cli.refresh || !cache_file_exists { log::trace!("inside if"); - match refresh(cli.experimental, &file_path, cli.quiet) { + match cache_refresh::refresh(cli.experimental, &file_path, cli.quiet) { Ok(_) => { if cli.refresh { return ExitCode::SUCCESS; @@ -963,7 +126,7 @@ fn main() -> ExitCode { } }; - let raw_matches = match get_matches(&cli, &content) { + let raw_matches = match matches::get_matches(&cli, &content) { Ok(raw_matches) => raw_matches, Err(err) => { log::error!("Can't get matches: {err}"); @@ -974,15 +137,24 @@ fn main() -> ExitCode { return ExitCode::FAILURE; } - let sorted_padded_matches = match sort_and_pad_matches(&cli, raw_matches) { - Ok(sorted_padded_matches) => sorted_padded_matches, - Err(err) => { - log::error!("Can't sort matches: {err}"); - return ExitCode::FAILURE; - } - }; + let formatted_matches = + match matches::format_matches(&cli, DEFAULTS.multi_line_indent, raw_matches) { + Ok(formatted_matches) => formatted_matches, + Err(err) => { + log::error!("Can't sort matches: {err}"); + return ExitCode::FAILURE; + } + }; - let colored_matches = match color_matches(&cli, sorted_padded_matches, color_choice) { + let colored_matches = match matches::color_matches( + &cli.search_term, + &cli.exact_color, + &cli.direct_color, + &cli.indirect_color, + cli.ignore_case, + formatted_matches, + color_choice, + ) { Ok(colored_matches) => colored_matches, Err(err) => { log::error!("Can't color matches: {err}"); @@ -990,272 +162,12 @@ fn main() -> ExitCode { } }; - if let Err(err) = print_matches(&cli, colored_matches) { + if let Err(err) = + matches::print_matches(cli.flip, cli.separate, cli.multi_line, colored_matches) + { log::error!("Can't print matches: {err}"); return ExitCode::FAILURE; } ExitCode::SUCCESS } - -#[cfg(test)] -mod tests { - use super::*; - - fn init() { - let _ = env_logger::builder().is_test(true).try_init(); - } - - #[test] - fn test_get_matches() { - init(); - - let cli = Cli::try_parse_from(vec!["nps", "second"]).unwrap(); - let content = "\ - the first line\n\ - the second line\n\ - the third line\ - "; - let matches = get_matches(&cli, content).unwrap(); - - assert_eq!(matches, "the second line\n"); - } - - #[test] - fn test_convert_case() { - init(); - - let test_string = "abCDef"; - - assert_eq!(convert_case(test_string, false), "abCDef"); - assert_eq!(convert_case(test_string, true), "abcdef"); - } - - #[test] - fn test_sort_and_pad_matches() { - init(); - - let cli_all_columns = Cli::try_parse_from(vec!["nps", "-e=true", "mypackage"]).unwrap(); - let cli_no_other_columns = - Cli::try_parse_from(vec!["nps", "-e=true", "-C=none", "mypackage"]).unwrap(); - let cli_version_column = - Cli::try_parse_from(vec!["nps", "-e=true", "-C=version", "mypackage"]).unwrap(); - let cli_description_column = - Cli::try_parse_from(vec!["nps", "-e=true", "-C=description", "mypackage"]).unwrap(); - let matches = "\ - mypackage v1 my package description\n\ - myotherpackage v2 has description as well\n\ - mypackage_extension v3 words words\n\ - mypackage_extension_2 v4 words words w0rds\n\ - mylastpackage v5.0.0 is not mypackage\ - " - .to_string(); - - let exact_matches_all_columns = "\ - mypackage v1 my package description\ - "; - let direct_matches_all_columns = "\ - mypackage_extension v3 words words\n\ - mypackage_extension_2 v4 words words w0rds\ - "; - let indirect_matches_all_columns = "\ - myotherpackage v2 has description as well\n\ - mylastpackage v5.0.0 is not mypackage\ - "; - - let exact_matches_no_other_columns = "\ - mypackage \ - "; - let direct_matches_no_other_columns = "\ - mypackage_extension \n\ - mypackage_extension_2 \ - "; - let indirect_matches_no_other_columns = "\ - myotherpackage \n\ - mylastpackage \ - "; - - let exact_matches_version_column = "\ - mypackage v1\ - "; - let direct_matches_version_column = "\ - mypackage_extension v3\n\ - mypackage_extension_2 v4\ - "; - let indirect_matches_version_column = "\ - myotherpackage v2\n\ - mylastpackage v5.0.0\ - "; - - let exact_matches_description_column = "\ - mypackage my package description\ - "; - let direct_matches_description_column = "\ - mypackage_extension words words\n\ - mypackage_extension_2 words words w0rds\ - "; - let indirect_matches_description_column = "\ - myotherpackage has description as well\n\ - mylastpackage is not mypackage\ - "; - - let sorted_and_padded_all_columns = - sort_and_pad_matches(&cli_all_columns, matches.clone()).unwrap(); - let sorted_and_padded_no_other_columns = - sort_and_pad_matches(&cli_no_other_columns, matches.clone()).unwrap(); - let sorted_and_padded_version_column = - sort_and_pad_matches(&cli_version_column, matches.clone()).unwrap(); - let sorted_and_padded_description_column = - sort_and_pad_matches(&cli_description_column, matches).unwrap(); - - assert_eq!( - exact_matches_all_columns, - sorted_and_padded_all_columns.0.join("\n") - ); - assert_eq!( - direct_matches_all_columns, - sorted_and_padded_all_columns.1.join("\n") - ); - assert_eq!( - indirect_matches_all_columns, - sorted_and_padded_all_columns.2.join("\n") - ); - - assert_eq!( - exact_matches_no_other_columns, - sorted_and_padded_no_other_columns.0.join("\n") - ); - assert_eq!( - direct_matches_no_other_columns, - sorted_and_padded_no_other_columns.1.join("\n") - ); - assert_eq!( - indirect_matches_no_other_columns, - sorted_and_padded_no_other_columns.2.join("\n") - ); - - assert_eq!( - exact_matches_version_column, - sorted_and_padded_version_column.0.join("\n") - ); - assert_eq!( - direct_matches_version_column, - sorted_and_padded_version_column.1.join("\n") - ); - assert_eq!( - indirect_matches_version_column, - sorted_and_padded_version_column.2.join("\n") - ); - - assert_eq!( - exact_matches_description_column, - sorted_and_padded_description_column.0.join("\n") - ); - assert_eq!( - direct_matches_description_column, - sorted_and_padded_description_column.1.join("\n") - ); - assert_eq!( - indirect_matches_description_column, - sorted_and_padded_description_column.2.join("\n") - ); - } - - #[test] - fn test_parse_json_to_lines() -> Result<(), Box> { - init(); - - let json = "{\ - \"legacyPackages.x86_64-linux.mypackage\": {\ - \"description\":\"i describe\",\ - \"pname\":\"mypackagebinary\",\ - \"version\":\"old\"},\ - \ - \"legacyPackages.x86_64-linux.myotherpackage\": {\ - \"description\":\"i also describe\",\ - \"pname\":\"myotherpackagebinary\",\ - \"version\":\"fresh\"}\ - }"; - let desired_output = "\ - myotherpackage fresh i also describe\n\ - mypackage old i describe\ - "; - let parsed = parse_json_to_lines(json)?; - - assert_eq!(parsed, desired_output); - Ok(()) - } - - #[test] - fn test_color_matches() { - init(); - - let cli = Cli::try_parse_from(vec!["nps", "-e=true", "mypackage"]).unwrap(); - let exact_matches = vec!["mypackage v1 my package description".to_string()]; - let direct_matches = vec![ - "mypackage_extension v1 my package description".to_string(), - "mypackage_extension_2 v1.0.1 my package description".to_string(), - ]; - let indirect_matches = vec![ - "mylastpackage v5.0.0 is not mypackage".to_string(), - "mylastpackage_2 v1 is not mypackage either".to_string(), - ]; - - let expect_color = [ - "\u{1b}[0m\u{1b}[1m\u{1b}[35mmypackage\u{1b}[0m v1 my package description\n", - "\u{1b}[0m\u{1b}[1m\u{1b}[34mmypackage\u{1b}[0m_extension_2 v1.0.1 my package description\n\ - \u{1b}[0m\u{1b}[1m\u{1b}[34mmypackage\u{1b}[0m_extension v1 my package description\n", - "mylastpackage_2 v1 is not \u{1b}[0m\u{1b}[1m\u{1b}[32mmypackage\u{1b}[0m either\n\ - mylastpackage v5.0.0 is not \u{1b}[0m\u{1b}[1m\u{1b}[32mmypackage\u{1b}[0m\n", - ]; - let expect_no_color = [ - "mypackage v1 my package description\n", - "mypackage_extension_2 v1.0.1 my package description\n\ - mypackage_extension v1 my package description\n", - "mylastpackage_2 v1 is not mypackage either\n\ - mylastpackage v5.0.0 is not mypackage\n", - ]; - - let matches = (exact_matches, direct_matches, indirect_matches); - - let colored_matches_color = - color_matches(&cli, matches.clone(), termcolor::ColorChoice::Always).unwrap(); - let colored_matches_no_color = - color_matches(&cli, matches, termcolor::ColorChoice::Never).unwrap(); - - for (expect, output) in std::iter::zip( - [expect_color, expect_no_color].concat(), - [colored_matches_color, colored_matches_no_color].concat(), - ) { - assert_eq!(expect, String::from_utf8(output.into_inner()).unwrap()); - } - } - - #[test] - fn test_check_flakes_enabled() { - init(); - - // Create a temporary directory for a nix.conf file - let tempdir = tempfile::TempDir::new().unwrap(); - let nix_conf_dir = &tempdir.path().join("nix"); - fs::create_dir_all(nix_conf_dir).unwrap(); - - let tempfile = NamedTempFile::new_in(&tempdir).unwrap(); - // Enable experimental features: "nix-command" and "flakes" - write!(&tempfile, "experimental-features = nix-command flakes").unwrap(); - tempfile.persist(nix_conf_dir.join("nix.conf")).unwrap(); - - temp_env::with_var("XDG_CONFIG_HOME", Some(&tempdir.path()), || { - assert!(check_flakes_enabled().unwrap()) - }); - - let tempfile = NamedTempFile::new_in(&tempdir).unwrap(); - // Disable all experimental features - write!(&tempfile, "experimental-features = ").unwrap(); - tempfile.persist(nix_conf_dir.join("nix.conf")).unwrap(); - - temp_env::with_var("XDG_CONFIG_HOME", Some(&tempdir.path()), || { - assert!(!check_flakes_enabled().unwrap()) - }); - } -} diff --git a/src/matches.rs b/src/matches.rs new file mode 100644 index 0000000..6c3fce0 --- /dev/null +++ b/src/matches.rs @@ -0,0 +1,717 @@ +use grep::{ + printer::{ColorSpecs, Standard, StandardBuilder, UserColorSpec}, + regex::RegexMatcherBuilder, + searcher::SearcherBuilder, +}; + +use std::{ + error::Error, + io::{self, Write}, + str, +}; +use termcolor::{Buffer, BufferWriter}; + +use crate::cli; + +type MatchVecs = (Vec, Vec, Vec); + +/// Case converter for case-insensitive searches +fn convert_case(string: &str, ignore_case: bool) -> String { + match ignore_case { + true => string.to_lowercase(), + false => string.to_string(), + } +} + +/// Find matches from cache file +pub fn get_matches(cli: &cli::Cli, content: &str) -> Result> { + let search_term = cli + .search_term + .as_ref() + .ok_or("Can't get search term as ref")?; + + // Matcher to find search term in rows + let matcher = RegexMatcherBuilder::new() + .case_insensitive(cli.ignore_case) + .build(search_term) + .map_err(|err| format!("Can't build regex: {err}"))?; + // Printer collects matching rows in a Vec + let mut printer = Standard::new_no_color(vec![]); + + // Execute search and collect output + SearcherBuilder::new() + .line_number(false) + .build() + .search_slice(&matcher, content.as_bytes(), printer.sink(&matcher)) + .map_err(|err| format!("Can't build searcher: {err}"))?; + + // into_inner gives us back the underlying writer we provided to + // new_no_color, which is wrapped in a termcolor::NoColor. Thus, a second + // into_inner gives us back the actual buffer. + let output = String::from_utf8(printer.into_inner().into_inner()) + .map_err(|err| format!("Can't parse printer string: {err}"))?; + + Ok(output) +} + +/// Sort matches into match types and pad the lines to aligned columns +pub fn format_matches( + cli: &cli::Cli, + indent: &str, + raw_matches: String, +) -> Result> { + let search_term = cli + .search_term + .as_ref() + .ok_or("Can't get search term as ref")?; + + let mut name_padding = 0; + let mut version_padding = 0; + + if !cli.multi_line { + let mut name_lengths: Vec = vec![]; + let mut version_lengths: Vec = vec![]; + + for line in raw_matches.lines() { + let split_line: Vec<&str> = line.splitn(3, ' ').collect(); + + // Try to get a split_line element: `.get()`, + // use &"" if missing: `.unwrap_or(&"")`, + // and append lengths `.len()` to *_lengths vectors. + #[allow(clippy::get_first)] + name_lengths.push(split_line.get(0).unwrap_or(&"").len()); + version_lengths.push(split_line.get(1).unwrap_or(&"").len()); + } + + // Minimum cell size will be the largest contained string + name_padding = *name_lengths.iter().max().unwrap_or(&0); + version_padding = *version_lengths.iter().max().unwrap_or(&0); + } + + let mut formatted_matches_exact: Vec = vec![]; + let mut formatted_matches_direct: Vec = vec![]; + let mut formatted_matches_indirect: Vec = vec![]; + + for line in raw_matches.lines() { + let split_line: Vec<&str> = line.splitn(3, ' ').collect(); + + #[allow(clippy::get_first)] // suppress clippy warning for this block + let name = split_line.get(0).unwrap_or(&""); + let version = split_line.get(1).unwrap_or(&""); + let description = split_line.get(2).unwrap_or(&""); + + let assembled_line = match (&cli.columns, cli.multi_line, description) { + (cli::ColumnsChoice::All, true, &"") => format!("{} {}\n", name, version), + (cli::ColumnsChoice::All, true, _) => { + format!("{} {}\n", name, version) + indent + &format!("{}\n", description) + } + (cli::ColumnsChoice::All, false, &"") => { + format!("{:name_padding$} {:version_padding$}", name, version) + } + (cli::ColumnsChoice::All, false, _) => format!( + "{:name_padding$} {:version_padding$} {}", + name, version, description + ), + + (cli::ColumnsChoice::Version, true, _) => format!("{} {}\n", name, version), + (cli::ColumnsChoice::Version, false, _) => { + format!("{:name_padding$} {}", name, version) + } + + (cli::ColumnsChoice::Description, true, &"") => format!("{}\n", name), + (cli::ColumnsChoice::Description, true, _) => { + format!("{}\n", name) + indent + &format!("{}\n", description) + } + (cli::ColumnsChoice::Description, false, &"") => format!("{:name_padding$}", name), + (cli::ColumnsChoice::Description, false, _) => { + format!("{:name_padding$} {}", name, description) + } + + (cli::ColumnsChoice::None, true, _) => format!("{}\n", name), + (cli::ColumnsChoice::None, false, _) => name.to_string(), + }; + + // Handle case-insensitive, if requested + let converted_search_term = convert_case(search_term, cli.ignore_case); + let converted_name = convert_case(name, cli.ignore_case); + + // Package names from channels are prepended with "nixos." or "nixpkgs." + match cli.experimental { + true => { + if converted_name == converted_search_term { + formatted_matches_exact.push(assembled_line); + } else if converted_name.starts_with(&converted_search_term) { + formatted_matches_direct.push(assembled_line); + } else { + formatted_matches_indirect.push(assembled_line); + } + } + false => { + if converted_name == ("nixos.".to_owned() + &converted_search_term) + || converted_name == ("nixpkgs.".to_owned() + &converted_search_term) + { + formatted_matches_exact.push(assembled_line); + } else if converted_name + .starts_with(&("nixos.".to_owned() + &converted_search_term)) + || converted_name.starts_with(&("nixpkgs.".to_owned() + &converted_search_term)) + { + formatted_matches_direct.push(assembled_line); + } else { + formatted_matches_indirect.push(assembled_line); + } + } + } + } + + // Let's have the top results at the bottom by default + if !cli.flip { + formatted_matches_exact.reverse(); + formatted_matches_direct.reverse(); + formatted_matches_indirect.reverse(); + } + + Ok(( + formatted_matches_exact, + formatted_matches_direct, + formatted_matches_indirect, + )) +} + +/// Color the search term in different match types +pub fn color_matches( + maybe_search_term: &Option, + exact_color: &cli::Colors, + direct_color: &cli::Colors, + indirect_color: &cli::Colors, + ignore_case: bool, + formatted_matches: MatchVecs, + color_choice: termcolor::ColorChoice, +) -> Result<[Buffer; 3], Box> { + let (padded_matches_exact, padded_matches_direct, padded_matches_indirect) = formatted_matches; + let search_term = maybe_search_term + .as_ref() + .ok_or("Can't get search term as ref")?; + + // Defining different colors for different match types + let exact_color_user_spec: UserColorSpec = format!("match:fg:{:?}", exact_color).parse()?; + let direct_color_user_spec: UserColorSpec = format!("match:fg:{:?}", direct_color).parse()?; + let indirect_color_user_spec: UserColorSpec = + format!("match:fg:{:?}", indirect_color).parse()?; + + // Font styles for match types + let exact_style: UserColorSpec = "match:style:bold".parse()?; + let direct_style: UserColorSpec = "match:style:bold".parse()?; + let indirect_style: UserColorSpec = "match:style:bold".parse()?; + + // Combining colors and styles to ColorSpecs + let exact_color_specs = ColorSpecs::new(&[exact_color_user_spec, exact_style]); + let direct_color_specs = ColorSpecs::new(&[direct_color_user_spec, direct_style]); + let indirect_color_specs = ColorSpecs::new(&[indirect_color_user_spec, indirect_style]); + + // Create buffers to write colored output into + let bufwtr = BufferWriter::stdout(color_choice); + let mut exact_buffer = bufwtr.buffer(); + let mut direct_buffer = bufwtr.buffer(); + let mut indirect_buffer = bufwtr.buffer(); + + // Printers print to the above buffers + let mut exact_printer = StandardBuilder::new() + .color_specs(exact_color_specs) + .build(&mut exact_buffer); + let mut direct_printer = StandardBuilder::new() + .color_specs(direct_color_specs) + .build(&mut direct_buffer); + let mut indirect_printer = StandardBuilder::new() + .color_specs(indirect_color_specs) + .build(&mut indirect_buffer); + + // Matcher to color `search_term` + let matcher = RegexMatcherBuilder::new() + .case_insensitive(ignore_case) + .build(search_term) + .map_err(|err| format!("Can't build regex: {err}"))?; + + // Matcher to find _everything_, so lines without matches are still printed. + // This can happen if certain columns are missing. + let matcher_all = RegexMatcherBuilder::new().build(".*")?; + + // Coloring and printing to buffers + SearcherBuilder::new() + .line_number(false) + .build() + .search_slice( + &matcher_all, + padded_matches_exact.join("\n").as_bytes(), + exact_printer.sink(&matcher), + ) + .map_err(|err| format!("Can't build searcher: {err}"))?; + SearcherBuilder::new() + .line_number(false) + .build() + .search_slice( + &matcher_all, + padded_matches_direct.join("\n").as_bytes(), + direct_printer.sink(&matcher), + ) + .map_err(|err| format!("Can't build searcher: {err}"))?; + SearcherBuilder::new() + .line_number(false) + .build() + .search_slice( + &matcher_all, + padded_matches_indirect.join("\n").as_bytes(), + indirect_printer.sink(&matcher), + ) + .map_err(|err| format!("Can't build searcher: {err}"))?; + + Ok([exact_buffer, direct_buffer, indirect_buffer]) +} + +/// Print matches to screen in correct ordering +pub fn print_matches( + flip: bool, + separate: bool, + multi_line: bool, + colored_matches: [Buffer; 3], +) -> Result<(), Box> { + // Assemble match type string segments + let mut out: Vec = vec![]; + for buffer in colored_matches.into_iter() { + let content = String::from_utf8(buffer.into_inner()) + .map_err(|err| format!("Can't get string from buffer: {err}"))?; + if !content.is_empty() { + out.push(content); + } + } + + if !flip { + out.reverse(); + } + + // Use newlines as separators, if requested or for multi lines + let separator = match separate || multi_line { + true => "\n".to_string(), + false => "".to_string(), + }; + // BufferWriter introduces a newline that we need to trim for some reason + writeln!(io::stdout(), "{}", &out.join(&separator).trim()) + .map_err(|err| format!("Can't write to stdout: {err}"))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + #[test] + fn test_get_matches() { + init(); + + let cli = cli::Cli::try_parse_from(vec!["nps", "second"]).unwrap(); + let content = "\ + the first line\n\ + the second line\n\ + the third line\ + "; + let matches = get_matches(&cli, content).unwrap(); + + assert_eq!(matches, "the second line\n"); + } + + #[test] + fn test_convert_case() { + init(); + + let test_string = "abCDef"; + + assert_eq!(convert_case(test_string, false), "abCDef"); + assert_eq!(convert_case(test_string, true), "abcdef"); + } + + #[test] + fn test_format_matches() { + init(); + + let cli_all_columns = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "mypackage"]).unwrap(); + let cli_no_other_columns = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-C=none", "mypackage"]).unwrap(); + let cli_version_column = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-C=version", "mypackage"]).unwrap(); + let cli_description_column = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-C=description", "mypackage"]) + .unwrap(); + + let cli_all_columns_multi_line = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-m=true", "mypackage"]).unwrap(); + let cli_no_other_columns_multi_line = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-C=none", "-m=true", "mypackage"]) + .unwrap(); + let cli_version_column_multi_line = + cli::Cli::try_parse_from(vec!["nps", "-e=true", "-C=version", "-m=true", "mypackage"]) + .unwrap(); + let cli_description_column_multi_line = cli::Cli::try_parse_from(vec![ + "nps", + "-e=true", + "-C=description", + "-m=true", + "mypackage", + ]) + .unwrap(); + + let matches = "\ + mypackage v1 my package description\n\ + mypackage v7\n\ + myotherpackage v2 has description as well\n\ + mypackage_extension v3 words words\n\ + mypackage_extension_2 v4 words words w0rds\n\ + mypackage_extension_3 v8\n\ + mylastpackage v5.0.0 is not mypackage\n\ + no_description v6\ + " + .to_string(); + + // single line + let exact_matches_all_columns = "\ + mypackage v7 \n\ + mypackage v1 my package description\ + "; + let direct_matches_all_columns = "\ + mypackage_extension_3 v8 \n\ + mypackage_extension_2 v4 words words w0rds\n\ + mypackage_extension v3 words words\ + "; + let indirect_matches_all_columns = "\ + no_description v6 \n\ + mylastpackage v5.0.0 is not mypackage\n\ + myotherpackage v2 has description as well\ + "; + + let exact_matches_no_other_columns = "\ + mypackage\n\ + mypackage\ + "; + let direct_matches_no_other_columns = "\ + mypackage_extension_3\n\ + mypackage_extension_2\n\ + mypackage_extension\ + "; + let indirect_matches_no_other_columns = "\ + no_description\n\ + mylastpackage\n\ + myotherpackage\ + "; + + let exact_matches_version_column = "\ + mypackage v7\n\ + mypackage v1\ + "; + let direct_matches_version_column = "\ + mypackage_extension_3 v8\n\ + mypackage_extension_2 v4\n\ + mypackage_extension v3\ + "; + let indirect_matches_version_column = "\ + no_description v6\n\ + mylastpackage v5.0.0\n\ + myotherpackage v2\ + "; + + let exact_matches_description_column = "\ + mypackage \n\ + mypackage my package description\ + "; + let direct_matches_description_column = "\ + mypackage_extension_3\n\ + mypackage_extension_2 words words w0rds\n\ + mypackage_extension words words\ + "; + let indirect_matches_description_column = "\ + no_description \n\ + mylastpackage is not mypackage\n\ + myotherpackage has description as well\ + "; + + // multi-line + let exact_matches_all_columns_multi_line = "\ + mypackage v7\n\n\ + mypackage v1\n my package description\n\ + "; + let direct_matches_all_columns_multi_line = "\ + mypackage_extension_3 v8\n\n\ + mypackage_extension_2 v4\n words words w0rds\n\n\ + mypackage_extension v3\n words words\n\ + "; + let indirect_matches_all_columns_multi_line = "\ + no_description v6\n\n\ + mylastpackage v5.0.0\n is not mypackage\n\n\ + myotherpackage v2\n has description as well\n\ + "; + + let exact_matches_no_other_columns_multi_line = "\ + mypackage\n\n\ + mypackage\n\ + "; + let direct_matches_no_other_columns_multi_line = "\ + mypackage_extension_3\n\n\ + mypackage_extension_2\n\n\ + mypackage_extension\n\ + "; + let indirect_matches_no_other_columns_multi_line = "\ + no_description\n\n\ + mylastpackage\n\n\ + myotherpackage\n\ + "; + + let exact_matches_version_column_multi_line = "\ + mypackage v7\n\n\ + mypackage v1\n\ + "; + let direct_matches_version_column_multi_line = "\ + mypackage_extension_3 v8\n\n\ + mypackage_extension_2 v4\n\n\ + mypackage_extension v3\n\ + "; + let indirect_matches_version_column_multi_line = "\ + no_description v6\n\n\ + mylastpackage v5.0.0\n\n\ + myotherpackage v2\n\ + "; + + let exact_matches_description_column_multi_line = "\ + mypackage\n\n\ + mypackage\n my package description\n\ + "; + let direct_matches_description_column_multi_line = "\ + mypackage_extension_3\n\n\ + mypackage_extension_2\n words words w0rds\n\n\ + mypackage_extension\n words words\n\ + "; + let indirect_matches_description_column_multi_line = "\ + no_description\n\n\ + mylastpackage\n is not mypackage\n\n\ + myotherpackage\n has description as well\n\ + "; + + let sorted_and_padded_all_columns = format_matches( + &cli_all_columns, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_no_other_columns = format_matches( + &cli_no_other_columns, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_version_column = format_matches( + &cli_version_column, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_description_column = format_matches( + &cli_description_column, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + + let sorted_and_padded_all_columns_multi_line = format_matches( + &cli_all_columns_multi_line, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_no_other_columns_multi_line = format_matches( + &cli_no_other_columns_multi_line, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_version_column_multi_line = format_matches( + &cli_version_column_multi_line, + crate::DEFAULTS.multi_line_indent, + matches.clone(), + ) + .unwrap(); + let sorted_and_padded_description_column_multi_line = format_matches( + &cli_description_column_multi_line, + crate::DEFAULTS.multi_line_indent, + matches, + ) + .unwrap(); + + assert_eq!( + exact_matches_all_columns, + sorted_and_padded_all_columns.0.join("\n") + ); + assert_eq!( + direct_matches_all_columns, + sorted_and_padded_all_columns.1.join("\n") + ); + assert_eq!( + indirect_matches_all_columns, + sorted_and_padded_all_columns.2.join("\n") + ); + + assert_eq!( + exact_matches_no_other_columns, + sorted_and_padded_no_other_columns.0.join("\n") + ); + assert_eq!( + direct_matches_no_other_columns, + sorted_and_padded_no_other_columns.1.join("\n") + ); + assert_eq!( + indirect_matches_no_other_columns, + sorted_and_padded_no_other_columns.2.join("\n") + ); + + assert_eq!( + exact_matches_version_column, + sorted_and_padded_version_column.0.join("\n") + ); + assert_eq!( + direct_matches_version_column, + sorted_and_padded_version_column.1.join("\n") + ); + assert_eq!( + indirect_matches_version_column, + sorted_and_padded_version_column.2.join("\n") + ); + + assert_eq!( + exact_matches_description_column, + sorted_and_padded_description_column.0.join("\n") + ); + assert_eq!( + direct_matches_description_column, + sorted_and_padded_description_column.1.join("\n") + ); + assert_eq!( + indirect_matches_description_column, + sorted_and_padded_description_column.2.join("\n") + ); + + assert_eq!( + exact_matches_all_columns_multi_line, + sorted_and_padded_all_columns_multi_line.0.join("\n") + ); + assert_eq!( + direct_matches_all_columns_multi_line, + sorted_and_padded_all_columns_multi_line.1.join("\n") + ); + assert_eq!( + indirect_matches_all_columns_multi_line, + sorted_and_padded_all_columns_multi_line.2.join("\n") + ); + + assert_eq!( + exact_matches_no_other_columns_multi_line, + sorted_and_padded_no_other_columns_multi_line.0.join("\n") + ); + assert_eq!( + direct_matches_no_other_columns_multi_line, + sorted_and_padded_no_other_columns_multi_line.1.join("\n") + ); + assert_eq!( + indirect_matches_no_other_columns_multi_line, + sorted_and_padded_no_other_columns_multi_line.2.join("\n") + ); + + assert_eq!( + exact_matches_version_column_multi_line, + sorted_and_padded_version_column_multi_line.0.join("\n") + ); + assert_eq!( + direct_matches_version_column_multi_line, + sorted_and_padded_version_column_multi_line.1.join("\n") + ); + assert_eq!( + indirect_matches_version_column_multi_line, + sorted_and_padded_version_column_multi_line.2.join("\n") + ); + + assert_eq!( + exact_matches_description_column_multi_line, + sorted_and_padded_description_column_multi_line.0.join("\n") + ); + assert_eq!( + direct_matches_description_column_multi_line, + sorted_and_padded_description_column_multi_line.1.join("\n") + ); + assert_eq!( + indirect_matches_description_column_multi_line, + sorted_and_padded_description_column_multi_line.2.join("\n") + ); + } + + #[test] + fn test_color_matches() { + init(); + + let cli = cli::Cli::try_parse_from(vec!["nps", "-e=true", "mypackage"]).unwrap(); + let exact_matches = vec!["mypackage v1 my package description".to_string()]; + let direct_matches = vec![ + "mypackage_extension v1 my package description".to_string(), + "mypackage_extension_2 v1.0.1 my package description".to_string(), + ]; + let indirect_matches = vec![ + "mylastpackage v5.0.0 is not mypackage".to_string(), + "mylastpackage_2 v1 is not mypackage either".to_string(), + ]; + + let expect_color = [ + "\u{1b}[0m\u{1b}[1m\u{1b}[35mmypackage\u{1b}[0m v1 my package description\n", + "\u{1b}[0m\u{1b}[1m\u{1b}[34mmypackage\u{1b}[0m_extension v1 my package description\n\ + \u{1b}[0m\u{1b}[1m\u{1b}[34mmypackage\u{1b}[0m_extension_2 v1.0.1 my package description\n", + "mylastpackage v5.0.0 is not \u{1b}[0m\u{1b}[1m\u{1b}[32mmypackage\u{1b}[0m\n\ + mylastpackage_2 v1 is not \u{1b}[0m\u{1b}[1m\u{1b}[32mmypackage\u{1b}[0m either\n", + ]; + let expect_no_color = [ + "mypackage v1 my package description\n", + "mypackage_extension v1 my package description\n\ + mypackage_extension_2 v1.0.1 my package description\n", + "mylastpackage v5.0.0 is not mypackage\n\ + mylastpackage_2 v1 is not mypackage either\n", + ]; + + let matches = (exact_matches, direct_matches, indirect_matches); + + let colored_matches_color = color_matches( + &cli.search_term, + &cli.exact_color, + &cli.direct_color, + &cli.indirect_color, + cli.ignore_case, + matches.clone(), + termcolor::ColorChoice::Always, + ) + .unwrap(); + let colored_matches_no_color = color_matches( + &cli.search_term, + &cli.exact_color, + &cli.direct_color, + &cli.indirect_color, + cli.ignore_case, + matches, + termcolor::ColorChoice::Never, + ) + .unwrap(); + + for (expect, output) in std::iter::zip( + [expect_color, expect_no_color].concat(), + [colored_matches_color, colored_matches_no_color].concat(), + ) { + assert_eq!(expect, String::from_utf8(output.into_inner()).unwrap()); + } + } +} diff --git a/tests/integration.rs b/tests/integration.rs index 0bf04ed..51b047c 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -69,7 +69,7 @@ MatchMyDescription a.b.c MyTestPackageName appears in my description mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName MyTestPackageName3 1.2.1 More test package description -MyTestPackageName2 1.0.1 +MyTestPackageName2 1.0.1 MyTestPackageName1 1.1.0 Another test package description MyTestPackageName 1.0.0 Test package description @@ -97,7 +97,7 @@ MatchMyDescription a.b.c MyTestPackageName appears in my description mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName MyTestPackageName3 1.2.1 More test package description -MyTestPackageName2 1.0.1 +MyTestPackageName2 1.0.1 MyTestPackageName1 1.1.0 Another test package description MyTestPackageName 1.0.0 Test package description @@ -121,7 +121,7 @@ fn experimental_output_flip_by_command_line_no_equals() { let desired_output = "MyTestPackageName 1.0.0 Test package description MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 +MyTestPackageName2 1.0.1 MyTestPackageName3 1.2.1 More test package description mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName @@ -149,7 +149,7 @@ fn experimental_output_flip_by_command_line_equals() { let desired_output = "MyTestPackageName 1.0.0 Test package description MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 +MyTestPackageName2 1.0.1 MyTestPackageName3 1.2.1 More test package description mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName @@ -177,7 +177,7 @@ fn experimental_output_flip_by_env_var() { let desired_output = "MyTestPackageName 1.0.0 Test package description MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 +MyTestPackageName2 1.0.1 MyTestPackageName3 1.2.1 More test package description mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName @@ -213,10 +213,10 @@ nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName nixos.MyTestPackageName3 1.2.1 More test package description -nixos.MyTestPackageName2 1.0.1 +nixos.MyTestPackageName2 1.0.1 nixos.MyTestPackageName1 1.1.0 Another test package description nixpkgs.MyTestPackageName3 1.2.1 More test package description -nixpkgs.MyTestPackageName2 1.0.1 +nixpkgs.MyTestPackageName2 1.0.1 nixpkgs.MyTestPackageName1 1.1.0 Another test package description nixos.MyTestPackageName 1.0.0 Test package description @@ -250,11 +250,11 @@ nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description nixos.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName nixos.MyTestPackageName3 1.2.1 More test package description -nixos.MyTestPackageName2 1.0.1 +nixos.MyTestPackageName2 1.0.1 nixos.MyTestPackageName1 1.1.0 Another test package description nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName nixpkgs.MyTestPackageName3 1.2.1 More test package description -nixpkgs.MyTestPackageName2 1.0.1 +nixpkgs.MyTestPackageName2 1.0.1 nixpkgs.MyTestPackageName1 1.1.0 Another test package description nixos.MyTestPackageName 1.0.0 Test package description From b0c52e769fc0dc7689cfc1c4dbad379c275e1bf5 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 23 Mar 2025 22:32:30 +0100 Subject: [PATCH 05/15] add -t flag for truncation of long lines --- CHANGELOG.md | 3 +- Cargo.lock | 33 +++++++++++++++--- Cargo.toml | 3 ++ src/cli.rs | 24 ++++++++++++- src/main.rs | 1 + src/matches.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 147 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3687304..998e29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Flag `-m` for multi-line output -- Tests for new flag +- Flag `-t` for truncation of long lines +- Tests for new flags ### Fixed - Fixes to README.md and DEVELOPMENT.md diff --git a/Cargo.lock b/Cargo.lock index a67d0dd..5f31573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,9 +363,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" dependencies = [ "jiff-static", "log", @@ -376,9 +376,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" dependencies = [ "proc-macro2", "quote", @@ -451,6 +451,9 @@ dependencies = [ "temp-env", "tempfile", "termcolor", + "terminal_size", + "unicode-segmentation", + "unicode-width", ] [[package]] @@ -709,6 +712,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix", + "windows-sys", +] + [[package]] name = "termtree" version = "0.5.1" @@ -721,6 +734,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "utf8parse" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index de71edd..52f53aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" tempfile = "3.19.1" termcolor = "1.4.1" +terminal_size = "0.4.2" +unicode-segmentation = "1.12.0" +unicode-width = "0.2.0" [dev-dependencies] assert_cmd = "2.0.16" diff --git a/src/cli.rs b/src/cli.rs index 1dd2a20..211213a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,5 @@ use clap::builder::styling::{AnsiColor, Effects, Styles}; use clap::{ArgAction, Parser, ValueEnum}; - use std::{path::PathBuf, str}; /// Find SEARCH_TERM in available nix packages and sort results by relevance. @@ -155,6 +154,21 @@ pub struct Cli { )] pub separate: bool, + /// Separate match types with a newline + /// + /// Only applicable when --multi-line=false + #[arg( + short, + long, + require_equals = true, + default_value_t = crate::DEFAULTS.truncate, + default_missing_value = "true", + num_args = 0..=1, + action = ArgAction::Set, + env = "NIX_PACKAGE_SEARCH_TRUNCATE" + )] + pub truncate: bool, + /// Search for any SEARCH_TERM in package names, description, or versions #[arg( required_unless_present_any = ["refresh"] @@ -292,6 +306,12 @@ NIX_PACKAGE_SEARCH_MULTI_LINE > [default: {DEFAULT_MULTI_LINE}] [possible values: true, false] + +NIX_PACKAGE_SEARCH_TRUNCATE + Truncate lines longer than terminal width? + > PACKAGE_NAME PACKAGE_VERSION PACKAGE_DESC… + [default: {DEFAULT_TRUNCATE}] + [possible values: true, false] "; /// Supply Styles for colored help output. @@ -340,6 +360,7 @@ fn option_help_text(help_text: &str) -> String { "{DEFAULT_MULTI_LINE}", &crate::DEFAULTS.multi_line.to_string(), ) + .replace("{DEFAULT_TRUNCATE}", &crate::DEFAULTS.truncate.to_string()) .replace( "{DEFAULT_PRINT_SEPARATOR}", &crate::DEFAULTS.print_separator.to_string(), @@ -399,6 +420,7 @@ pub struct Defaults<'a> { pub multi_line_indent: &'a str, pub print_separator: bool, pub quiet: bool, + pub truncate: bool, pub exact_color: Colors, pub direct_color: Colors, diff --git a/src/main.rs b/src/main.rs index 56c1ee5..c471d16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ const DEFAULTS: cli::Defaults = cli::Defaults { multi_line_indent: " ", // not user settable print_separator: true, quiet: false, + truncate: false, exact_color: cli::Colors::Magenta, direct_color: cli::Colors::Blue, diff --git a/src/matches.rs b/src/matches.rs index 6c3fce0..7d9d482 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -3,13 +3,15 @@ use grep::{ regex::RegexMatcherBuilder, searcher::SearcherBuilder, }; - use std::{ error::Error, - io::{self, Write}, + io::{self, IsTerminal, Write}, str, }; use termcolor::{Buffer, BufferWriter}; +use terminal_size::{terminal_size, Width}; +use unicode_segmentation::UnicodeSegmentation; +use unicode_width::UnicodeWidthStr; use crate::cli; @@ -54,6 +56,37 @@ pub fn get_matches(cli: &cli::Cli, content: &str) -> Result String { + let mut string_width = line.width(); // using UnicodeWidthStr + if string_width < terminal_width { + // line fits on screen + return line.to_string(); + } + // graphemes are letters/characters/symbols, see https://en.wikipedia.org/wiki/Grapheme + let mut graphemes = UnicodeSegmentation::graphemes(line, true).collect::>(); + // leaving 1 space for the ellipsis '…' + while string_width > terminal_width - 1 { + graphemes.pop(); + string_width = graphemes.clone().into_iter().collect::().width(); + } + format!("{}…", graphemes.into_iter().collect::()) +} + +/// Call `truncate` on all lines in a (potential multi-line) chunk +fn truncate(cli: &cli::Cli, lines: &str, terminal_width: usize) -> String { + let truncated = lines + .lines() + .map(|x| truncate_line(x, terminal_width)) + .collect::>() + .join("\n") + .to_string(); + match cli.multi_line { + true => truncated + "\n", + false => truncated, + } +} + /// Sort matches into match types and pad the lines to aligned columns pub fn format_matches( cli: &cli::Cli, @@ -163,6 +196,26 @@ pub fn format_matches( } } + // Only truncate if we are actually outputting to a terminal + if cli.truncate & io::stdout().is_terminal() { + let size = terminal_size(); + let (Width(terminal_width_u16), _) = size.ok_or("Can't get terminal size")?; + let terminal_width: usize = terminal_width_u16.into(); + + formatted_matches_exact = formatted_matches_exact + .iter() + .map(|x| truncate(cli, x, terminal_width)) + .collect(); + formatted_matches_direct = formatted_matches_direct + .iter() + .map(|x| truncate(cli, x, terminal_width)) + .collect(); + formatted_matches_indirect = formatted_matches_indirect + .iter() + .map(|x| truncate(cli, x, terminal_width)) + .collect(); + } + // Let's have the top results at the bottom by default if !cli.flip { formatted_matches_exact.reverse(); @@ -714,4 +767,38 @@ mod tests { assert_eq!(expect, String::from_utf8(output.into_inner()).unwrap()); } } + #[test] + fn test_truncate() { + init(); + + let cli = cli::Cli::try_parse_from(vec!["nps", "_"]).unwrap(); + let cli_multi_line = cli::Cli::try_parse_from(vec!["nps", "-m", "_"]).unwrap(); + + let width = 10; + let lines = "long ascii text"; + let multi_lines = "short\n\ + long ascii text\n\ + emoji 👨🏻‍💻👩🏻‍💻\n\ + em+1 👨🏻‍💻👩🏻‍💻\n\ + hanzi 打酱油\n\ + ha-1 打酱油"; + let expected = "long asci…"; + let expected_multi_line = "short\n\ + long asci…\n\ + emoji 👨🏻\u{200d}💻…\n\ + em+1 👨🏻\u{200d}💻…\n\ + hanzi 打…\n\ + ha-1 打酱…\n"; + let truncated = truncate(&cli, lines, width); + let truncated_multi_line = truncate(&cli_multi_line, multi_lines, width); + + // The characters might not render well in an editor. Print them + // in the terminal to see where it's truncated. + println!("{}\n", lines); + println!("{}\n", &truncated); + println!("{}", &expected); + + assert_eq!(truncated, expected); + assert_eq!(truncated_multi_line, expected_multi_line); + } } From 1192c046aa39b56d771afc7f287011b07054ab14 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Mon, 24 Mar 2025 19:21:37 +0100 Subject: [PATCH 06/15] add and improve tests --- tests/integration.rs | 222 ++++++++++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 89 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 51b047c..667493a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -63,17 +63,17 @@ fn too_much_debug() { fn experimental_output_case_sensitive() { init(); - let desired_output = - "MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -MatchMyDescription a.b.c MyTestPackageName appears in my description -mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName - -MyTestPackageName3 1.2.1 More test package description -MyTestPackageName2 1.0.1 -MyTestPackageName1 1.1.0 Another test package description - -MyTestPackageName 1.0.0 Test package description -"; + let desired_output = "\ + MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + \n\ + MyTestPackageName3 1.2.1 More test package description\n\ + MyTestPackageName2 1.0.1\n\ + MyTestPackageName1 1.1.0 Another test package description\n\ + \n\ + MyTestPackageName 1.0.0 Test package description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("-i=false") .arg("--cache-folder=tests/") @@ -91,17 +91,18 @@ MyTestPackageName 1.0.0 Test package description fn experimental_output() { init(); - let desired_output = "MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization -MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -MatchMyDescription a.b.c MyTestPackageName appears in my description - -mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -MyTestPackageName3 1.2.1 More test package description -MyTestPackageName2 1.0.1 -MyTestPackageName1 1.1.0 Another test package description - -MyTestPackageName 1.0.0 Test package description -"; + let desired_output = "\ + MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization\n\ + MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + \n\ + mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + MyTestPackageName3 1.2.1 More test package description\n\ + MyTestPackageName2 1.0.1\n\ + MyTestPackageName1 1.1.0 Another test package description\n\ + \n\ + MyTestPackageName 1.0.0 Test package description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("--cache-folder=tests/") .arg("--experimental=true") @@ -118,16 +119,17 @@ MyTestPackageName 1.0.0 Test package description fn experimental_output_flip_by_command_line_no_equals() { init(); - let desired_output = "MyTestPackageName 1.0.0 Test package description - -MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 -MyTestPackageName3 1.2.1 More test package description - -mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -MatchMyDescription a.b.c MyTestPackageName appears in my description -MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -"; + let desired_output = "\ + MyTestPackageName 1.0.0 Test package description\n\ + \n\ + MyTestPackageName1 1.1.0 Another test package description\n\ + MyTestPackageName2 1.0.1\n\ + MyTestPackageName3 1.2.1 More test package description\n\ + \n\ + mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("-i=false") .arg("-f") @@ -146,16 +148,17 @@ MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my descriptio fn experimental_output_flip_by_command_line_equals() { init(); - let desired_output = "MyTestPackageName 1.0.0 Test package description - -MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 -MyTestPackageName3 1.2.1 More test package description - -mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -MatchMyDescription a.b.c MyTestPackageName appears in my description -MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -"; + let desired_output = "\ + MyTestPackageName 1.0.0 Test package description\n\ + \n\ + MyTestPackageName1 1.1.0 Another test package description\n\ + MyTestPackageName2 1.0.1\n\ + MyTestPackageName3 1.2.1 More test package description\n\ + \n\ + mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("-i=false") .arg("-f=true") @@ -174,16 +177,17 @@ MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my descriptio fn experimental_output_flip_by_env_var() { init(); - let desired_output = "MyTestPackageName 1.0.0 Test package description - -MyTestPackageName1 1.1.0 Another test package description -MyTestPackageName2 1.0.1 -MyTestPackageName3 1.2.1 More test package description - -mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -MatchMyDescription a.b.c MyTestPackageName appears in my description -MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -"; + let desired_output = "\ + MyTestPackageName 1.0.0 Test package description\n\ + \n\ + MyTestPackageName1 1.1.0 Another test package description\n\ + MyTestPackageName2 1.0.1\n\ + MyTestPackageName3 1.2.1 More test package description\n\ + \n\ + mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("-i=false"); cmd.arg("--cache-folder=tests/") @@ -204,24 +208,24 @@ fn output_case_sensitive() { // The cache mixes scenarios for nixos-the-OS and nix-the-package-manager. We test for both at // the same time. - let desired_output = - "nixos.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -nixos.MatchMyDescription a.b.c MyTestPackageName appears in my description -nixos.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -nixpkgs.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description -nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName - -nixos.MyTestPackageName3 1.2.1 More test package description -nixos.MyTestPackageName2 1.0.1 -nixos.MyTestPackageName1 1.1.0 Another test package description -nixpkgs.MyTestPackageName3 1.2.1 More test package description -nixpkgs.MyTestPackageName2 1.0.1 -nixpkgs.MyTestPackageName1 1.1.0 Another test package description - -nixos.MyTestPackageName 1.0.0 Test package description -nixpkgs.MyTestPackageName 1.0.0 Test package description -"; + let desired_output = "\ + nixos.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + nixos.MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + nixos.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + nixpkgs.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + \n\ + nixos.MyTestPackageName3 1.2.1 More test package description\n\ + nixos.MyTestPackageName2 1.0.1\n\ + nixos.MyTestPackageName1 1.1.0 Another test package description\n\ + nixpkgs.MyTestPackageName3 1.2.1 More test package description\n\ + nixpkgs.MyTestPackageName2 1.0.1\n\ + nixpkgs.MyTestPackageName1 1.1.0 Another test package description\n\ + \n\ + nixos.MyTestPackageName 1.0.0 Test package description\n\ + nixpkgs.MyTestPackageName 1.0.0 Test package description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("-i=false") .arg("--cache-folder=tests/") @@ -241,25 +245,26 @@ fn output() { // The cache mixes scenarios for nixos-the-OS and nix-the-package-manager. We test for both at // the same time. - let desired_output = "nixos.MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization -nixos.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -nixos.MatchMyDescription a.b.c MyTestPackageName appears in my description -nixpkgs.MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization -nixpkgs.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description -nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description - -nixos.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -nixos.MyTestPackageName3 1.2.1 More test package description -nixos.MyTestPackageName2 1.0.1 -nixos.MyTestPackageName1 1.1.0 Another test package description -nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName -nixpkgs.MyTestPackageName3 1.2.1 More test package description -nixpkgs.MyTestPackageName2 1.0.1 -nixpkgs.MyTestPackageName1 1.1.0 Another test package description - -nixos.MyTestPackageName 1.0.0 Test package description -nixpkgs.MyTestPackageName 1.0.0 Test package description -"; + let desired_output = "\ + nixos.MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization\n\ + nixos.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + nixos.MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + nixpkgs.MatchMyDescription2 9.8.7 mytestpackageName appears in my description with different capitalization\n\ + nixpkgs.MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ + nixpkgs.MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ + \n\ + nixos.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + nixos.MyTestPackageName3 1.2.1 More test package description\n\ + nixos.MyTestPackageName2 1.0.1\n\ + nixos.MyTestPackageName1 1.1.0 Another test package description\n\ + nixpkgs.mytestpackageName3 3.2.1 More test package description, now with MyTestPackageName\n\ + nixpkgs.MyTestPackageName3 1.2.1 More test package description\n\ + nixpkgs.MyTestPackageName2 1.0.1\n\ + nixpkgs.MyTestPackageName1 1.1.0 Another test package description\n\ + \n\ + nixos.MyTestPackageName 1.0.0 Test package description\n\ + nixpkgs.MyTestPackageName 1.0.0 Test package description\n\ + "; let mut cmd = Command::cargo_bin("nps").unwrap(); cmd.arg("--cache-folder=tests/") .arg("--experimental=false") @@ -371,3 +376,42 @@ fn experimental_cache_creation() { ); }); } + +#[test] +fn multi_line() { + init(); + + let desired_output = "\ +MatchMyDescription1 9.8.7 + Also here MyTestPackageName appears in my description + +MatchMyDescription a.b.c + MyTestPackageName appears in my description + +mytestpackageName3 3.2.1 + More test package description, now with MyTestPackageName + +MyTestPackageName3 1.2.1 + More test package description + +MyTestPackageName2 1.0.1 + +MyTestPackageName1 1.1.0 + Another test package description + +MyTestPackageName 1.0.0 + Test package description +"; + let mut cmd = Command::cargo_bin("nps").unwrap(); + cmd.arg("-i=false") + .arg("--cache-folder=tests/") + .arg("--multi-line=true") + .arg("--experimental=true") + .arg("MyTestPackageName") + .arg("-dddd") + .env_clear(); // remove env vars + + cmd.assert() + .success() + .stdout(predicate::str::diff(desired_output)); +} From d055e5770f24c0fc70b5b4cc6eb3d2579ffb99bf Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Mon, 24 Mar 2025 19:22:40 +0100 Subject: [PATCH 07/15] update dependencies --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f31573..3dd26de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,9 +409,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" diff --git a/Cargo.toml b/Cargo.toml index 52f53aa..fca096a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ clap = { version = "4.5.32", features = ["derive", "env", "string"] } env_logger = "0.11.7" grep = "0.3.2" home = "0.5.11" -log = "0.4.26" +log = "0.4.27" regex = "1.11.1" serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" From 342b4ba2568d76620b7ed2797de0fe0dcafb851e Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Mon, 24 Mar 2025 19:33:23 +0100 Subject: [PATCH 08/15] update documentation for new features --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index 3985405..861915e 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,18 @@ Options: [default: true] [possible values: true, false] + -m, --multi-line[=] + Multi line + + Print search matches on two lines, followed by a newline: + > PACKAGE_NAME PACKAGE_VERSION + > PACKAGE_DESCRIPTION + > + + [env: NIX_PACKAGE_SEARCH_MULTI_LINE=] + [default: false] + [possible values: true, false] + -q, --quiet[=] Suppress non-debug messages @@ -224,10 +236,21 @@ Options: -s, --separate[=] Separate match types with a newline + Only applicable when --multi-line=false + [env: NIX_PACKAGE_SEARCH_PRINT_SEPARATOR=] [default: true] [possible values: true, false] + -t, --truncate[=] + Separate match types with a newline + + Only applicable when --multi-line=false + + [env: NIX_PACKAGE_SEARCH_TRUNCATE=] + [default: false] + [possible values: true, false] + -h, --help Print help (see a summary with '-h') @@ -246,8 +269,10 @@ environment.sessionVariables = { #NIX_PACKAGE_SEARCH_EXPERIMENTAL = "false"; # Set to "true" for flakes #NIX_PACKAGE_SEARCH_FLIP = "false"; #NIX_PACKAGE_SEARCH_IGNORE_CASE = "true"; + #NIX_PACKAGE_SEARCH_MULTI_LINE = "false"; #NIX_PACKAGE_SEARCH_PRINT_SEPARATOR = "true"; #NIX_PACKAGE_SEARCH_QUIET = "false"; + #NIX_PACKAGE_SEARCH_TRUNCATE = "false"; #NIX_PACKAGE_SEARCH_CACHE_FOLDER_ABSOLUTE_PATH = "/home/YOUR_USERNAME/.nix-package-search"; #NIX_PACKAGE_SEARCH_EXACT_COLOR = "magenta"; @@ -322,6 +347,25 @@ Search ignore capitalization for the search? - default: true - possible values: true, false + +#### `NIX_PACKAGE_SEARCH_MULTI_LINE` +Print search matches on multiple lines? +``` +> PACKAGE_NAME PACKAGE_VERSION +> PACKAGE_DESCRIPTION +> +``` +- default: false +- possible values: true, false + +#### `NIX_PACKAGE_SEARCH_TRUNCATE` +Truncate lines longer than terminal width? +``` +> PACKAGE_NAME PACKAGE_VERSION PACKAGE_DESC… +``` +- default: false +- possible values: true, false + ## Contributing 1. Check existing issues or open a new one to suggest a feature or report a bug. From 07a5eebd5cd7e8b4a260976f27af81350e3f4019 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Mon, 24 Mar 2025 19:34:50 +0100 Subject: [PATCH 09/15] update release help text --- hooks/pre-push | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/pre-push b/hooks/pre-push index 5c2f491..7b06eea 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -40,7 +40,7 @@ fi echo "Checking for tags..." if ! [[ $(git tag --points-at HEAD) ]] then - echo "Create a tag \`cargo release {major,minor,patch,release,rc,beta,alpha} --no-release [--execute]\`" + echo "Create a tag \`cargo release {major,minor,patch,release,rc,beta,alpha} --no-publish [--execute]\`" exit 1 fi From 94c25156cc71d1c185a37266b6c961ca1edde78a Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Mon, 24 Mar 2025 19:36:36 +0100 Subject: [PATCH 10/15] chore: Release nps version 0.2.10 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 998e29b..0fabcd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +## [0.2.10] - 2025-03-24 + ### Added - Flag `-m` for multi-line output - Flag `-t` for truncation of long lines @@ -150,7 +152,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Versioning now adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.9...development +[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.10...development +[0.2.10]: https://github.com/OleMussmann/nps/compare/v0.2.9...v0.2.10 [0.2.9]: https://github.com/OleMussmann/nps/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/OleMussmann/nps/compare/v0.2.7...v0.2.8 [0.2.7]: https://github.com/OleMussmann/nps/compare/v0.2.6...v0.2.7 diff --git a/Cargo.lock b/Cargo.lock index 3dd26de..dd14690 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,7 +436,7 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nps" -version = "0.2.9" +version = "0.2.10" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index fca096a..f712ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nps" -version = "0.2.9" +version = "0.2.10" description = "Find SEARCH_TERM in available nix packages and sort results by relevance" authors = ["Ole Mussmann "] homepage = "https://github.com/OleMussmann/nps" From 4066b5c0de3e1ea71dd4a203864d0e2d5a58469b Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sat, 6 Dec 2025 15:25:10 +0100 Subject: [PATCH 11/15] update dependencies --- Cargo.lock | 358 +++++++++++++++++++++++-------------------- Cargo.toml | 24 +-- flake.lock | 103 ++++++++++--- flake.nix | 2 +- tests/integration.rs | 30 ++-- 5 files changed, 300 insertions(+), 217 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd14690..5d1c5c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -28,48 +28,47 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "assert_cmd" -version = "2.0.16" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" +checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" dependencies = [ "anstyle", "bstr", - "doc-comment", "libc", "predicates", "predicates-core", @@ -79,21 +78,21 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", @@ -102,15 +101,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.5.32" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -118,9 +117,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -130,9 +129,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -142,15 +141,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "difflib" @@ -158,12 +157,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -184,9 +177,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", @@ -194,9 +187,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", @@ -207,12 +200,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -232,21 +225,21 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi", + "wasip2", ] [[package]] name = "globset" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", @@ -257,9 +250,9 @@ dependencies = [ [[package]] name = "grep" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308ae749734e28d749a86f33212c7b756748568ce332f970ac1d9cd8531f32e6" +checksum = "309217bc53e2c691c314389c7fa91f9cd1a998cda19e25544ea47d94103880c3" dependencies = [ "grep-cli", "grep-matcher", @@ -270,9 +263,9 @@ dependencies = [ [[package]] name = "grep-cli" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47f1288f0e06f279f84926fa4c17e3fcd2a22b357927a82f2777f7be26e4cec0" +checksum = "cf32d263c5d5cc2a23ce587097f5ddafdb188492ba2e6fb638eaccdc22453631" dependencies = [ "bstr", "globset", @@ -284,18 +277,18 @@ dependencies = [ [[package]] name = "grep-matcher" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02" +checksum = "36d7b71093325ab22d780b40d7df3066ae4aebb518ba719d38c697a8228a8023" dependencies = [ "memchr", ] [[package]] name = "grep-printer" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c112110ae4a891aa4d83ab82ecf734b307497d066f437686175e83fbd4e013fe" +checksum = "fd76035e87871f51c1ee5b793e32122b3ccf9c692662d9622ef1686ff5321acb" dependencies = [ "bstr", "grep-matcher", @@ -308,9 +301,9 @@ dependencies = [ [[package]] name = "grep-regex" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edd147c7e3296e7a26bd3a81345ce849557d5a8e48ed88f736074e760f91f7e" +checksum = "0ce0c256c3ad82bcc07b812c15a45ec1d398122e8e15124f96695234db7112ef" dependencies = [ "bstr", "grep-matcher", @@ -321,9 +314,9 @@ dependencies = [ [[package]] name = "grep-searcher" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b6c14b3fc2e0a107d6604d3231dec0509e691e62447104bc385a46a7892cda" +checksum = "ac63295322dc48ebb20a25348147905d816318888e64f531bfc2a2bc0577dc34" dependencies = [ "bstr", "encoding_rs", @@ -342,18 +335,18 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" @@ -363,22 +356,22 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.5" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", @@ -387,43 +380,42 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -467,15 +459,21 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +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 = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -483,22 +481,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-link", ] [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -541,42 +539,42 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -586,9 +584,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -597,21 +595,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustix" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -628,18 +626,28 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.219" +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 = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -648,21 +656,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strsim" @@ -672,9 +681,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -692,15 +701,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.19.1" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", "getrandom", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -714,12 +723,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.60.2", ] [[package]] @@ -730,9 +739,9 @@ checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-segmentation" @@ -742,9 +751,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" @@ -762,38 +771,54 @@ dependencies = [ ] [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", @@ -806,57 +831,54 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" diff --git a/Cargo.toml b/Cargo.toml index f712ce0..8b4830b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,21 +11,21 @@ readme = "README.md" edition = "2021" [dependencies] -clap = { version = "4.5.32", features = ["derive", "env", "string"] } -env_logger = "0.11.7" -grep = "0.3.2" -home = "0.5.11" -log = "0.4.27" -regex = "1.11.1" -serde = { version = "1.0.219", features = ["derive"] } -serde_json = "1.0.140" -tempfile = "3.19.1" +clap = { version = "4.5.53", features = ["derive", "env", "string"] } +env_logger = "0.11.8" +grep = "0.4.1" +home = "0.5.12" +log = "0.4.29" +regex = "1.12.2" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" +tempfile = "3.23.0" termcolor = "1.4.1" -terminal_size = "0.4.2" +terminal_size = "0.4.3" unicode-segmentation = "1.12.0" -unicode-width = "0.2.0" +unicode-width = "0.2.2" [dev-dependencies] -assert_cmd = "2.0.16" +assert_cmd = "2.1.1" predicates = "3.1.3" temp-env = "0.3.6" diff --git a/flake.lock b/flake.lock index 682bc20..0c37220 100644 --- a/flake.lock +++ b/flake.lock @@ -1,13 +1,35 @@ { "nodes": { + "fenix": { + "inputs": { + "nixpkgs": [ + "naersk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1752475459, + "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", + "owner": "nix-community", + "repo": "fenix", + "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "flake-compat": { "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "revCount": 57, + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "revCount": 69, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz" }, "original": { "type": "tarball", @@ -15,12 +37,15 @@ } }, "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1667395993, - "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -31,14 +56,15 @@ }, "naersk": { "inputs": { + "fenix": "fenix", "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1733346208, - "narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=", + "lastModified": 1763384566, + "narHash": "sha256-r+wgI+WvNaSdxQmqaM58lVNvJYJ16zoq+tKN20cLst4=", "owner": "nix-community", "repo": "naersk", - "rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921", + "rev": "d4155d6ebb70fbe2314959842f744aa7cabbbf6a", "type": "github" }, "original": { @@ -49,24 +75,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1734323986, - "narHash": "sha256-m/lh6hYMIWDYHCAsn81CDAiXoT3gmxXI9J987W5tZrE=", - "path": "/nix/store/wj2qla569hnxwqfc26imv5hqbxc1rc27-source", - "rev": "394571358ce82dff7411395829aa6a3aad45b907", - "type": "path" + "lastModified": 1752077645, + "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", + "type": "github" }, "original": { - "id": "nixpkgs", - "type": "indirect" + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1734424634, - "narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=", + "lastModified": 1764950072, + "narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33", + "rev": "f61125a668a320878494449750330ca58b78c557", "type": "github" }, "original": { @@ -83,6 +112,38 @@ "naersk": "naersk", "nixpkgs": "nixpkgs_2" } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1752428706, + "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index a022f17..d8185ad 100644 --- a/flake.nix +++ b/flake.nix @@ -43,7 +43,7 @@ clippy # linting hyperfine # benchmarking nixfmt-rfc-style # code formatting - python312Packages.grip # markdown rendering + python313Packages.grip # markdown rendering rustfmt # code formatting ]; }; diff --git a/tests/integration.rs b/tests/integration.rs index 667493a..0ce789b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,4 +1,4 @@ -use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; +use assert_cmd::{assert::OutputAssertExt, cargo}; use predicates::prelude::predicate; use regex::Regex; use std::{fs, io::Write, process::Command}; @@ -12,7 +12,7 @@ fn init() { fn short_help() { init(); - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-h").arg("-dddd"); cmd.assert().success().stdout(predicate::str::contains( "Find SEARCH_TERM in available nix packages and sort results by relevance", @@ -26,7 +26,7 @@ fn short_help() { fn long_help() { init(); - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("--help").arg("-dddd"); cmd.assert().success().stdout(predicate::str::contains( "Use up to four times for increased verbosity", @@ -40,7 +40,7 @@ fn long_help() { fn no_search_term() { init(); - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-dddd"); cmd.assert().failure().stderr(predicate::str::contains( "error: the following required arguments were not provided: @@ -52,7 +52,7 @@ fn no_search_term() { fn too_much_debug() { init(); - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-ddddd").arg("search_term").env_clear(); cmd.assert() .failure() @@ -74,7 +74,7 @@ fn experimental_output_case_sensitive() { \n\ MyTestPackageName 1.0.0 Test package description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false") .arg("--cache-folder=tests/") .arg("--experimental=true") @@ -103,7 +103,7 @@ fn experimental_output() { \n\ MyTestPackageName 1.0.0 Test package description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("--cache-folder=tests/") .arg("--experimental=true") .arg("MyTestPackageName") @@ -130,7 +130,7 @@ fn experimental_output_flip_by_command_line_no_equals() { MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false") .arg("-f") .arg("--cache-folder=tests/") @@ -159,7 +159,7 @@ fn experimental_output_flip_by_command_line_equals() { MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false") .arg("-f=true") .arg("--cache-folder=tests/") @@ -188,7 +188,7 @@ fn experimental_output_flip_by_env_var() { MatchMyDescription a.b.c MyTestPackageName appears in my description\n\ MatchMyDescription1 9.8.7 Also here MyTestPackageName appears in my description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false"); cmd.arg("--cache-folder=tests/") .arg("--experimental=true") @@ -226,7 +226,7 @@ fn output_case_sensitive() { nixos.MyTestPackageName 1.0.0 Test package description\n\ nixpkgs.MyTestPackageName 1.0.0 Test package description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false") .arg("--cache-folder=tests/") .arg("--experimental=false") @@ -265,7 +265,7 @@ fn output() { nixos.MyTestPackageName 1.0.0 Test package description\n\ nixpkgs.MyTestPackageName 1.0.0 Test package description\n\ "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("--cache-folder=tests/") .arg("--experimental=false") .arg("MyTestPackageName") @@ -305,7 +305,7 @@ fn cache_creation() { tempfile.persist(nix_conf_dir.join("nix.conf")).unwrap(); temp_env::with_var("XDG_CONFIG_HOME", Some(&tempdir.path()), || { - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg(format!("--cache-folder={}", &temp_path.display())) .arg("--experimental=false") .arg("-dddd") @@ -350,7 +350,7 @@ fn experimental_cache_creation() { let temp_dir = TempDir::new().unwrap(); let temp_path = temp_dir.path().to_owned(); - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg(format!("--cache-folder={}", &temp_path.display())) .arg("--experimental=true") .arg("--quiet") @@ -402,7 +402,7 @@ MyTestPackageName1 1.1.0 MyTestPackageName 1.0.0 Test package description "; - let mut cmd = Command::cargo_bin("nps").unwrap(); + let mut cmd = Command::new(cargo::cargo_bin!("nps")); cmd.arg("-i=false") .arg("--cache-folder=tests/") .arg("--multi-line=true") From 6eadf9b7d71c7cbfd8a40c59b27206294ada5000 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 7 Dec 2025 15:42:30 +0100 Subject: [PATCH 12/15] update documentation for nixpkgs repository --- CHANGELOG.md | 3 +++ README.md | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fabcd4..f4d234b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed +- Update documentation + ## [0.2.10] - 2025-03-24 ### Added diff --git a/README.md b/README.md index 861915e..1eb1c06 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,33 @@ Find installable packages at lightning speed and sort the result by relevance, s ![The command `nps avahi` lists all nixpkgs matching `avahi`, sorted by relevance.](https://i.imgur.com/wNnWdxC.png "nps avahi") -## Installation +## Running via `nixpkgs` Repository (Recommended ⭐) +This is the safest way to run a well-tested `nps` version from the [official repository](https://search.nixos.org/packages?channel=25.11&show=nps&query=nps). + +### Try It Without Installing +#### Flakes ❄️ +```bash +nix run nixpkgs#nps -- COMMAND_LINE_OPTIONS +``` + +#### No Flakes ☀️ +```bash +nix --extra-experimental-features "nix-command flakes" run nixpkgs#nps -- COMMAND_LINE_OPTIONS +``` + +### Declarative Installation (Recommended ⭐) +Add `nps` to your `systemPackages` in `configuration.nix`: + +```nix +environment.systemPackages = with pkgs; [ + nps + ... +]; +``` + +## Running via GitHub (Bleeding Edge) +If you want the latest features before they hit `nixpkgs`. + ### Try It Without Installing #### Flakes ❄️ ```bash @@ -25,7 +51,7 @@ nix run github:OleMussmann/nps -- COMMAND_LINE_OPTIONS nix --extra-experimental-features "nix-command flakes" run github:OleMussmann/nps -- COMMAND_LINE_OPTIONS ``` -### Declarative Installation (Recommended) +### Declarative Installation (Recommended ⭐) #### Flakes ❄️ > ⚠️ The way of installing third-party flakes is highly dependent on your personal configuration. As far as I know there is no standardized, canonical way to do this. Instead, here is a generic approach via overlays. You will need to adapt it to your config files. @@ -33,7 +59,7 @@ Add `nps` to your inputs: ```nix inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; # Nix Package Search - nps nps.url = "github:OleMussmann/nps"; @@ -88,7 +114,7 @@ environment.systemPackages = with pkgs; [ - Build with `cargo build --release`. Dependencies needed: `gcc`, `cargo` - Copy or symlink the `target/release/nps` executable to a folder in your `PATH`, or include it in your `PATH`. -## Automatic Package Scanning (Optional) +## Automatic Cache Refresh (Optional, Recommended ⭐) Instead of running `nps -r` (or `nps -e -r` for using the nix "experimental" features a.k.a flakes) by hand every once in a while to refresh the package cache, or you can set up a systemd timer at regular intervals. If you automate it, make sure to do so with your local user environment. ```nix @@ -119,7 +145,7 @@ systemd.services."refresh-nps-cache" = { }; ``` -### Testing Automated Package Scanning +### Testing Automated Cache Refresh - Test the service by starting it by hand and checking the logs. ```bash sudo systemctl start refresh-nps-cache.service From e07c5278760c6c51e3b65650d67f06fc8f922f1c Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 7 Dec 2025 15:49:35 +0100 Subject: [PATCH 13/15] chore: Release nps version 0.2.11 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d234b..9d0f0d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +## [0.2.11] - 2025-12-07 + ### Fixed - Update documentation @@ -155,7 +157,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Versioning now adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.10...development +[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.11...development +[0.2.11]: https://github.com/OleMussmann/nps/compare/v0.2.10...v0.2.11 [0.2.10]: https://github.com/OleMussmann/nps/compare/v0.2.9...v0.2.10 [0.2.9]: https://github.com/OleMussmann/nps/compare/v0.2.8...v0.2.9 [0.2.8]: https://github.com/OleMussmann/nps/compare/v0.2.7...v0.2.8 diff --git a/Cargo.lock b/Cargo.lock index 5d1c5c8..be6b506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nps" -version = "0.2.10" +version = "0.2.11" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 8b4830b..4594c60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nps" -version = "0.2.10" +version = "0.2.11" description = "Find SEARCH_TERM in available nix packages and sort results by relevance" authors = ["Ole Mussmann "] homepage = "https://github.com/OleMussmann/nps" From 421942535d0d7663b5530303c14ba8d958ca8af2 Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 7 Dec 2025 16:06:11 +0100 Subject: [PATCH 14/15] update GitHub actions --- .github/workflows/coverage.yml | 4 ++-- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 4 ++-- CHANGELOG.md | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 373d661..2770a88 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 - name: Set up nix run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d17507..49ee4cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: name: release nps runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Create release text run: | TAG=$(grep "## \[" CHANGELOG.md | head -n 2 | tail -n 1 | sed "s/^## \\[\(.*\)\\].*/\1/") diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8263076..1b73c1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,10 +24,10 @@ jobs: toolchain: nightly steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 - name: Set up nix run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d0f0d1..badcba0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +### Fixed +- GitHub CI runners + ## [0.2.11] - 2025-12-07 ### Fixed From e71c134e676588e5d8a7a849dc6664c64e7913ab Mon Sep 17 00:00:00 2001 From: Ole Mussmann Date: Sun, 7 Dec 2025 16:44:57 +0100 Subject: [PATCH 15/15] chore: Release nps version 0.2.12 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index badcba0..f4f648e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - ReleaseDate +## [0.2.12] - 2025-12-07 + ### Fixed - GitHub CI runners @@ -160,7 +162,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Versioning now adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.11...development +[Unreleased]: https://github.com/OleMussmann/nps/compare/v0.2.12...development +[0.2.12]: https://github.com/OleMussmann/nps/compare/v0.2.11...v0.2.12 [0.2.11]: https://github.com/OleMussmann/nps/compare/v0.2.10...v0.2.11 [0.2.10]: https://github.com/OleMussmann/nps/compare/v0.2.9...v0.2.10 [0.2.9]: https://github.com/OleMussmann/nps/compare/v0.2.8...v0.2.9 diff --git a/Cargo.lock b/Cargo.lock index be6b506..8e68a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -428,7 +428,7 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "nps" -version = "0.2.11" +version = "0.2.12" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 4594c60..8b77098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nps" -version = "0.2.11" +version = "0.2.12" description = "Find SEARCH_TERM in available nix packages and sort results by relevance" authors = ["Ole Mussmann "] homepage = "https://github.com/OleMussmann/nps"