aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/mp/mpdpopm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/mp/mpdpopm/.envrc7
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.lock1689
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.toml32
-rw-r--r--pkgs/by-name/mp/mpdpopm/README.md189
-rw-r--r--pkgs/by-name/mp/mpdpopm/build.rs3
-rw-r--r--pkgs/by-name/mp/mpdpopm/config.lsp24
-rw-r--r--pkgs/by-name/mp/mpdpopm/flake.lock48
-rw-r--r--pkgs/by-name/mp/mpdpopm/flake.nix66
-rw-r--r--pkgs/by-name/mp/mpdpopm/package.nix2
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs232
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs (renamed from pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs)492
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/clients.rs2
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/config.rs138
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs237
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/mod.rs28
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters.lalrpop59
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters_ast.rs49
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/lib.rs60
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/messages.rs409
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs140
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/playcounts.rs62
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/storage/mod.rs8
-rwxr-xr-xpkgs/by-name/mp/mpdpopm/update.sh6
23 files changed, 2597 insertions, 1385 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/.envrc b/pkgs/by-name/mp/mpdpopm/.envrc
index 9f477e71..40e13e1c 100644
--- a/pkgs/by-name/mp/mpdpopm/.envrc
+++ b/pkgs/by-name/mp/mpdpopm/.envrc
@@ -10,13 +10,8 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/agpl.txt>.
-use flake || use nix
-watch_file flake.nix
+pre_cache_load rust
PATH_add ./scripts
PATH_add ./target/debug/
PATH_add ./target/release/
-
-if on_git_branch; then
- echo && git status --short --branch
-fi
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.lock b/pkgs/by-name/mp/mpdpopm/Cargo.lock
index 96909646..4ab1ff3e 100644
--- a/pkgs/by-name/mp/mpdpopm/Cargo.lock
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.lock
@@ -4,27 +4,33 @@ version = 4
[[package]]
name = "aho-corasick"
-version = "1.1.4"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
name = "android_system_properties"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
-version = "0.6.21"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -37,15 +43,15 @@ dependencies = [
[[package]]
name = "anstyle"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
-version = "0.2.7"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
@@ -56,7 +62,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
@@ -67,14 +73,23 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
name = "anyhow"
-version = "1.0.100"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
[[package]]
name = "ascii-canvas"
@@ -87,41 +102,77 @@ dependencies = [
[[package]]
name = "async-trait"
-version = "0.1.89"
+version = "0.1.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "atomic"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340"
+dependencies = [
+ "bytemuck",
]
[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bit-set"
-version = "0.8.0"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
- "bit-vec",
+ "bit-vec 0.6.3",
]
[[package]]
+name = "bit-set"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
+dependencies = [
+ "bit-vec 0.9.1",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
name = "bit-vec"
-version = "0.8.0"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.10.0"
+version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "block-buffer"
@@ -140,21 +191,42 @@ checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
[[package]]
name = "bumpalo"
-version = "3.19.1"
+version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "by_address"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
[[package]]
name = "bytes"
-version = "1.11.0"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
[[package]]
name = "cc"
-version = "1.2.54"
+version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -167,10 +239,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+ "rand_core 0.10.1",
+]
+
+[[package]]
name = "chrono"
-version = "0.4.43"
+version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -181,9 +270,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.54"
+version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
+checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
dependencies = [
"clap_builder",
"clap_derive",
@@ -191,9 +280,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.54"
+version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
+checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
dependencies = [
"anstream",
"anstyle",
@@ -203,27 +292,50 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.49"
+version = "4.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
]
[[package]]
name = "clap_lex"
-version = "0.7.7"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
-version = "1.0.4"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
[[package]]
name = "core-foundation-sys"
@@ -241,6 +353,48 @@ dependencies = [
]
[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags 2.13.1",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -251,6 +405,84 @@ dependencies = [
]
[[package]]
+name = "csscolorparser"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf"
+dependencies = [
+ "lab",
+ "phf",
+]
+
+[[package]]
+name = "darling"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "deltae"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.119",
+]
+
+[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -261,16 +493,25 @@ dependencies = [
]
[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
name = "either"
-version = "1.15.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "ena"
-version = "0.14.3"
+version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5"
+checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1"
dependencies = [
"log",
]
@@ -288,14 +529,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.61.2",
+ "windows-sys",
+]
+
+[[package]]
+name = "euclid"
+version = "0.22.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "fancy-regex"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
+dependencies = [
+ "bit-set 0.5.3",
+ "regex",
+]
+
+[[package]]
+name = "filedescriptor"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
+dependencies = [
+ "libc",
+ "thiserror 1.0.69",
+ "winapi",
]
[[package]]
name = "find-msvc-tools"
-version = "0.1.8"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+
+[[package]]
+name = "finl_unicode"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fixedbitset"
@@ -304,10 +587,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
name = "futures"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
dependencies = [
"futures-channel",
"futures-core",
@@ -320,9 +621,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [
"futures-core",
"futures-sink",
@@ -330,15 +631,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-executor"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
dependencies = [
"futures-core",
"futures-task",
@@ -347,38 +648,38 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-macro"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
]
[[package]]
name = "futures-sink"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-channel",
"futures-core",
@@ -388,7 +689,6 @@ dependencies = [
"futures-task",
"memchr",
"pin-project-lite",
- "pin-utils",
"slab",
]
@@ -403,10 +703,59 @@ dependencies = [
]
[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "foldhash 0.1.5",
+]
+
+[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
[[package]]
name = "heck"
@@ -415,10 +764,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
name = "iana-time-zone"
-version = "0.1.64"
+version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -439,13 +794,41 @@ dependencies = [
]
[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
name = "indexmap"
-version = "2.13.0"
+version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "indoc"
+version = "2.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "instability"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
+dependencies = [
+ "darling",
+ "indoc",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
]
[[package]]
@@ -465,37 +848,55 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.17"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
-version = "0.3.85"
+version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
dependencies = [
- "once_cell",
+ "cfg-if",
+ "futures-util",
"wasm-bindgen",
]
[[package]]
+name = "kasuari"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
+dependencies = [
+ "hashbrown 0.16.1",
+ "portable-atomic",
+ "thiserror 2.0.20",
+]
+
+[[package]]
name = "keccak"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
- "cpufeatures",
+ "cpufeatures 0.2.17",
]
[[package]]
+name = "lab"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f"
+
+[[package]]
name = "lalrpop"
-version = "0.22.2"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
+checksum = "98a80a963123205c7157323c99611bc4abb65dcbd62ef46dc4bac74a3941bc75"
dependencies = [
"ascii-canvas",
- "bit-set",
+ "bit-set 0.9.1",
"ena",
"itertools",
"lalrpop-util",
@@ -512,12 +913,11 @@ dependencies = [
[[package]]
name = "lalrpop-util"
-version = "0.22.2"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
+checksum = "884f3e747ed2dcee867cda1b0c31a048f9e20de2d916a248949319921a2e666e"
dependencies = [
"regex-automata",
- "rustversion",
]
[[package]]
@@ -528,9 +928,36 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.180"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "line-clipping"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e"
+dependencies = [
+ "bitflags 2.13.1",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
[[package]]
name = "lock_api"
@@ -543,9 +970,28 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "lru"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff9840bcc50b71349309900da0ce7279aa336ae71d73250b07998932c7d97c25"
+dependencies = [
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix",
+ "winapi",
+]
[[package]]
name = "matchers"
@@ -558,19 +1004,41 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.7.6"
+version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "memmem"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
-version = "1.1.1"
+version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
+ "log",
"wasi",
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
@@ -589,9 +1057,12 @@ dependencies = [
"lazy_static",
"os_str_bytes",
"pin-project",
+ "rand 0.10.2",
+ "ratatui",
"regex",
"serde",
"serde_json",
+ "shlex",
"tokio",
"toml",
"tracing",
@@ -605,12 +1076,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
+name = "nix"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+dependencies = [
+ "bitflags 2.13.1",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
]
[[package]]
@@ -623,10 +1134,19 @@ dependencies = [
]
[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "once_cell"
-version = "1.21.3"
+version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
@@ -635,15 +1155,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
name = "os_str_bytes"
-version = "7.1.1"
+version = "7.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63eceb7b5d757011a87d08eb2123db15d87fb0c281f65d101ce30a1e96c3ad5c"
+checksum = "73b489f051c9d2f4299ddf8d55e33c65c86a7b76f49359af15f79ebe541d1595"
dependencies = [
"memchr",
]
[[package]]
+name = "palette"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64"
+dependencies = [
+ "approx",
+ "libm",
+ "palette_derive",
+ "palette_math",
+]
+
+[[package]]
+name = "palette_derive"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be"
+dependencies = [
+ "by_address",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "palette_math"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12"
+dependencies = [
+ "libm",
+]
+
+[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -667,16 +1229,102 @@ dependencies = [
]
[[package]]
+name = "pest"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89cc5a242e25ed4e7704d0be240f2cfbe20a8c27e7e252d94835be93d92dc39f"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abf21475cc3820fe4b2ca2dc2142902f67a02189f3b5b3a229f4febc01a43e5"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adba4db388f687393c18c51348d44a41d870ca9df71a2c98172ea3035dc6936e"
+dependencies = [
+ "pest",
+]
+
+[[package]]
name = "petgraph"
-version = "0.7.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
- "fixedbitset",
+ "fixedbitset 0.5.7",
+ "hashbrown 0.15.5",
"indexmap",
]
[[package]]
+name = "phf"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.11.3",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
+dependencies = [
+ "phf_generator",
+ "phf_shared 0.11.3",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
+dependencies = [
+ "phf_shared 0.11.3",
+ "rand 0.8.8",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
+dependencies = [
+ "phf_generator",
+ "phf_shared 0.11.3",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -686,6 +1334,15 @@ dependencies = [
]
[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -693,35 +1350,41 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
-version = "1.1.10"
+version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.10"
+version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
name = "pin-project-lite"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
-name = "pin-utils"
-version = "0.1.0"
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "precomputed-hash"
@@ -731,36 +1394,181 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
-version = "1.0.106"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.44"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
+dependencies = [
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "ratatui"
+version = "0.30.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d"
+dependencies = [
+ "instability",
+ "ratatui-core",
+ "ratatui-crossterm",
+ "ratatui-macros",
+ "ratatui-termina",
+ "ratatui-termwiz",
+ "ratatui-widgets",
+ "serde",
+]
+
+[[package]]
+name = "ratatui-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
+dependencies = [
+ "bitflags 2.13.1",
+ "compact_str",
+ "critical-section",
+ "hashbrown 0.17.1",
+ "itertools",
+ "kasuari",
+ "lru",
+ "palette",
+ "serde",
+ "strum",
+ "thiserror 2.0.20",
+ "unicode-segmentation",
+ "unicode-truncate",
+ "unicode-width",
+]
+
+[[package]]
+name = "ratatui-crossterm"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0"
+dependencies = [
+ "cfg-if",
+ "crossterm",
+ "instability",
+ "ratatui-core",
+]
+
+[[package]]
+name = "ratatui-macros"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed7dc68daa7498a43e4d68e0eb078427e10c38fbcfbb1e42d955f1fa2140d814"
+dependencies = [
+ "ratatui-core",
+ "ratatui-widgets",
+]
+
+[[package]]
+name = "ratatui-termina"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0bf912d9e66f057a759d92e386a280ea886b352ab757d6ac4d653c7ed2c43c2"
+dependencies = [
+ "instability",
+ "ratatui-core",
+ "termina",
+]
+
+[[package]]
+name = "ratatui-termwiz"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf03e0380b7744054d6cb74224fe3adf062a029754933f575ca1e3b4c2ce977"
+dependencies = [
+ "ratatui-core",
+ "termwiz",
+]
+
+[[package]]
+name = "ratatui-widgets"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
+dependencies = [
+ "bitflags 2.13.1",
+ "hashbrown 0.17.1",
+ "indoc",
+ "instability",
+ "itertools",
+ "line-clipping",
+ "ratatui-core",
+ "serde",
+ "strum",
+ "time",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
]
[[package]]
name = "regex"
-version = "1.12.2"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -770,9 +1578,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.13"
+version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
@@ -781,15 +1589,43 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.8.8"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags 2.13.1",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
[[package]]
name = "rustversion"
-version = "1.0.22"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "same-file"
@@ -807,10 +1643,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
name = "serde"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
@@ -818,29 +1660,29 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
]
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
@@ -851,18 +1693,29 @@ dependencies = [
[[package]]
name = "serde_spanned"
-version = "1.0.4"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
+[[package]]
name = "sha3"
-version = "0.10.8"
+version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
dependencies = [
"digest",
"keccak",
@@ -879,9 +1732,30 @@ dependencies = [
[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
[[package]]
name = "signal-hook-registry"
@@ -895,41 +1769,47 @@ dependencies = [
[[package]]
name = "siphasher"
-version = "1.0.1"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "slab"
-version = "0.4.11"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
-version = "1.15.1"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
[[package]]
name = "socket2"
-version = "0.6.2"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
dependencies = [
"libc",
- "windows-sys 0.60.2",
+ "windows-sys",
]
[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
name = "string_cache"
-version = "0.8.9"
+version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
dependencies = [
"new_debug_unreachable",
"parking_lot",
- "phf_shared",
+ "phf_shared 0.13.1",
"precomputed-hash",
]
@@ -940,10 +1820,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
+name = "strum"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.28.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "syn"
-version = "2.0.114"
+version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
@@ -956,23 +1879,160 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys",
+]
+
+[[package]]
+name = "termina"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9048a889effe34a5cddee0af7f53285198b16dca3be510858d38dfdb3e62a04e"
+dependencies = [
+ "bitflags 2.13.1",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "windows-sys",
+]
+
+[[package]]
+name = "terminfo"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
+dependencies = [
+ "fnv",
+ "nom",
+ "phf",
+ "phf_codegen",
+]
+
+[[package]]
+name = "termios"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "termwiz"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
+dependencies = [
+ "anyhow",
+ "base64",
+ "bitflags 2.13.1",
+ "fancy-regex",
+ "filedescriptor",
+ "finl_unicode",
+ "fixedbitset 0.4.2",
+ "hex",
+ "lazy_static",
+ "libc",
+ "log",
+ "memmem",
+ "nix",
+ "num-derive",
+ "num-traits",
+ "ordered-float",
+ "pest",
+ "pest_derive",
+ "phf",
+ "sha2",
+ "signal-hook",
+ "siphasher",
+ "terminfo",
+ "termios",
+ "thiserror 1.0.69",
+ "ucd-trie",
+ "unicode-segmentation",
+ "vtparse",
+ "wezterm-bidi",
+ "wezterm-blob-leases",
+ "wezterm-color-types",
+ "wezterm-dynamic",
+ "wezterm-input-types",
+ "winapi",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl 2.0.20",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
]
[[package]]
name = "thread_local"
-version = "1.1.9"
+version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
dependencies = [
"cfg-if",
]
[[package]]
+name = "time"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
+dependencies = [
+ "deranged",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
name = "tokio"
-version = "1.49.0"
+version = "1.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
dependencies = [
"bytes",
"libc",
@@ -981,25 +2041,25 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
name = "tokio-macros"
-version = "2.6.0"
+version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
]
[[package]]
name = "toml"
-version = "0.9.11+spec-1.1.0"
+version = "1.1.5+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
+checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785"
dependencies = [
"indexmap",
"serde_core",
@@ -1012,27 +2072,27 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.7.5+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
-version = "1.0.6+spec-1.1.0"
+version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
-version = "1.0.6+spec-1.1.0"
+version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "tracing"
@@ -1053,7 +2113,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1079,9 +2139,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
-version = "0.3.22"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
+checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -1097,15 +2157,44 @@ dependencies = [
[[package]]
name = "typenum"
-version = "1.19.0"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unicode-ident"
-version = "1.0.22"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "unicode-truncate"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
+dependencies = [
+ "itertools",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
@@ -1120,6 +2209,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
+name = "uuid"
+version = "1.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
+dependencies = [
+ "atomic",
+ "getrandom 0.4.3",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1132,6 +2233,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
+name = "vtparse"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1148,10 +2258,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
name = "wasm-bindgen"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
@@ -1162,9 +2281,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1172,36 +2291,130 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.5",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.108"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
[[package]]
+name = "wezterm-bidi"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec"
+dependencies = [
+ "log",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-blob-leases"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7"
+dependencies = [
+ "getrandom 0.3.4",
+ "mac_address",
+ "sha2",
+ "thiserror 1.0.69",
+ "uuid",
+]
+
+[[package]]
+name = "wezterm-color-types"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296"
+dependencies = [
+ "csscolorparser",
+ "deltae",
+ "lazy_static",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "wezterm-dynamic"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac"
+dependencies = [
+ "log",
+ "ordered-float",
+ "strsim",
+ "thiserror 1.0.69",
+ "wezterm-dynamic-derive",
+]
+
+[[package]]
+name = "wezterm-dynamic-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "wezterm-input-types"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e"
+dependencies = [
+ "bitflags 1.3.2",
+ "euclid",
+ "lazy_static",
+ "serde",
+ "wezterm-dynamic",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1222,7 +2435,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1233,7 +2446,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1262,15 +2475,6 @@ dependencies = [
[[package]]
name = "windows-sys"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-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"
@@ -1279,78 +2483,19 @@ dependencies = [
]
[[package]]
-name = "windows-targets"
-version = "0.53.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
-dependencies = [
- "windows-link",
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.53.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.53.1"
+name = "winnow"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
[[package]]
-name = "winnow"
-version = "0.7.14"
+name = "wit-bindgen"
+version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zmij"
-version = "1.0.16"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.toml b/pkgs/by-name/mp/mpdpopm/Cargo.toml
index c82537e6..8152fb40 100644
--- a/pkgs/by-name/mp/mpdpopm/Cargo.toml
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.toml
@@ -21,24 +21,36 @@ keywords = ["mpd", "music", "daemon"]
categories = ["multimedia", "network-programming", "database"]
[build-dependencies]
-lalrpop = { version = "0.22", features = ["lexer"] }
+lalrpop = { version = "0.23", features = ["lexer"] }
[dependencies]
async-trait = "0.1"
boolinator = "2.4"
chrono = "0.4"
-clap = {version = "4.5", features = ["derive"]}
+clap = { version = "4.6", features = ["derive"] }
errno = "0.3"
futures = "0.3"
-lalrpop-util = { version = "0.22", features = ["lexer"] }
+lalrpop-util = { version = "0.23", features = ["lexer"] }
lazy_static = "1.5"
-os_str_bytes = "7.1"
+os_str_bytes = "7.2"
pin-project = "1.1"
-regex = "1.12"
+regex = "1.13"
serde = { version = "1.0", features = ["derive"] }
-serde_json = "1.0.149"
-toml = "0.9"
-tokio = { version = "1.49", features = ["io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
+serde_json = "1.0.151"
+toml = "1.1"
+tokio = { version = "1.53", features = [
+ "io-util",
+ "macros",
+ "net",
+ "process",
+ "rt-multi-thread",
+ "signal",
+ "sync",
+ "time",
+] }
tracing = "0.1.44"
-tracing-subscriber = { version = "0.3.22", features = ["env-filter"]}
-anyhow = "1.0.100"
+tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
+anyhow = "1.0.104"
+shlex = "2.0.1"
+rand = "0.10.2"
+ratatui = "0.30.2"
diff --git a/pkgs/by-name/mp/mpdpopm/README.md b/pkgs/by-name/mp/mpdpopm/README.md
index 3c2d961b..b4d0db7d 100644
--- a/pkgs/by-name/mp/mpdpopm/README.md
+++ b/pkgs/by-name/mp/mpdpopm/README.md
@@ -1,26 +1,23 @@
-
# Table of Contents
-1. [Introduction](#orgb2618c9)
-2. [What Can You Do With It?](#orgf1adf2c)
-3. [Licsense](#org3f75b89)
-4. [Prerequisites](#org67de102)
-5. [Installing](#installing)
- 1. [Use the pre-built binaries](#orgb2e3434)
- 2. [Crates.io](#org971a8b3)
- 3. [Use the Debian package](#org55e51f8)
- 4. [Use the Arch package](#org49ada47)
- 5. [Autotools source distributions](#org9c94559)
- 6. [Building from source](#org64bc5dd)
-6. [Getting Started](#getting_started)
- 1. [Program Structure](#org4a22fae)
- 2. [Getting Set-up](#orgfbd2d7d)
- 1. [MPD](#orgb37b483)
- 2. [mppopmd](#org38f4b69)
- 3. [mppopm](#orgfa9dacf)
-7. [Status & Roadmap](#orgd90c7da)
-
-
+1. [Introduction](#orgb2618c9)
+1. [What Can You Do With It?](#orgf1adf2c)
+1. [Licsense](#org3f75b89)
+1. [Prerequisites](#org67de102)
+1. [Installing](#installing)
+ 1. [Use the pre-built binaries](#orgb2e3434)
+ 1. [Crates.io](#org971a8b3)
+ 1. [Use the Debian package](#org55e51f8)
+ 1. [Use the Arch package](#org49ada47)
+ 1. [Autotools source distributions](#org9c94559)
+ 1. [Building from source](#org64bc5dd)
+1. [Getting Started](#getting_started)
+ 1. [Program Structure](#org4a22fae)
+ 1. [Getting Set-up](#orgfbd2d7d)
+ 1. [MPD](#orgb37b483)
+ 1. [mppopmd](#org38f4b69)
+ 1. [mppopm](#orgfa9dacf)
+1. [Status & Roadmap](#orgd90c7da)
<a id="orgb2618c9"></a>
@@ -30,7 +27,6 @@
This README focuses on obtaining & installing [mpdpopm](https://github.com/sp1ff/mpdpopm); the user manual is distributed with the package in [Texinfo](https://www.gnu.org/software/texinfo/) format. The HTML version of the user manual is hosted on my personal [site](https://www.unwoundstack.com/doc/mpdpopm/curr).
-
<a id="orgf1adf2c"></a>
# What Can You Do With It?
@@ -39,73 +35,76 @@ Once you've [installed](#installing) & [started](#getting_started) [mpdpopm](htt
If you'd like to make use of this information in your song selection, you can ask `mppopmd` to queue-up songs on this basis by saying things like:
- mppopm findadd "(rating > 128)"
+```
+mppopm findadd "(rating > 128)"
+```
to add all songs with a rating greater than 128 to the play queue, or
- mppopm findadd "(lastplayed <= \"2022-12-28\")"
+```
+mppopm findadd "(lastplayed <= \"2022-12-28\")"
+```
to add all songs that haven't been played in the last year.
-
<a id="org3f75b89"></a>
# Licsense
[mpdpopm](https://github.com/sp1ff/mpdpopm) is GPL v3 software.
-
<a id="org67de102"></a>
# Prerequisites
[Music Player Daemon](https://www.musicpd.org/): "Music Player Daemon (MPD) is a flexible, powerful, server-side application for playing music. Through plugins and libraries it can play a variety of sound files while being controlled by its network protocol." If you're reading this, I assume you're already running MPD, so this document won't have much to say on installing & configuring it other than that you **do** need to setup the sticker database by setting `sticker_file` in your configuration.
-If you choose to use the pre-built binaries or the Debian or Arch packages (available under [releases](https://github.com/sp1ff/mpdpopm/releases)), that's all you'll need&#x2013; you can jump ahead to the section entitled [Installing](#getting_started), below.
+If you choose to use the pre-built binaries or the Debian or Arch packages (available under [releases](https://github.com/sp1ff/mpdpopm/releases)), that's all you'll need– you can jump ahead to the section entitled [Installing](#getting_started), below.
If you would prefer to download [mpdpopm](https://github.com/sp1ff/mpdpopm) from [crates.io](https://crates.io/crates/mpdpopm), you'll need need the [Rust](https://www.rust-lang.org/tools/install) toolchain ("Rust is a memory- & thread-safe language with no runtime or garbage collector"). Installing the toolchain is easy:
- curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
+```
+curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
+```
[mpdpopm](https://github.com/sp1ff/mpdpopm) is also available as an Autotools source distribution (also under [releases](https://github.com/sp1ff/mpdpopm/releases)), and of course you can just clone the repo & build the project from source. In either of those two cases you'll need the Gnu [Autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) installed in addition to Rust. In the former case, grab the tarball in the format of your choice & perform the usual "./configure && make && make install" incantation. In the latter, you'll need to invoke "./bootstrap" after you clone the repo. Again, if you're considering that route, I assume you're familiar with the Autotools & won't say much about them here.
-
<a id="installing"></a>
# Installing
As mentioned above, you can install [mpdpopm](https://github.com/sp1ff/mpdpopm) in a few different ways. In increasing order of complexity:
-
<a id="orgb2e3434"></a>
## Use the pre-built binaries
-Thanks to a suggestion by [m040601](https://github.com/m040601), you can download pre-built binaries for each [release](https://github.com/sp1ff/mpdpopm/releases). At the time of this writing, only Linux & MacOS are supported, and only on x86<sub>64</sub> at that. If that works for you, you can do something like:
+Thanks to a suggestion by [m040601](https://github.com/m040601), you can download pre-built binaries for each [release](https://github.com/sp1ff/mpdpopm/releases). At the time of this writing, only Linux & MacOS are supported, and only on x86<sub>64</sub> at that. If that works for you, you can do something like:
- cd /tmp
- curl -L --output mpdpopm-0.3.5.tar.gz https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5-x86_64-unknown-linux.tar.gz
- tar xf mpdpopm-0.3.5.tar.gz
- tree mpdpopm-0.3.5-x86_64-unknown-linux/
- mpdpopm-0.3.5-x86_64-unknown-linux/
- ├── bin
- │ ├── mppopm
- │ └── mppopmd
- └── doc
- ├── AUTHORS
- ├── ChangeLog
- ├── COPYING
- ├── NEWS
- ├── README.org
- ├── THANKS
- ├── mppopmd.conf
- ├── mppopmd.info
- └── mppopmd.service
-
- 2 directories, 10 files
+```
+cd /tmp
+curl -L --output mpdpopm-0.3.5.tar.gz https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5-x86_64-unknown-linux.tar.gz
+tar xf mpdpopm-0.3.5.tar.gz
+tree mpdpopm-0.3.5-x86_64-unknown-linux/
+mpdpopm-0.3.5-x86_64-unknown-linux/
+├── bin
+│ ├── mppopm
+│ └── mppopmd
+└── doc
+ ├── AUTHORS
+ ├── ChangeLog
+ ├── COPYING
+ ├── NEWS
+ ├── README.org
+ ├── THANKS
+ ├── mppopmd.conf
+ ├── mppopmd.info
+ └── mppopmd.service
-Copy the binaries `mppopmd` (the daemon) and `mppopm` (the CLI) to a convenient place (e.g. `/usr/local/bin` or `$HOME/.local/bin`) and proceed to [Getting Started](#getting_started), below.
+2 directories, 10 files
+```
+Copy the binaries `mppopmd` (the daemon) and `mppopm` (the CLI) to a convenient place (e.g. `/usr/local/bin` or `$HOME/.local/bin`) and proceed to [Getting Started](#getting_started), below.
<a id="org971a8b3"></a>
@@ -113,105 +112,104 @@ Copy the binaries `mppopmd` (the daemon) and `mppopm` (the CLI) to a convenient
If you've got the Rust toolchain installed, just say `cargo install mpdpopm`. The binaries will now be in `$HOME/.cargo/bin`, and you can proceed to [Getting Started](#getting_started), below.
-
<a id="org55e51f8"></a>
## Use the Debian package
If you're running on a Debian-based Linux distribution, and you're on an x86<sub>64</sub> processor, I've begun providing a Debian binary package, courtesy of the very cool [cargo-deb](https://github.com/mmstick/cargo-deb) Cargo helper command. Just do:
- cd /tmp
- curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm_0.3.5_amd64.deb
- sudo dpkg -i mpdpopm_0.3.5_amd64.deb
+```
+cd /tmp
+curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm_0.3.5_amd64.deb
+sudo dpkg -i mpdpopm_0.3.5_amd64.deb
+```
The binaries will be placed in `/usr/local/bin`, and you can proceed to [Getting Started](#getting_started), below.
-
<a id="org49ada47"></a>
## Use the Arch package
If you're running on an Arch-based Linux distribution, I maintain a few packages in the [AUR](https://aur.archlinux.org/):
-- [mpdpopm](https://aur.archlinux.org/packages/mpdpopm): which will grab the latest release & build it locally
-- [mpdpopm-git](https://aur.archlinux.org/packages/mpdpopm-git): grab `HEAD` from `master` & build it locally
-- [mpdpopm-bin](https://aur.archlinux.org/packages/mpdpopm-bin): grab the pre-built binaries from the latest release & install 'em
+- [mpdpopm](https://aur.archlinux.org/packages/mpdpopm): which will grab the latest release & build it locally
+- [mpdpopm-git](https://aur.archlinux.org/packages/mpdpopm-git): grab `HEAD` from `master` & build it locally
+- [mpdpopm-bin](https://aur.archlinux.org/packages/mpdpopm-bin): grab the pre-built binaries from the latest release & install 'em
You can clone the git repo for whichever package you'd like to use (remotes at link), do `makepkg` & then use pacman to install the package you just built, or use an AUR package manager (I use `yay`, e.g.)
-
<a id="org9c94559"></a>
## Autotools source distributions
If you've got the Rust toolchain as well as Autotools installed, you can build from source via Autotools:
- cd /tmp
- curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5.tar.xz
- tar xf mpdpopm-0.3.5.tar.xz
- cd mpdpopm-0.3.5
- ./configure
- make
- make check
- sudo make install
+```
+cd /tmp
+curl -L -O https://github.com/sp1ff/mpdpopm/releases/download/0.3.5/mpdpopm-0.3.5.tar.xz
+tar xf mpdpopm-0.3.5.tar.xz
+cd mpdpopm-0.3.5
+./configure
+make
+make check
+sudo make install
+```
All the usual `configure` options apply (`--prefix`, e.g.) In particular, you can say `--enable-debug` to produce debug builds.
-
<a id="org64bc5dd"></a>
## Building from source
Finally, and again if you have the build toolchain (Rust & Autotools) installed, you can build from source:
- git clone git@github.com:sp1ff/mpdpopm.git
- cd mpdpopm
- ./bootstrap
- ./configure
- make
- make check
- sudo make install
+```
+git clone git@github.com:sp1ff/mpdpopm.git
+cd mpdpopm
+./bootstrap
+./configure
+make
+make check
+sudo make install
+```
Notice the call to `./bootstrap`, in this case.
-
<a id="getting_started"></a>
# Getting Started
This README provides a "quick-start" guide to getting mpdpopm up & running. For detailed user docs, refer to the [manual](https://www.unwoundstack.com/doc/mpdpopm/curr).
-
<a id="org4a22fae"></a>
## Program Structure
[mpdpopm](https://github.com/sp1ff/mpdpopm) provides two programs:
-1. `mppopmd` is the companion daemon process
-2. `mppopm` is the associated command-line interface to the daemon
+1. `mppopmd` is the companion daemon process
+1. `mppopm` is the associated command-line interface to the daemon
`mppopmd` will monitor `mpd` for song playback & note when songs complete; this is how it knows to increment the playcount & update the last played timestamp for each song to which you listen. `mppopmd` records this information (i.e. play counts, last played and ratings) using `mpd` [stickers](https://www.musicpd.org/doc/html/protocol.html#stickers). A sticker is a little bit of textual information which clients can attach to songs in the form of a name-value pair. [mpdpopm](https://github.com/sp1ff/mpdpopm) defines a new sticker name for each of these items & udpates the values for each song when & as requested.
-Of course, other `mpd` clients will not, in general, be aware of `mppopmd` or the stickers it sets: you the user will have to bridge that gap. You could of course just fire-up `netcat` & start sending commands over the MPD protocol using `sendmessage`, but that's not particularly convenient&#x2013; that's where `mppopm` comes in. `mppopm` is the client interface; one can through it instruct `mppopmd` to set ratings, get & set the various stickers mpdpopm knows about, and even search for songs in terms of mpdpopm attributes & add them to the play queue.
-
+Of course, other `mpd` clients will not, in general, be aware of `mppopmd` or the stickers it sets: you the user will have to bridge that gap. You could of course just fire-up `netcat` & start sending commands over the MPD protocol using `sendmessage`, but that's not particularly convenient– that's where `mppopm` comes in. `mppopm` is the client interface; one can through it instruct `mppopmd` to set ratings, get & set the various stickers mpdpopm knows about, and even search for songs in terms of mpdpopm attributes & add them to the play queue.
<a id="orgfbd2d7d"></a>
## Getting Set-up
-
<a id="orgb37b483"></a>
### MPD
If you're reading this, I assume you already have MPD up & running, so this section will be brief. One note, prompted by user [m040601](https://github.com/m040601), however: as mentioned above, [mpdpopm](https://github.com/sp1ff/mpdpopm) leverages the MPD sticker database. I was chagrined to find that if you do not configure MPD to maintain a sticker database, all sticker commands will simply be disabled. Therefore, before setting up [mpdpopm](https://github.com/sp1ff/mpdpopm), find your `mpd` configuration file and check to be sure you have a `sticker_file` entry; something like this:
- sticker_file "/home/sp1ff/lib/mpd/sticker.sql"
+```
+sticker_file "/home/sp1ff/lib/mpd/sticker.sql"
+```
Check also that the you have write access to the named file & its parent directory.
-
<a id="org38f4b69"></a>
### mppopmd
@@ -224,37 +222,38 @@ Once you've got the daemon running to your satisfaction, if you're on a systemd-
[tanshoku](https://github.com/tanshoku) was kind enough to contribute a systemd unit for this purpose. At present, the build does not install it, but provides it as an example and leaves it to the user to install should they desire (and after they have edited it to suit their configuration). You can find it in `${prefix}/share/mpdpopm/examples` for the Autotools distribution, `/usr/local/share/mpdpopm/examples` for the Debian package, and in the `doc` folder for the pre-built binaries.
-
<a id="orgfa9dacf"></a>
### mppopm
At this point, [mpdpopm](https://github.com/sp1ff/mpdpopm) will happily monitor your playback history & keep play counts & last played timestamps for you. If you would like to rate tracks, however, you will need to somehow induce your favorite mpd client to send a "rating" message to the [mpdpopm](https://github.com/sp1ff/mpdpopm) commands channel ("unwoundstack.com:commands" by default). Since this is unlikely to be convenient, I wrote an mpd client for the purpose: a little CLI called `mppopm`. You can simply execute
- mppopm set-rating '*****'
+```
+mppopm set-rating '*****'
+```
-to set the current track's rating to five "stars" (say `mppopm --help` for an explanation of the rating system; in brief&#x2013; it's Winamp's). NB. the set rating command by default produces no output; if you want confirmation that something's happening, use the `-v` flag.
+to set the current track's rating to five "stars" (say `mppopm --help` for an explanation of the rating system; in brief– it's Winamp's). NB. the set rating command by default produces no output; if you want confirmation that something's happening, use the `-v` flag.
The CLI offers "get" & "set" commands for play counts, last played timestamps & the rating. It also provides commands for searching your songs on the basis of play count, rating & last played times in addition to the usual artist, title &c. Say `mppopm --help` for a full list of options, including how to tell it where the mpd server can be found on your network.
-
<a id="orgd90c7da"></a>
# Status & Roadmap
I am currently using [mpdpopm](https://github.com/sp1ff/mpdpopm) day in & day out with my music collection, but it's early days; I have chosen the version number (0.n) in the hopes of indicating that. Right now, mpdpopm is the bare-bones of an app: it's plumbing, not the sink.
-Heretofore, you could use the `mppopm` CLI to, say, rate the current song, but in order to actually <span class="underline">do</span> anything with that rating in the future, you'd have had to write some kind of mpd client for yourself. With the 0.2 release, I've added support for extended MPD filter syntax that allows queries that include the stickers that [mpdpopm](https://github.com/sp1ff/mpdpopm) manages&#x2013; so you can now, for instance, say:
+Heretofore, you could use the `mppopm` CLI to, say, rate the current song, but in order to actually <span class="underline">do</span> anything with that rating in the future, you'd have had to write some kind of mpd client for yourself. With the 0.2 release, I've added support for extended MPD filter syntax that allows queries that include the stickers that [mpdpopm](https://github.com/sp1ff/mpdpopm) manages– so you can now, for instance, say:
- mppopm findadd "(artist =~ \"foo\") and (rating > 175)"
+```
+mppopm findadd "(artist =~ \"foo\") and (rating > 175)"
+```
MPD will handle the "artist =~" clause & [mpdpopm](https://github.com/sp1ff/mpdpopm) the "rating >" clause, as well as combining the results.
This will hopefully be a start to making [mpdpopm](https://github.com/sp1ff/mpdpopm) into a more of a user-facing application than a developer-facing utlity.
-Windows support may be some time coming; the daemon depends on Unix signal handling, the MPD Unix socket, and the Unix daemon logic, especially `fork` & `exec`&#x2026; if you'd like to run it on Windows, let me know&#x2013; if there's enough interest, and I can get some kind of Windows VM setup, I'll look at a port.
+Windows support may be some time coming; the daemon depends on Unix signal handling, the MPD Unix socket, and the Unix daemon logic, especially `fork` & `exec`… if you'd like to run it on Windows, let me know– if there's enough interest, and I can get some kind of Windows VM setup, I'll look at a port.
-Longer-term, I see [mpdpopm](https://github.com/sp1ff/mpdpopm) as a "dual" to mpd&#x2013; mpd commits to never altering your files. mpdpopm will take on that task in terms of tags, at least. To address the "plumbing, not the sink" problem, I'd like to author a client that will handle player control (of course), but also visualization & tag editing&#x2013; a complete music library solution.
+Longer-term, I see [mpdpopm](https://github.com/sp1ff/mpdpopm) as a "dual" to mpd– mpd commits to never altering your files. mpdpopm will take on that task in terms of tags, at least. To address the "plumbing, not the sink" problem, I'd like to author a client that will handle player control (of course), but also visualization & tag editing– a complete music library solution.
Suggestions, bug reports & PRs welcome!
-
diff --git a/pkgs/by-name/mp/mpdpopm/build.rs b/pkgs/by-name/mp/mpdpopm/build.rs
index 3d8bbd48..04586f29 100644
--- a/pkgs/by-name/mp/mpdpopm/build.rs
+++ b/pkgs/by-name/mp/mpdpopm/build.rs
@@ -9,4 +9,7 @@ fn main() {
.set_out_dir(out_dir)
.process_dir("./")
.unwrap();
+
+ println!("cargo:rerun-if-changed=build.rs");
+ println!("cargo:rerun-if-changed=./src/filters.lalrpop");
}
diff --git a/pkgs/by-name/mp/mpdpopm/config.lsp b/pkgs/by-name/mp/mpdpopm/config.lsp
index 4b657226..d3471a05 100644
--- a/pkgs/by-name/mp/mpdpopm/config.lsp
+++ b/pkgs/by-name/mp/mpdpopm/config.lsp
@@ -1,13 +1,11 @@
-;; SAMPLE MPPOPMD CONFIGURATION FILE -*- mode: lisp; -*-
-;; You will need to edit this to suit your particular installation.
-;; In particular, examine the `log' & `local_music_dir' values since those
-;; are difficult to guess. Also check the `host' & `port' settings.
-((version . "1")
- (log . "/home/soispha/.local/share/mppopmd/log")
- ;; (conn TCP (host . "localhost") (port . 6600))
- ;; Replace the above line with this to use the local socket
- (conn Local (path . "/run/user/1000/mpd/socket"))
- (local_music_dir . "/home/soispha/media/music/beets")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (commands_chan . "unwoundstack.com:commands"))
+{
+ "conn": {
+ "Local": {
+ "path": "/run/user/1000/mpd/socket"
+ }
+ },
+ "mode": "Dj",
+ "local_music_dir": "/home/soispha/media/music/beets",
+ "log": "/home/soispha/.local/share/mpdpopm/log",
+ "version": "1"
+}
diff --git a/pkgs/by-name/mp/mpdpopm/flake.lock b/pkgs/by-name/mp/mpdpopm/flake.lock
deleted file mode 100644
index c1d50dc3..00000000
--- a/pkgs/by-name/mp/mpdpopm/flake.lock
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1769237874,
- "narHash": "sha256-saOixpqPT4fiE/M8EfHv9I98f3sSEvt6nhMJ/z0a7xI=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "523257564973361cc3e55e3df3e77e68c20b0b80",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs",
- "treefmt-nix": "treefmt-nix"
- }
- },
- "treefmt-nix": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768158989,
- "narHash": "sha256-67vyT1+xClLldnumAzCTBvU0jLZ1YBcf4vANRWP3+Ak=",
- "owner": "numtide",
- "repo": "treefmt-nix",
- "rev": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "treefmt-nix",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/mp/mpdpopm/flake.nix b/pkgs/by-name/mp/mpdpopm/flake.nix
deleted file mode 100644
index f6b622fe..00000000
--- a/pkgs/by-name/mp/mpdpopm/flake.nix
+++ /dev/null
@@ -1,66 +0,0 @@
-# Mpdpopm - A mpd rating tracker
-#
-# Copyright (C) 2026 Benedikt Peetz, Michael Herstine <sp1ff@pobox.com> <benedikt.peetz@b-peetz.de, sp1ff@pobox.com>
-# SPDX-License-Identifier: AGPL-3.0-or-later
-#
-# This file is part of Mpdpopm.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/agpl.txt>.
-{
- description = "A mpd rating tracker";
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
-
- treefmt-nix = {
- url = "github:numtide/treefmt-nix";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- };
-
- outputs = {
- self,
- nixpkgs,
- treefmt-nix,
- ...
- }: let
- system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
-
- treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
- in {
- checks."${system}" = {
- formatting = treefmtEval.config.build.check self;
- };
-
- formatter."${system}" = treefmtEval.config.build.wrapper;
-
- devShells."${system}".default = pkgs.mkShell {
- packages = [
- # rust stuff
- pkgs.cargo
- pkgs.clippy
- pkgs.rustc
- pkgs.rustfmt
- pkgs.mold
-
- pkgs.cargo-edit
- pkgs.cargo-expand
- pkgs.cargo-flamegraph
-
- # Releng
- pkgs.git-bug
- pkgs.reuse
- pkgs.cocogitto
-
- # Perf
- pkgs.hyperfine
- ];
- };
- };
-}
-# vim: ts=2
-
diff --git a/pkgs/by-name/mp/mpdpopm/package.nix b/pkgs/by-name/mp/mpdpopm/package.nix
index 907bb1cf..7048f41e 100644
--- a/pkgs/by-name/mp/mpdpopm/package.nix
+++ b/pkgs/by-name/mp/mpdpopm/package.nix
@@ -16,7 +16,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
version = "0.1.0";
buildInputs = [];
- nativeBuildInputs = [ ];
+ nativeBuildInputs = [];
src = ./.;
cargoLock = {
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
new file mode 100644
index 00000000..7ec89441
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/cli.rs
@@ -0,0 +1,232 @@
+use clap::{Parser, Subcommand};
+use std::path::PathBuf;
+
+/// `mppopmd' client
+#[derive(Parser)]
+pub(crate) struct Args {
+ /// path to configuration file
+ #[arg(short, long)]
+ pub(crate) config: Option<PathBuf>,
+
+ /// enable verbose logging
+ #[arg(short, long)]
+ pub(crate) verbose: bool,
+
+ /// enable debug loggin (implies --verbose)
+ #[arg(short, long)]
+ pub(crate) debug: bool,
+
+ #[command(subcommand)]
+ pub(crate) command: SubCommand,
+}
+
+#[derive(Subcommand)]
+pub(crate) enum RatingCommand {
+ /// retrieve the rating for one or more tracks
+ ///
+ /// With no arguments, retrieve the rating of the current song & print it
+ /// on stdout. With one argument, retrieve that track's rating & print it
+ /// on stdout. With multiple arguments, print their ratings on stdout, one
+ /// per line, prefixed by the track name.
+ ///
+ /// Ratings are expressed as an integer between -128 & 128, exclusive, with
+ /// the convention that 0 denotes "un-rated".
+ #[clap(verbatim_doc_comment)]
+ Get {
+ /// Always show the song URI, even when there is only one track
+ #[arg(short, long)]
+ with_uri: bool,
+
+ tracks: Option<Vec<String>>,
+ },
+
+ /// set the rating for one track
+ ///
+ /// With one argument, set the rating of the current song to that argument.
+ /// With a second argument, rate that song at the first argument. Ratings
+ /// may be expressed a an integer between 0 & 255, inclusive.
+ #[clap(verbatim_doc_comment)]
+ Set { rating: i8, track: Option<String> },
+
+ /// increment the rating for one track
+ ///
+ /// With one argument, increment the rating of the current song.
+ /// With a second argument, rate that song at the first argument.
+ #[clap(verbatim_doc_comment)]
+ Inc { track: Option<String> },
+
+ /// decrement the rating for one track
+ ///
+ /// With one argument, decrement the rating of the current song.
+ /// With a second argument, rate that song at the first argument.
+ #[clap(verbatim_doc_comment)]
+ Decr { track: Option<String> },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlayCountCommand {
+ /// retrieve the play count for one or more tracks
+ ///
+ /// With no arguments, retrieve the play count of the current song & print it
+ /// on stdout. With one argument, retrieve that track's play count & print it
+ /// on stdout. With multiple arguments, print their play counts on stdout, one
+ /// per line, prefixed by the track name.
+ #[clap(verbatim_doc_comment)]
+ Get {
+ /// Always show the song URI, even when there is only one track
+ #[arg(short, long)]
+ with_uri: bool,
+
+ tracks: Option<Vec<String>>,
+ },
+
+ /// set the play count for one track
+ ///
+ /// With one argument, set the play count of the current song to that argument. With a
+ /// second argument, set the play count for that song to the first.
+ #[clap(verbatim_doc_comment)]
+ Set {
+ play_count: usize,
+ track: Option<String>,
+ },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum LastPlayedCommand {
+ /// retrieve the last played timestamp for one or more tracks
+ ///
+ /// With no arguments, retrieve the last played timestamp of the current
+ /// song & print it on stdout. With one argument, retrieve that track's
+ /// last played time & print it on stdout. With multiple arguments, print
+ /// their last played times on stdout, one per line, prefixed by the track
+ /// name.
+ ///
+ /// The last played timestamp is expressed in seconds since Unix epoch.
+ #[clap(verbatim_doc_comment)]
+ Get {
+ /// Always show the song URI, even when there is only one track
+ #[arg(short, long)]
+ with_uri: bool,
+
+ tracks: Option<Vec<String>>,
+ },
+
+ /// set the last played timestamp for one track
+ ///
+ /// With one argument, set the last played time of the current song. With two
+ /// arguments, set the last played time for the second argument to the first.
+ /// The last played timestamp is expressed in seconds since Unix epoch.
+ #[clap(verbatim_doc_comment)]
+ Set {
+ last_played: u64,
+ track: Option<String>,
+ },
+}
+
+#[derive(Subcommand)]
+pub(crate) enum PlaylistsCommand {
+ /// retrieve the list of stored playlists
+ #[clap(verbatim_doc_comment)]
+ Get {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum DjCommand {
+ /// Activate the automatic DJ mode on the mpdpopmd daemon.
+ ///
+ /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+ /// recommendation algorithm.
+ #[clap(verbatim_doc_comment)]
+ Start {
+ /// The chance to select a "positive" track
+ #[arg(long, default_value_t = 0.65)]
+ positive_chance: f64,
+
+ /// The chance to select a "neutral" track
+ #[arg(long, default_value_t = 0.5)]
+ neutral_chance: f64,
+
+ /// The chance to select a "negative" track
+ #[arg(long, default_value_t = 0.2)]
+ negative_chance: f64,
+ },
+
+ /// Deactivate the automatic DJ mode on the mpdpopmd daemon.
+ ///
+ /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+ /// recommendation algorithm.
+ #[clap(verbatim_doc_comment)]
+ Stop {},
+}
+
+#[derive(Subcommand)]
+pub(crate) enum SubCommand {
+ /// Change details about rating.
+ Rating {
+ #[command(subcommand)]
+ command: RatingCommand,
+ },
+
+ /// Change details about play count.
+ PlayCount {
+ #[command(subcommand)]
+ command: PlayCountCommand,
+ },
+
+ /// Change details about last played date.
+ LastPlayed {
+ #[command(subcommand)]
+ command: LastPlayedCommand,
+ },
+
+ /// Change details about generated playlists.
+ Playlists {
+ #[command(subcommand)]
+ command: PlaylistsCommand,
+ },
+
+ /// search for songs matching matching a filter and add them to the queue
+ ///
+ /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
+ /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
+ ///
+ /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
+ /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
+ /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
+ ///
+ /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
+ /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
+ ///
+ /// mppopm searchadd "(rating > 2)"
+ ///
+ /// Will add all songs in the library with a rating sticker > 2 to the play queue.
+ ///
+ /// mppopm also introduces OR clauses (MPD only supports AND), so that:
+ ///
+ /// mppopm searchadd "((rating > 2) AND (artist =~ \"pogues\"))"
+ ///
+ /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
+ /// 2.
+ #[clap(verbatim_doc_comment)]
+ Searchadd {
+ filter: String,
+
+ /// Respect the casing, when performing the filter evaluation.
+ #[arg(short, long, default_value_t = false)]
+ case_sensitive: bool,
+ },
+
+ /// Modify the automatic DJ mode on the mpdpopmd daemon.
+ ///
+ /// In this mode, the daemon will automatically add new tracks to the playlist based on a
+ /// recommendation algorithm.
+ Dj {
+ #[command(subcommand)]
+ command: DjCommand,
+ },
+
+ /// Show general stats about your music collection.
+ ///
+ /// This includes favorite artist, songs and also the negative ones.
+ Stats {},
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
index d9d607d5..42f01873 100644
--- a/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
@@ -26,18 +26,38 @@
//! along the lines of [mpdcron](https://alip.github.io/mpdcron)). `mppopm` is a command-line client
//! for `mppopmd`. Run `mppopm --help` for detailed usage.
+use std::{collections::HashMap, io::stdout};
+
+use clap::Parser;
use mpdpopm::{
- clients::{Client, PlayerStatus, quote},
+ clients::{Client, PlayerStatus},
config::{self, Config},
- storage::{last_played, play_count, rating_count},
+ dj::algorithms::Discovery,
+ filters::ExpressionParser,
+ filters_ast::{FilterStickerNames, evaluate},
+ messanges::COMMAND_CHANNEL,
+ storage::{last_played, play_count, rating, skip_count},
};
use anyhow::{Context, Result, anyhow, bail};
-use clap::{Parser, Subcommand};
+use ratatui::{
+ Terminal, TerminalOptions, Viewport,
+ crossterm::style::Stylize,
+ layout::HorizontalAlignment,
+ prelude::CrosstermBackend,
+ style::{Color, Style},
+ text::Line,
+ widgets::{Bar, BarChart, BarGroup, Block, Borders},
+};
use tracing::{debug, info, level_filters::LevelFilter, trace};
use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt};
-use std::path::PathBuf;
+use crate::cli::{
+ Args, DjCommand, LastPlayedCommand, PlayCountCommand, PlaylistsCommand, RatingCommand,
+ SubCommand,
+};
+
+mod cli;
/// Map `tracks' argument(s) to a Vec of String containing one or more mpd URIs
///
@@ -91,7 +111,7 @@ async fn get_ratings(
let mut ratings: Vec<(String, i8)> = Vec::new();
for file in map_tracks(client, tracks).await? {
- let rating = rating_count::get(client, &file).await?;
+ let rating = rating::get(client, &file).await?;
ratings.push((file, rating.unwrap_or_default()));
}
@@ -112,7 +132,7 @@ async fn set_rating(client: &mut Client, rating: i8, arg: Option<String>) -> Res
let is_current = arg.is_none();
let file = provide_file(client, arg).await?;
- rating_count::set(client, &file, rating).await?;
+ rating::set(client, &file, rating).await?;
match is_current {
false => info!("Set the rating for \"{}\" to \"{}\".", file, rating),
@@ -127,9 +147,9 @@ async fn inc_rating(client: &mut Client, arg: Option<String>) -> Result<()> {
let is_current = arg.is_none();
let file = provide_file(client, arg).await?;
- let now = rating_count::get(client, &file).await?;
+ let now = rating::get(client, &file).await?;
- rating_count::set(client, &file, now.unwrap_or_default().saturating_add(1)).await?;
+ rating::set(client, &file, now.unwrap_or_default().saturating_add(1)).await?;
match is_current {
false => info!("Incremented the rating for \"{}\".", file),
@@ -144,9 +164,9 @@ async fn decr_rating(client: &mut Client, arg: Option<String>) -> Result<()> {
let is_current = arg.is_none();
let file = provide_file(client, arg).await?;
- let now = rating_count::get(client, &file).await?;
+ let now = rating::get(client, &file).await?;
- rating_count::set(client, &file, now.unwrap_or_default().saturating_sub(1)).await?;
+ rating::set(client, &file, now.unwrap_or_default().saturating_sub(1)).await?;
match is_current {
false => info!("Decremented the rating for \"{}\".", file),
@@ -263,242 +283,34 @@ async fn get_playlists(client: &mut Client) -> Result<()> {
}
/// Add songs selected by filter to the queue
-async fn findadd(client: &mut Client, chan: &str, filter: &str, case: bool) -> Result<()> {
- let qfilter = quote(filter);
- debug!("findadd: got ``{}'', quoted to ``{}''.", filter, qfilter);
- let cmd = format!("{} {}", if case { "findadd" } else { "searchadd" }, qfilter);
- client.send_message(chan, &cmd).await?;
- Ok(())
-}
-
-/// Send an arbitrary command
-async fn send_command(client: &mut Client, chan: &str, args: Vec<String>) -> Result<()> {
- client
- .send_message(
- chan,
- args.iter()
- .map(String::as_str)
- .map(quote)
- .collect::<Vec<String>>()
- .join(" ")
- .as_str(),
- )
- .await?;
- Ok(())
-}
-
-/// `mppopmd' client
-#[derive(Parser)]
-struct Args {
- /// path to configuration file
- #[arg(short, long)]
- config: Option<PathBuf>,
-
- /// enable verbose logging
- #[arg(short, long)]
- verbose: bool,
-
- /// enable debug loggin (implies --verbose)
- #[arg(short, long)]
- debug: bool,
-
- #[command(subcommand)]
- command: SubCommand,
-}
-
-#[derive(Subcommand)]
-enum RatingCommand {
- /// retrieve the rating for one or more tracks
- ///
- /// With no arguments, retrieve the rating of the current song & print it
- /// on stdout. With one argument, retrieve that track's rating & print it
- /// on stdout. With multiple arguments, print their ratings on stdout, one
- /// per line, prefixed by the track name.
- ///
- /// Ratings are expressed as an integer between 0 & 255, inclusive, with
- /// the convention that 0 denotes "un-rated".
- #[clap(verbatim_doc_comment)]
- Get {
- /// Always show the song URI, even when there is only one track
- #[arg(short, long)]
- with_uri: bool,
-
- tracks: Option<Vec<String>>,
- },
-
- /// set the rating for one track
- ///
- /// With one argument, set the rating of the current song to that argument.
- /// With a second argument, rate that song at the first argument. Ratings
- /// may be expressed a an integer between 0 & 255, inclusive.
- #[clap(verbatim_doc_comment)]
- Set { rating: i8, track: Option<String> },
-
- /// increment the rating for one track
- ///
- /// With one argument, increment the rating of the current song.
- /// With a second argument, rate that song at the first argument.
- #[clap(verbatim_doc_comment)]
- Inc { track: Option<String> },
-
- /// decrement the rating for one track
- ///
- /// With one argument, decrement the rating of the current song.
- /// With a second argument, rate that song at the first argument.
- #[clap(verbatim_doc_comment)]
- Decr { track: Option<String> },
-}
-
-#[derive(Subcommand)]
-enum PlayCountCommand {
- /// retrieve the play count for one or more tracks
- ///
- /// With no arguments, retrieve the play count of the current song & print it
- /// on stdout. With one argument, retrieve that track's play count & print it
- /// on stdout. With multiple arguments, print their play counts on stdout, one
- /// per line, prefixed by the track name.
- #[clap(verbatim_doc_comment)]
- Get {
- /// Always show the song URI, even when there is only one track
- #[arg(short, long)]
- with_uri: bool,
-
- tracks: Option<Vec<String>>,
- },
-
- /// set the play count for one track
- ///
- /// With one argument, set the play count of the current song to that argument. With a
- /// second argument, set the play count for that song to the first.
- #[clap(verbatim_doc_comment)]
- Set {
- play_count: usize,
- track: Option<String>,
- },
-}
-
-#[derive(Subcommand)]
-enum LastPlayedCommand {
- /// retrieve the last played timestamp for one or more tracks
- ///
- /// With no arguments, retrieve the last played timestamp of the current
- /// song & print it on stdout. With one argument, retrieve that track's
- /// last played time & print it on stdout. With multiple arguments, print
- /// their last played times on stdout, one per line, prefixed by the track
- /// name.
- ///
- /// The last played timestamp is expressed in seconds since Unix epoch.
- #[clap(verbatim_doc_comment)]
- Get {
- /// Always show the song URI, even when there is only one track
- #[arg(short, long)]
- with_uri: bool,
-
- tracks: Option<Vec<String>>,
- },
-
- /// set the last played timestamp for one track
- ///
- /// With one argument, set the last played time of the current song. With two
- /// arguments, set the last played time for the second argument to the first.
- /// The last played timestamp is expressed in seconds since Unix epoch.
- #[clap(verbatim_doc_comment)]
- Set {
- last_played: u64,
- track: Option<String>,
- },
-}
-
-#[derive(Subcommand)]
-enum PlaylistsCommand {
- /// retrieve the list of stored playlists
- #[clap(verbatim_doc_comment)]
- Get {},
-}
-
-#[derive(Subcommand)]
-enum SubCommand {
- /// Change details about rating.
- Rating {
- #[command(subcommand)]
- command: RatingCommand,
- },
-
- /// Change details about play count.
- PlayCount {
- #[command(subcommand)]
- command: PlayCountCommand,
- },
+async fn searchadd(client: &mut Client, filter: &str, case_sensitive: bool) -> Result<()> {
+ let ast = match ExpressionParser::new().parse(filter) {
+ Ok(ast) => ast,
+ Err(err) => {
+ bail!("Failed to parse filter: `{}`", err)
+ }
+ };
- /// Change details about last played date.
- LastPlayed {
- #[command(subcommand)]
- command: LastPlayedCommand,
- },
+ debug!("ast: {:#?}", ast);
- /// Change details about generated playlists.
- Playlists {
- #[command(subcommand)]
- command: PlaylistsCommand,
- },
+ let mut results = Vec::new();
+ for song in evaluate(&ast, case_sensitive, client, &FilterStickerNames::default())
+ .await
+ .context("Failed to evaluate filter")?
+ {
+ let out = client.add(&song).await;
- /// search case-sensitively for songs matching matching a filter and add them to the queue
- ///
- /// This command extends the MPD command `findadd' (which will search the MPD database) to allow
- /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
- ///
- /// The MPD `findadd' <https://www.musicpd.org/doc/html/protocol.html#command-findadd> will search the
- /// MPD database for songs that match a given filter & add them to the play queue. The filter syntax is
- /// documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
- ///
- /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
- /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
- ///
- /// mppopm findadd "(rating > 128)"
- ///
- /// Will add all songs in the library with a rating sticker > 128 to the play queue.
- ///
- /// mppopm also introduces OR clauses (MPD only supports AND), so that:
- ///
- /// mppopm findadd "((rating > 128) AND (artist =~ \"pogues\"))"
- ///
- /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
- /// 128.
- ///
- /// `findadd' is case-sensitive; for case-insensitive searching see the `searchadd' command.
- #[clap(verbatim_doc_comment)]
- Findadd { filter: String },
+ if out.is_ok() {
+ eprintln!("Added: `{}`", song)
+ }
- /// search case-insensitively for songs matching matching a filter and add them to the queue
- ///
- /// This command extends the MPD command `searchadd' (which will search the MPD database) to allow
- /// searches on attributes managed by mpdpopm: rating, playcount & last played time.
- ///
- /// The MPD `searchadd' <https://www.musicpd.org/doc/html/protocol.html#command-searchadd> will search
- /// the MPD database for songs that match a given filter & add them to the play queue. The filter syntax
- /// is documented here <https://www.musicpd.org/doc/html/protocol.html#filter-syntax>.
- ///
- /// This command adds three new terms on which you can filter: rating, playcount & lastplayed. Each is
- /// expressed as an unsigned integer, with zero interpreted as "not set". For instance:
- ///
- /// mppopm searchadd "(rating > 128)"
- ///
- /// Will add all songs in the library with a rating sticker > 128 to the play queue.
- ///
- /// mppopm also introduces OR clauses (MPD only supports AND), so that:
- ///
- /// mppopm searchadd "((rating > 128) AND (artist =~ \"pogues\"))"
- ///
- /// will add all songs whose artist tag matches the regexp "pogues" with a rating greater than
- /// 128.
- ///
- /// `searchadd' is case-insensitive; for case-sensitive searching see the `findadd' command.
- #[clap(verbatim_doc_comment)]
- Searchadd { filter: String },
+ results.push(out);
+ }
- /// Send a command to mpd.
- #[clap(verbatim_doc_comment)]
- SendCommand { args: Vec<String> },
+ match results.into_iter().collect::<Result<Vec<()>>>() {
+ Ok(_) => Ok(()),
+ Err(err) => Err(err),
+ }
}
#[tokio::main]
@@ -584,14 +396,196 @@ async fn main() -> Result<()> {
SubCommand::Playlists { command } => match command {
PlaylistsCommand::Get {} => get_playlists(&mut client).await,
},
- SubCommand::Findadd { filter } => {
- findadd(&mut client, &config.commands_chan, &filter, true).await
- }
- SubCommand::Searchadd { filter } => {
- findadd(&mut client, &config.commands_chan, &filter, false).await
- }
- SubCommand::SendCommand { args } => {
- send_command(&mut client, &config.commands_chan, args).await
+ SubCommand::Searchadd {
+ filter,
+ case_sensitive,
+ } => searchadd(&mut client, &filter, case_sensitive).await,
+ SubCommand::Dj { command } => match command {
+ DjCommand::Start {
+ positive_chance,
+ neutral_chance,
+ negative_chance,
+ } => {
+ client
+ .send_message(
+ COMMAND_CHANNEL,
+ format!(
+ "dj start \
+ --positive-chance {positive_chance} \
+ --neutral-chance {neutral_chance} \
+ --negative-chance {negative_chance}"
+ )
+ .as_str(),
+ )
+ .await
+ }
+ DjCommand::Stop {} => client.send_message(COMMAND_CHANNEL, "dj stop").await,
+ },
+ SubCommand::Stats {} => {
+ struct Rating {
+ play_count: Option<usize>,
+ skip_count: Option<usize>,
+ last_played: Option<u64>,
+ rating: Option<i8>,
+ dj_weight: i64,
+ }
+ fn vertical_bar<'a>(count: i64, amount: usize) -> Bar<'a> {
+ fn amount_style(amount: usize) -> Style {
+ let green = (255.0 * (1.0 - ((amount as f64) - 50.0) / 40.0)) as u8;
+ let color = Color::Rgb(255, green, 0);
+
+ Style::new().fg(color)
+ }
+
+ Bar::default()
+ .value(amount as u64)
+ .label(Line::from(count.to_string()))
+ .style(amount_style(amount))
+ .value_style(amount_style(amount).reversed())
+ }
+ macro_rules! top_five {
+ ($(@$convert:tt)? mode = $mode:tt, $rating_map:expr, $key:ident, $($other:ident),* $(,)?) => {
+ let mut vec = $rating_map
+ .iter()
+ .filter_map(|(track, rating)| top_five!(@convert $($convert)? rating.$key).map(|v| (track, v, rating)))
+ .collect::<Vec<_>>();
+ vec.sort_by_key(|(_, pc, _)| *pc);
+
+ top_five!(@gen_mode $mode, vec.iter())
+ .take(5)
+ .for_each(|(song, play_count, rating)| {
+ println!(
+ concat!(" - {}: {}", $(top_five!(@gen_empty $other)),*),
+ <String as Clone>::clone(&song).bold().blue(),
+ play_count.to_string().bold().white(),
+ $(
+ rating
+ .$other
+ .map(|r| format!(" ({}: {r})", stringify!($other)))
+ .unwrap_or(String::new()),
+ )*
+ )
+ });
+ };
+ (@gen_mode top, $expr:expr) => {
+ $expr.rev()
+ };
+ (@gen_mode bottom, $expr:expr) => {
+ $expr
+ };
+ (@gen_empty $tt:tt) => {
+ "{}"
+ };
+ (@convert convert_to_option $tt:expr) => {
+ Some($tt)
+ };
+ (@convert $tt:expr) => {
+ $tt
+ }
+ }
+ macro_rules! histogram {
+ ($(@$convert:tt)? $rating_map:expr, $key:ident, $title:literal) => {
+ let backend = CrosstermBackend::new(stdout());
+ let viewport = Viewport::Inline(20);
+ let mut terminal =
+ Terminal::with_options(backend, TerminalOptions { viewport })?;
+
+ let result = (|| {
+ terminal.draw(|frame| {
+ let line_chart = frame.area();
+
+ let bars: Vec<Bar> = {
+ let mut map = HashMap::new();
+ $rating_map
+ .values()
+ .filter_map(|rating| histogram!(@convert $($convert)? rating.$key))
+ .for_each(|dj_weight| {
+ map.entry(dj_weight)
+ .and_modify(|e| {
+ *e += 1;
+ })
+ .or_insert(1);
+ });
+
+ let mut vec = map.into_iter().collect::<Vec<(_, _)>>();
+ vec.sort_by_key(|(pc, _)| *pc);
+
+ vec.into_iter()
+ }
+ .map(|(dj_weight, amount)| vertical_bar(dj_weight.try_into().expect("Should be convertible"), amount))
+ .collect();
+
+ let title = Line::from($title).centered();
+ let chart = BarChart::default()
+ .data(BarGroup::default().bars(&bars))
+ .block(
+ Block::new()
+ .title(title)
+ .title_alignment(HorizontalAlignment::Left)
+ .borders(Borders::all()),
+ )
+ .bar_width(5);
+
+ frame.render_widget(chart, line_chart);
+ })?;
+
+ Ok::<_, anyhow::Error>(())
+ })();
+
+ ratatui::restore();
+ println!();
+ result?;
+ };
+ (@convert convert_to_option $val:expr) => {
+ Some($val)
+ };
+ (@convert $val:expr) => {
+ $val
+ };
+ }
+
+ let all = client.get_all_songs().await?;
+
+ let mut rating_map = HashMap::new();
+ for song in &all {
+ let rating = Rating {
+ play_count: play_count::get(&mut client, song).await?,
+ skip_count: skip_count::get(&mut client, song).await?,
+ last_played: last_played::get(&mut client, song).await?,
+ rating: rating::get(&mut client, song).await?,
+ dj_weight: Discovery::weight_track(&mut client, song).await?,
+ };
+ rating_map.insert(song, rating);
+ }
+
+ let played_songs = rating_map
+ .values()
+ .filter(|s| s.last_played.is_some())
+ .count();
+
+ println!(
+ "Songs played: {:.2}%",
+ (played_songs as f64 / all.len() as f64) * 100.0
+ );
+
+ histogram!(rating_map, play_count, "Play counts");
+
+ println!("\nMost played songs:");
+ top_five!(mode = top, rating_map, play_count, skip_count, rating);
+
+ println!("\nMost skipped songs:");
+ top_five!(mode = top, rating_map, skip_count, play_count, rating);
+
+ println!("\nTop songs based on dj weight:");
+ top_five!(@convert_to_option mode = top, rating_map, dj_weight, rating, play_count, skip_count);
+
+ println!("\nBottom 5 songs based on dj weight:");
+ top_five!(@convert_to_option mode = bottom, rating_map, dj_weight, rating, play_count, skip_count);
+
+ println!();
+ histogram!(@convert_to_option rating_map, dj_weight, "Dj weights");
+
+ Ok(())
}
}
}
diff --git a/pkgs/by-name/mp/mpdpopm/src/clients.rs b/pkgs/by-name/mp/mpdpopm/src/clients.rs
index b88e4041..b934714a 100644
--- a/pkgs/by-name/mp/mpdpopm/src/clients.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/clients.rs
@@ -716,9 +716,7 @@ impl Client {
}
#[cfg(test)]
-/// Let's test Client!
mod client_tests {
-
use super::test_mock::Mock;
use super::*;
diff --git a/pkgs/by-name/mp/mpdpopm/src/config.rs b/pkgs/by-name/mp/mpdpopm/src/config.rs
index 2d9c466b..8bb5abfb 100644
--- a/pkgs/by-name/mp/mpdpopm/src/config.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/config.rs
@@ -56,7 +56,16 @@ pub enum Connection {
impl Connection {
pub fn new() -> Result<Self> {
- let env = env::var("MPD_HOST")?;
+ let env = match env::var("MPD_HOST") {
+ Ok(env) => Some(env),
+ Err(err) => match err {
+ env::VarError::NotPresent => None,
+ env::VarError::NotUnicode(_) => {
+ bail!("Failed to get `MPD_HOST` env var: {err}")
+ }
+ },
+ }
+ .unwrap_or("/run/mpd/socket".to_owned());
if env.starts_with("/") {
// We assume that this is a path to a local socket
@@ -107,6 +116,17 @@ mod test_connection {
}
}
+/// THe possible start-up mode.
+#[derive(Default, Deserialize, Debug, Serialize)]
+pub enum Mode {
+ #[default]
+ /// Don't do anything special
+ Normal,
+
+ /// Already start the DJ mode on start-up
+ Dj,
+}
+
/// This is the most recent `mppopmd` configuration struct.
#[derive(Deserialize, Debug, Serialize)]
#[serde(default)]
@@ -124,6 +144,9 @@ pub struct Config {
/// How to connect to mpd
pub conn: Connection,
+ /// The mode to start in
+ pub mode: Mode,
+
/// The `mpd' root music directory, relative to the host on which *this* daemon is running
pub local_music_dir: PathBuf,
@@ -133,9 +156,6 @@ pub struct Config {
/// The interval, in milliseconds, at which to poll `mpd' for the current state
pub poll_interval_ms: u64,
-
- /// Channel to setup for assorted commands-- channel names must satisfy "[-a-zA-Z-9_.:]+"
- pub commands_chan: String,
}
impl Default for Config {
@@ -153,7 +173,7 @@ impl Config {
local_music_dir: [PREFIX, "Music"].iter().collect(),
played_thresh: 0.6,
poll_interval_ms: 5000,
- commands_chan: String::from("unwoundstack.com:commands"),
+ mode: Mode::default(),
})
}
}
@@ -167,111 +187,3 @@ pub fn from_str(text: &str) -> Result<Config> {
};
Ok(cfg)
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- #[ignore = "We changed the config format to json"]
- fn test_from_str() {
- let cfg = Config::default();
- assert_eq!(cfg.commands_chan, String::from("unwoundstack.com:commands"));
-
- assert_eq!(
- serde_json::to_string(&cfg).unwrap(),
- format!(
- r#"((version . "1") (log . "{}/log/mppopmd.log") (conn TCP (host . "localhost") (port . 6600)) (local_music_dir . "{}/Music") (playcount_sticker . "unwoundstack.com:playcount") (lastplayed_sticker . "unwoundstack.com:lastplayed") (played_thresh . 0.6) (poll_interval_ms . 5000) (commands_chan . "unwoundstack.com:commands") (playcount_command . "") (playcount_command_args) (rating_sticker . "unwoundstack.com:rating") (ratings_command . "") (ratings_command_args) (gen_cmds))"#,
- LOCALSTATEDIR, PREFIX
- )
- );
-
- let cfg: Config = serde_json::from_str(
- r#"
-((version . "1")
- (log . "/usr/local/var/log/mppopmd.log")
- (conn TCP (host . "localhost") (port . 6600))
- (local_music_dir . "/usr/local/Music")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (commands_chan . "unwoundstack.com:commands")
- (playcount_command . "")
- (playcount_command_args)
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "")
- (ratings_command_args)
- (gen_cmds))
-"#,
- )
- .unwrap();
- assert_eq!(cfg._version, String::from("1"));
-
- let cfg: Config = serde_json::from_str(
- r#"
-((version . "1")
- (log . "/usr/local/var/log/mppopmd.log")
- (conn Local (path . "/home/mgh/var/run/mpd/mpd.sock"))
- (local_music_dir . "/usr/local/Music")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (commands_chan . "unwoundstack.com:commands")
- (playcount_command . "")
- (playcount_command_args)
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "")
- (ratings_command_args)
- (gen_cmds))
-"#,
- )
- .unwrap();
- assert_eq!(cfg._version, String::from("1"));
- assert_eq!(
- cfg.conn,
- Connection::Local {
- path: PathBuf::from("/home/mgh/var/run/mpd/mpd.sock")
- }
- );
-
- // Test fallback to "v0" of the config struct
- let cfg = from_str(r#"
-((log . "/home/mgh/var/log/mppopmd.log")
- (host . "192.168.1.14")
- (port . 6600)
- (local_music_dir . "/space/mp3")
- (playcount_sticker . "unwoundstack.com:playcount")
- (lastplayed_sticker . "unwoundstack.com:lastplayed")
- (played_thresh . 0.6)
- (poll_interval_ms . 5000)
- (playcount_command . "/usr/local/bin/scribbu")
- (playcount_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-C" "%playcount" "%full-file"))
- (commands_chan . "unwoundstack.com:commands")
- (rating_sticker . "unwoundstack.com:rating")
- (ratings_command . "/usr/local/bin/scribbu")
- (ratings_command_args . ("popm" "-v" "-a" "-f" "-o" "sp1ff@pobox.com" "-r" "%rating" "%full-file"))
- (gen_cmds .
- (((name . "set-genre")
- (formal_parameters . (Literal Track))
- (default_after . 1)
- (cmd . "/usr/local/bin/scribbu")
- (args . ("genre" "-a" "-C" "-g" "%1" "%full-file"))
- (update . TrackOnly))
- ((name . "set-xtag")
- (formal_parameters . (Literal Track))
- (default_after . 1)
- (cmd . "/usr/local/bin/scribbu")
- (args . ("xtag" "-A" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
- (update . TrackOnly))
- ((name . "merge-xtag")
- (formal_parameters . (Literal Track))
- (default_after . 1)
- (cmd . "/usr/local/bin/scribbu")
- (args . ("xtag" "-m" "-o" "sp1ff@pobox.com" "-T" "%1" "%full-file"))
- (update . TrackOnly)))))
-"#).unwrap();
- assert_eq!(cfg.log, PathBuf::from("/home/mgh/var/log/mppopmd.log"));
- }
-}
diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
new file mode 100644
index 00000000..0004fd17
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
@@ -0,0 +1,237 @@
+use std::{
+ collections::HashSet,
+ time::{Duration, SystemTime},
+};
+
+use anyhow::{Context, Result};
+use rand::{RngExt, distr, seq::SliceRandom};
+use tracing::info;
+
+use crate::{clients::Client, storage};
+
+pub(crate) trait Algorithm {
+ async fn next_track(&mut self, client: &mut Client) -> Result<String>;
+}
+
+/// Generates generic discovery playlist, that fulfills following requirements:
+/// - Will (eventually) include every not-played song. (So it can be used to rank a library)
+/// - Returns liked songs more often then not-played or negative songs.
+pub struct Discovery {
+ already_done: HashSet<String>,
+ negative_chance: f64,
+ neutral_chance: f64,
+ positive_chance: f64,
+}
+
+impl Algorithm for Discovery {
+ async fn next_track(&mut self, client: &mut Client) -> Result<String> {
+ macro_rules! take {
+ ($rng:expr, $from:expr) => {{
+ info!(concat!(
+ "Trying to select a `",
+ stringify!($from),
+ "` track."
+ ));
+
+ assert!(!$from.is_empty());
+
+ let normalized_weights = {
+ // We normalize the weights here, because negative values don't work for the
+ // distribution function we use below.
+ // "-5" "-3" "1" "6" "19" | +5
+ // -> "0" "2" "6" "11" "24"
+ let mut weights = $from.iter().map(|(_, w)| *w).collect::<Vec<_>>();
+
+ weights.sort_by_key(|w| *w);
+
+ let first = *weights.first().expect(
+ "the value to exist, because we never run `take!` with an empty vector",
+ );
+
+ if first.is_negative() {
+ weights
+ .into_iter()
+ .rev()
+ .map(|w| w + first.abs())
+ .collect::<Vec<_>>()
+ } else {
+ weights
+ }
+ };
+
+ let sample = $rng.sample(
+ distr::weighted::WeightedIndex::new(normalized_weights.iter())
+ .expect("to be okay, because the weights are normalized"),
+ );
+
+ let output = $from.remove(sample);
+
+ info!(
+ concat!(
+ "(",
+ stringify!($from),
+ ") Selected `{}` with weight: `{}` (normalized to `{}`)"
+ ),
+ output.0, output.1, normalized_weights[sample]
+ );
+
+ Ok::<_, anyhow::Error>(output)
+ }};
+ }
+
+ let mut rng = rand::rng();
+ let (mut positive, mut neutral, mut negative) = {
+ let tracks = {
+ let mut base = client
+ .get_all_songs()
+ .await?
+ .into_iter()
+ .filter(|song| !self.already_done.contains(song))
+ .collect::<Vec<_>>();
+
+ if base.is_empty() {
+ // We could either have no tracks in the library,
+ // or we actually already listed to everything.
+ self.already_done = HashSet::new();
+
+ info!("Resetting already done songs, as we have no more to choose from");
+
+ base = client.get_all_songs().await?;
+ }
+
+ base
+ };
+
+ let mut sorted_tracks = Vec::with_capacity(tracks.len());
+ for track in tracks {
+ let weight = Self::weight_track(client, &track).await?;
+
+ sorted_tracks.push((track, weight));
+ }
+
+ sorted_tracks.sort_by_key(|(_, weight)| *weight);
+
+ let len = sorted_tracks.len() / 3;
+
+ // We split the tracks into three thirds, so that we can also force a pick from e.g.
+ // the lower third (the negative ones).
+ let mut negative = sorted_tracks.drain(..len).collect::<Vec<_>>();
+ let mut neutral = sorted_tracks.drain(..len).collect::<Vec<_>>();
+ let mut positive = sorted_tracks;
+
+ negative.shuffle(&mut rng);
+ neutral.shuffle(&mut rng);
+ positive.shuffle(&mut rng);
+
+ assert_eq!(negative.len(), neutral.len());
+
+ (positive, neutral, negative)
+ };
+
+ let pick = rng.sample(
+ distr::weighted::WeightedIndex::new(
+ [
+ self.positive_chance,
+ self.neutral_chance,
+ self.negative_chance,
+ ]
+ .iter(),
+ )
+ .expect("to be valid, as hardcoded"),
+ );
+
+ let next = match pick {
+ 0 if !positive.is_empty() => take!(rng, positive),
+ 1 if !neutral.is_empty() => take!(rng, neutral),
+ 2 if !negative.is_empty() => take!(rng, negative),
+ 0..=2 => {
+ // We couldn't actually satisfy the request, because we didn't have the required
+ // track. So we just use the first non-empty one.
+ if !positive.is_empty() {
+ take!(rng, positive)
+ } else if !neutral.is_empty() {
+ take!(rng, neutral)
+ } else if !negative.is_empty() {
+ take!(rng, negative)
+ } else {
+ assert!(positive.is_empty() && neutral.is_empty() && negative.is_empty());
+ todo!("No songs available to select from, I don't know how to select one.");
+ }
+ }
+ _ => unreachable!("These indexes are not possible"),
+ }?;
+
+ self.already_done.insert(next.0.to_owned());
+
+ Ok(next.0)
+ }
+}
+
+impl Discovery {
+ pub(crate) fn new(positive_chance: f64, neutral_chance: f64, negative_chance: f64) -> Self {
+ Self {
+ already_done: HashSet::new(),
+ positive_chance,
+ neutral_chance,
+ negative_chance,
+ }
+ }
+
+ /// Calculate a recommendation score for a track.
+ ///
+ /// The algorithm maps tracks, that the user likes to a high score and songs that the user
+ /// dislikes to a lower number.
+ /// Currently, only the rating, skip count and play count are considered. Similarity scores,
+ /// fetched from e.g. last.fm should be included in the future.
+ pub async fn weight_track(client: &mut Client, track: &str) -> Result<i64> {
+ let last_played_delta = {
+ let last_played = storage::last_played::get(client, track).await?.unwrap_or(0);
+ let now = SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .expect("to be before")
+ .as_secs();
+
+ let played_seconds_ago = now - last_played;
+
+ const HOUR: u64 = Duration::from_hours(1).as_secs();
+ const DAY: u64 = Duration::from_hours(24).as_secs();
+ const MONTH: u64 = Duration::from_hours(24 * 30).as_secs();
+
+ match played_seconds_ago {
+ ..HOUR => {
+ // it was played in the last hour already
+ -3
+ }
+ HOUR..DAY => {
+ // it was not played in the last hour, but in the last day
+ -2
+ }
+ DAY..MONTH => {
+ // it was not played in the last day, but in the last month
+ -1
+ }
+ MONTH.. => {
+ // it was not played in a month
+ 1
+ }
+ }
+ };
+
+ let rating = i32::from(storage::rating::get(client, track).await?.unwrap_or(0));
+ let play_count = i32::try_from(storage::play_count::get(client, track).await?.unwrap_or(0))
+ .context("`play_count` too big")?;
+ let skip_count = i32::try_from(storage::skip_count::get(client, track).await?.unwrap_or(0))
+ .context("`skip_count` too big")?;
+
+ let output: f64 = 1.0 * f64::from(rating)
+ + 0.3 * f64::from(play_count)
+ + -0.6 * f64::from(skip_count)
+ + 0.65 * f64::from(last_played_delta);
+
+ let weight = output.round() as i64;
+
+ // info!("`{track}`: {weight}");
+
+ Ok(weight)
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs
new file mode 100644
index 00000000..548ed4f4
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/dj/mod.rs
@@ -0,0 +1,28 @@
+use anyhow::Result;
+use tracing::info;
+
+use crate::{clients::Client, dj::algorithms::Algorithm};
+
+pub mod algorithms;
+
+pub(crate) struct Dj<A: Algorithm> {
+ algo: A,
+}
+
+impl<A: Algorithm> Dj<A> {
+ pub(crate) fn new(algo: A) -> Self {
+ Self { algo }
+ }
+
+ /// Add the next track to the playlist.
+ ///
+ /// This should be called after the previous track is finished, to avoid unbounded growth.
+ pub(crate) async fn add_track(&mut self, client: &mut Client) -> Result<()> {
+ let next = self.algo.next_track(client).await?;
+
+ info!("Adding `{next}`, due to active dj mode");
+ client.add(&next).await?;
+
+ Ok(())
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop
index a591a3ba..970fc040 100644
--- a/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop
+++ b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop
@@ -15,8 +15,18 @@
use lalrpop_util::ParseError;
-use crate::filters_ast::{Conjunction, Disjunction, Expression, OpCode, Selector, Term, Value,
- expect_quoted, parse_iso_8601};
+use crate::filters_ast::{
+ Conjunction,
+ Disjunction,
+ Expression,
+ OpCode,
+ Selector,
+ Term,
+ Value,
+ expect_quoted,
+ parse_iso_8601
+};
+use tracing::debug;
grammar;
@@ -36,7 +46,7 @@ pub ExprSel: Selector = {
r"(?i)artist" => Selector::Artist,
r"(?i)album" => Selector::Album,
r"(?i)albumartist" => Selector::AlbumArtist,
- r"(?i)titile" => Selector::Title,
+ r"(?i)title" => Selector::Title,
r"(?i)track" => Selector::Track,
r"(?i)name" => Selector::Name,
r"(?i)genre" => Selector::Genre,
@@ -63,20 +73,27 @@ pub ExprSel: Selector = {
r"(?i)rating" => Selector::Rating,
r"(?i)playcount" => Selector::PlayCount,
r"(?i)lastplayed" => Selector::LastPlayed,
+ r"(?i)skipped" => Selector::Skipped,
};
pub Token: Value = {
- <s:r"[0-9]+"> =>? {
- eprintln!("matched token: ``{}''.", s);
+ <s:r"(-)?[0-9]+"> =>? {
+ debug!("matched token: ``{}''.", s);
// We need to yield a Result<Value, ParseError>
match s.parse::<usize>() {
Ok(n) => Ok(Value::Uint(n)),
- Err(_) => Err(ParseError::User {
- error: "Internal parse error while parsing unsigned int" })
+ Err(_) => match s.parse::<i64>() {
+ Ok(n) => Ok(Value::Int(n)),
+ Err(_) => Err(
+ ParseError::User {
+ error: "Internal parse error while parsing unsigned int"
+ }
+ )
+ }
}
},
<s:r#""([ \t'a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\"|\\')+""#> => {
- eprintln!("matched token: ``{}''.", s);
+ debug!("matched token: ``{}''.", s);
let s = expect_quoted(s).unwrap();
match parse_iso_8601(&mut s.as_bytes()) {
Ok(x) => Value::UnixEpoch(x),
@@ -84,7 +101,7 @@ pub Token: Value = {
}
},
<s:r#"'([ \t"a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\'|\\")+'"#> => {
- eprintln!("matched token: ``{}''.", s);
+ debug!("matched token: ``{}''.", s);
let s = expect_quoted(s).unwrap();
match parse_iso_8601(&mut s.as_bytes()) {
Ok(x) => Value::UnixEpoch(x),
@@ -94,50 +111,50 @@ pub Token: Value = {
};
pub Term: Box<Term> = {
- <t:ExprSel> <u:Token> => {
- eprintln!("matched unary condition: ``({}, {:#?})''", t, u);
- Box::new(Term::UnaryCondition(t, u))
- },
<t:ExprSel> <o:ExprOp> <u:Token> => {
- eprintln!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u);
+ debug!("matched binary condition: ``({}, {:#?}, {:#?})''", t, o, u);
Box::new(Term::BinaryCondition(t, o, u))
},
+ <t:ExprSel> <u:Token> => {
+ debug!("matched unary condition: ``({}, {:#?})''", t, u);
+ Box::new(Term::UnaryCondition(t, u))
+ },
}
pub Conjunction: Box<Conjunction> = {
<e1:Expression> "AND" <e2:Expression> => {
- eprintln!("matched conjunction: ``({:#?}, {:#?})''", e1, e2);
+ debug!("matched conjunction: ``({:#?}, {:#?})''", e1, e2);
Box::new(Conjunction::Simple(e1, e2))
},
<c:Conjunction> "AND" <e:Expression> => {
- eprintln!("matched conjunction: ``({:#?}, {:#?})''", c, e);
+ debug!("matched conjunction: ``({:#?}, {:#?})''", c, e);
Box::new(Conjunction::Compound(c, e))
},
}
pub Disjunction: Box<Disjunction> = {
<e1:Expression> "OR" <e2:Expression> => {
- eprintln!("matched disjunction: ``({:#?}, {:#?})''", e1, e2);
+ debug!("matched disjunction: ``({:#?}, {:#?})''", e1, e2);
Box::new(Disjunction::Simple(e1, e2))
},
<c:Disjunction> "OR" <e:Expression> => {
- eprintln!("matched disjunction: ``({:#?}, {:#?})''", c, e);
+ debug!("matched disjunction: ``({:#?}, {:#?})''", c, e);
Box::new(Disjunction::Compound(c, e))
},
}
pub Expression: Box<Expression> = {
"(" <t:Term> ")" => {
- eprintln!("matched parenthesized term: ``({:#?})''", t);
+ debug!("matched parenthesized term: ``({:#?})''", t);
Box::new(Expression::Simple(t))
},
"(" "!" <e:Expression> ")" => Box::new(Expression::Negation(e)),
"(" <c:Conjunction> ")" => {
- eprintln!("matched parenthesized conjunction: ``({:#?})''", c);
+ debug!("matched parenthesized conjunction: ``({:#?})''", c);
Box::new(Expression::Conjunction(c))
},
"(" <c:Disjunction> ")" => {
- eprintln!("matched parenthesized disjunction: ``({:#?})''", c);
+ debug!("matched parenthesized disjunction: ``({:#?})''", c);
Box::new(Expression::Disjunction(c))
},
}
diff --git a/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs
index bd1a67d6..9c68d329 100644
--- a/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs
@@ -18,7 +18,7 @@
//! This module provides support for our [lalrpop](https://github.com/lalrpop/lalrpop) grammar.
use crate::clients::Client;
-use crate::storage::{last_played, play_count, rating_count};
+use crate::storage::{last_played, play_count, rating, skip_count};
use anyhow::{Context, Error, Result, anyhow, bail};
use boolinator::Boolinator;
@@ -95,6 +95,7 @@ pub enum Selector {
Rating,
PlayCount,
LastPlayed,
+ Skipped,
}
impl std::fmt::Display for Selector {
@@ -133,6 +134,7 @@ impl std::fmt::Display for Selector {
Selector::Rating => "rating",
Selector::PlayCount => "playcount",
Selector::LastPlayed => "lastplayed",
+ Selector::Skipped => "skipped",
}
)
}
@@ -143,6 +145,7 @@ pub enum Value {
Text(String),
UnixEpoch(i64),
Uint(usize),
+ Int(i64),
}
fn quote_value(x: &Value) -> String {
@@ -166,6 +169,9 @@ fn quote_value(x: &Value) -> String {
Value::Uint(n) => {
format!("'{}'", n)
}
+ Value::Int(n) => {
+ format!("'{}'", n)
+ }
}
}
@@ -655,6 +661,7 @@ async fn eval_numeric_sticker_term<
.for_each(|song| {
m.entry(song).or_insert(default_val);
});
+
// Now that we don't have to worry about operations that can fail, we can use
// `filter_map'.
Ok(m.drain()
@@ -674,6 +681,7 @@ pub struct FilterStickerNames<'a> {
rating: &'a str,
playcount: &'a str,
lastplayed: &'a str,
+ skipped: &'a str,
}
impl FilterStickerNames<'static> {
@@ -685,9 +693,10 @@ impl FilterStickerNames<'static> {
impl Default for FilterStickerNames<'static> {
fn default() -> Self {
Self {
- rating: rating_count::STICKER,
+ rating: rating::STICKER,
playcount: play_count::STICKER,
lastplayed: last_played::STICKER,
+ skipped: skip_count::STICKER,
}
}
}
@@ -711,18 +720,19 @@ async fn eval_term<'a>(
.collect()),
Term::BinaryCondition(attr, op, val) => {
if *attr == Selector::Rating {
- match val {
- Value::Uint(n) => {
- if *n > 255 {
- bail!("Rating of `{}` is greater than allowed!", n)
- }
- Ok(
- eval_numeric_sticker_term(stickers.rating, client, *op, *n as u8, 0)
- .await?,
- )
- }
- _ => bail!("filter ratings expect an unsigned int; got {:#?}", val),
- }
+ let value = match val {
+ Value::Int(n) => *n as i128,
+ Value::Uint(n) => *n as i128,
+ _ => bail!("filter ratings expect an int; got {:#?}", val),
+ };
+
+ let val: i8 = value.try_into().with_context(|| {
+ format!(
+ "Failed to convert `{}` into a number from -128 to 128!",
+ value
+ )
+ })?;
+ Ok(eval_numeric_sticker_term(stickers.rating, client, *op, val, 0).await?)
} else if *attr == Selector::PlayCount {
match val {
Value::Uint(n) => {
@@ -731,7 +741,7 @@ async fn eval_term<'a>(
.await?,
)
}
- _ => bail!("filter ratings expect an unsigned int; got {:#?}", val),
+ _ => bail!("filter play_count expect an unsigned int; got {:#?}", val),
}
} else if *attr == Selector::LastPlayed {
match val {
@@ -741,7 +751,14 @@ async fn eval_term<'a>(
.await?,
)
}
- _ => bail!("filter ratings expect an unsigned int; got {:#?}", val),
+ _ => bail!("filter last_played expect an unix epoch; got {:#?}", val),
+ }
+ } else if *attr == Selector::Skipped {
+ match val {
+ Value::Uint(t) => {
+ Ok(eval_numeric_sticker_term(stickers.skipped, client, *op, *t, 0).await?)
+ }
+ _ => bail!("filter skipped expect an unsigned int; got {:#?}", val),
}
} else {
Ok(client
diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs
index 4fe523ea..6d04a527 100644
--- a/pkgs/by-name/mp/mpdpopm/src/lib.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs
@@ -34,8 +34,9 @@
pub mod clients;
pub mod config;
+pub mod dj;
pub mod filters_ast;
-pub mod messages;
+pub mod messanges;
pub mod playcounts;
pub mod storage;
pub mod vars;
@@ -46,14 +47,13 @@ pub mod vars;
#[allow(clippy::let_unit_value)]
#[allow(clippy::just_underscores_and_digits)]
pub mod filters {
- include!(concat!(env!("OUT_DIR"), "/src/filters.rs"));
+ include!(concat!(env!("OUT_DIR"), "/filters.rs"));
}
use crate::{
clients::{Client, IdleClient, IdleSubSystem},
config::{Config, Connection},
- filters_ast::FilterStickerNames,
- messages::MessageProcessor,
+ messanges::{COMMAND_CHANNEL, MessageQueue},
playcounts::PlayState,
};
@@ -70,8 +70,6 @@ use tracing::{debug, error, info};
pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
info!("mpdpopm {} beginning.", vars::VERSION);
- let filter_stickers = FilterStickerNames::new();
-
let mut client =
match cfg.conn {
Connection::Local { ref path } => Client::open(path)
@@ -95,8 +93,10 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
.context("Failed to connect to TCP idle client")?,
};
+ let mut mqueue = MessageQueue::new(cfg.mode);
+
idle_client
- .subscribe(&cfg.commands_chan)
+ .subscribe(COMMAND_CHANNEL)
.await
.context("Failed to subscribe to idle_client")?;
@@ -110,12 +110,10 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
let tick = sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse();
pin_mut!(ctrl_c, sighup, sigkill, tick);
- let mproc = MessageProcessor::new();
-
let mut done = false;
+ let mut msg_check_needed = false;
while !done {
debug!("selecting...");
- let mut msg_check_needed = false;
{
// `idle_client' mutably borrowed here
let mut idle = Box::pin(idle_client.idle().fuse());
@@ -140,33 +138,20 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
tick.set(sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse());
state.update(&mut client)
.await
- .context("PlayState update failed")?
+ .context("PlayState update failed")?;
},
- // next = cmds.next() => match next {
- // Some(out) => {
- // debug!("output status is {:#?}", out.out);
- // match out.upd {
- // Some(uri) => {
- // debug!("{} needs to be updated", uri);
- // client.update(&uri).await.map_err(|err| Error::Client {
- // source: err,
- // back: Backtrace::new(),
- // })?;
- // },
- // None => debug!("No database update needed"),
- // }
- // },
- // None => {
- // debug!("No more commands to process.");
- // }
- // },
res = idle => match res {
Ok(subsys) => {
debug!("subsystem {} changed", subsys);
if subsys == IdleSubSystem::Player {
- state.update(&mut client)
+ if state.update(&mut client)
.await
- .context("PlayState update failed")?
+ .context("PlayState update failed")? {
+ mqueue
+ .advance_dj(&mut client)
+ .await
+ .context("MessageQueue tick failed")?;
+ }
} else if subsys == IdleSubSystem::Message {
msg_check_needed = true;
}
@@ -183,19 +168,12 @@ pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
}
if msg_check_needed {
+ msg_check_needed = false;
+
// Check for any messages that have come in; if there's an error there's not a lot we
// can do about it (suppose some client fat-fingers a command name, e.g.)-- just log it
// & move on.
- if let Err(err) = mproc
- .check_messages(
- &mut client,
- &mut idle_client,
- state.last_status(),
- &cfg.commands_chan,
- &filter_stickers,
- )
- .await
- {
+ if let Err(err) = mqueue.check_messages(&mut client, &mut idle_client).await {
error!("Error while processing messages: {err:#?}");
}
}
diff --git a/pkgs/by-name/mp/mpdpopm/src/messages.rs b/pkgs/by-name/mp/mpdpopm/src/messages.rs
deleted file mode 100644
index 171a246a..00000000
--- a/pkgs/by-name/mp/mpdpopm/src/messages.rs
+++ /dev/null
@@ -1,409 +0,0 @@
-// Copyright (C) 2020-2025 Michael herstine <sp1ff@pobox.com>
-//
-// This file is part of mpdpopm.
-//
-// mpdpopm is free software: you can redistribute it and/or modify it under the terms of the GNU
-// General Public License as published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// mpdpopm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
-// the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
-// Public License for more details.
-//
-// You should have received a copy of the GNU General Public License along with mpdpopm. If not,
-// see <http://www.gnu.org/licenses/>.
-
-//! # messages
-//!
-//! Process incoming messages to the [mpdpopm](https://github.com/sp1ff/mpdpopm) daemon.
-//!
-//! # Introduction
-//!
-//! The [mpdpopm](https://github.com/sp1ff/mpdpopm) daemon accepts commands over a dedicated
-//! [channel](https://www.musicpd.org/doc/html/protocol.html#client-to-client). It also provides for
-//! a generalized framework in which the [mpdpopm](https://github.com/sp1ff/mpdpopm) administrator
-//! can define new commands backed by arbitrary command execution server-side.
-//!
-//! # Commands
-//!
-//! The following commands are built-in:
-//!
-//! - set rating: `rate RATING( TRACK)?`
-//! - set playcount: `setpc PC( TRACK)?`
-//! - set lastplayed: `setlp TIMESTAMP( TRACK)?`
-//!
-//! There is no need to provide corresponding accessors since this functionality is already provided
-//! via "sticker get". Dedicated accessors could provide the same functionality with slightly more
-//! convenience since the sticker name would not have to be specified (as with "sticker get") & may
-//! be added at a later date.
-//!
-//! I'm expanding the MPD filter functionality to include attributes tracked by mpdpopm:
-//!
-//! - findadd replacement: `findadd FILTER [sort TYPE] [window START:END]`
-//! (cf. [here](https://www.musicpd.org/doc/html/protocol.html#the-music-database))
-//!
-//! - searchadd replacement: `searchadd FILTER [sort TYPE] [window START:END]`
-//! (cf. [here](https://www.musicpd.org/doc/html/protocol.html#the-music-database))
-//!
-//! Additional commands may be added through the
-//! [generalized commands](crate::commands#the-generalized-command-framework) feature.
-
-use crate::{
- clients::{Client, IdleClient, PlayerStatus},
- filters::ExpressionParser,
- filters_ast::{FilterStickerNames, evaluate},
-};
-
-use anyhow::{Context, Error, Result, anyhow, bail};
-use boolinator::Boolinator;
-use tracing::debug;
-
-use std::collections::VecDeque;
-
-/// Break `buf` up into individual tokens while removing MPD-style quoting.
-///
-/// When a client sends a command to [mpdpopm](crate), it will look like this on the wire:
-///
-/// ```text
-/// sendmessage ${CHANNEL} "some-command \"with space\" simple \"'with single' and \\\\\""
-/// ```
-///
-/// In other words, the MPD "sendmessage" command takes two parameters: the channel and the
-/// message. The recipient (i.e. us) is responsible for breaking up the message into its constituent
-/// parts (a command name & its arguments in our case).
-///
-/// The message will perforce be quoted according ot the MPD rules:
-///
-/// 1. an un-quoted token may contain any printable ASCII character except space, tab, ' & "
-///
-/// 2. to include spaces, tabs, '-s or "-s, the token must be enclosed in "-s, and any "-s or \\-s
-/// therein must be backslash escaped
-///
-/// When the messages is delivered to us, it has already been un-escaped; i.e. we will see the
-/// string:
-///
-/// ```text
-/// some-command "with space" simple "'with single' and \\"
-/// ```
-///
-/// This function will break that string up into individual tokens with one more level
-/// of escaping removed; i.e. it will return an iterator that will yield the four tokens:
-///
-/// 1. some-command
-/// 2. with space
-/// 3. simple
-/// 4. 'with single' and \\
-///
-/// [MPD](https://github.com/MusicPlayerDaemon/MPD) has a nice
-/// [implementation](https://github.com/MusicPlayerDaemon/MPD/blob/master/src/util/Tokenizer.cxx#L170)
-/// that modifies the string in place by copying subsequent characters on top of escape characters
-/// in the same buffer, inserting nulls in between the resulting tokens,and then working in terms of
-/// pointers to the resulting null-terminated strings.
-///
-/// Once I realized that I could split slices I saw how to implement an Iterator that do the same
-/// thing (an idiomatic interface to the tokenization backed by a zero-copy implementation). I was
-/// inspired by [My Favorite Rust Function
-/// Signature](<https://www.brandonsmith.ninja/blog/favorite-rust-function>).
-///
-/// NB. This method works in terms of a slice of [`u8`] because we can't index into Strings in
-/// Rust, and MPD deals only in terms of ASCII at any rate.
-pub fn tokenize(buf: &mut [u8]) -> impl Iterator<Item = Result<&[u8]>> {
- TokenIterator::new(buf)
-}
-
-struct TokenIterator<'a> {
- /// The slice on which we operate; modified in-place as we yield tokens
- slice: &'a mut [u8],
- /// Index into [`slice`] of the first non-whitespace character
- input: usize,
-}
-
-impl<'a> TokenIterator<'a> {
- pub fn new(slice: &'a mut [u8]) -> TokenIterator<'a> {
- let input = match slice.iter().position(|&x| x > 0x20) {
- Some(n) => n,
- None => slice.len(),
- };
- TokenIterator { slice, input }
- }
-}
-
-impl<'a> Iterator for TokenIterator<'a> {
- type Item = Result<&'a [u8]>;
-
- fn next(&mut self) -> Option<Self::Item> {
- let nslice = self.slice.len();
- if self.slice.is_empty() || self.input == nslice {
- None
- } else if '"' == self.slice[self.input] as char {
- // This is NextString in MPD: walk self.slice, un-escaping characters, until we find
- // a closing ". Note that we un-escape by moving characters forward in the slice.
- let mut inp = self.input + 1;
- let mut out = self.input;
- while self.slice[inp] as char != '"' {
- if '\\' == self.slice[inp] as char {
- inp += 1;
- if inp == nslice {
- return Some(Err(anyhow!("Trailing backslash")));
- }
- }
- self.slice[out] = self.slice[inp];
- out += 1;
- inp += 1;
- if inp == nslice {
- return Some(Err(anyhow!("No closing quote")));
- }
- }
- // The next token is in self.slice[self.input..out] and self.slice[inp] is "
- let tmp = std::mem::take(&mut self.slice);
- let (_, tmp) = tmp.split_at_mut(self.input);
- let (result, new_slice) = tmp.split_at_mut(out - self.input);
- self.slice = new_slice;
- // strip any leading whitespace
- self.input = inp - out + 1; // +1 to skip the closing "
- while self.input < self.slice.len() && self.slice[self.input] as char == ' ' {
- self.input += 1;
- }
- Some(Ok(result))
- } else {
- // This is NextUnquoted in MPD; walk self.slice, validating characters until the end
- // or the next whitespace
- let mut i = self.input;
- while i < nslice {
- if 0x20 >= self.slice[i] {
- break;
- }
- if self.slice[i] as char == '"' || self.slice[i] as char == '\'' {
- return Some(Err(anyhow!("Invalid char: `{}`", self.slice[i])));
- }
- i += 1;
- }
- // The next token is in self.slice[self.input..i] & self.slice[i] is either one-
- // past-the end or whitespace.
- let tmp = std::mem::take(&mut self.slice);
- let (_, tmp) = tmp.split_at_mut(self.input);
- let (result, new_slice) = tmp.split_at_mut(i - self.input);
- self.slice = new_slice;
- // strip any leading whitespace
- self.input = match self.slice.iter().position(|&x| x > 0x20) {
- Some(n) => n,
- None => self.slice.len(),
- };
- Some(Ok(result))
- }
- }
-}
-
-/// Collective state needed for processing messages, both built-in & generalized
-#[derive(Default)]
-pub struct MessageProcessor {}
-
-impl MessageProcessor {
- /// Whip up a new instance; other than cloning the iterators, should just hold references in the
- /// enclosing scope
- pub fn new() -> MessageProcessor {
- Self::default()
- }
-
- /// Read messages off the commands channel & dispatch 'em
- pub async fn check_messages<'a>(
- &self,
- client: &mut Client,
- idle_client: &mut IdleClient,
- state: PlayerStatus,
- command_chan: &str,
- stickers: &FilterStickerNames<'a>,
- ) -> Result<()> {
- let m = idle_client
- .get_messages()
- .await
- .context("Failed to `get_messages` from client")?;
-
- for (chan, msgs) in m {
- // Only supporting a single channel, ATM
- (chan == command_chan).ok_or_else(|| anyhow!("Unknown chanell: `{}`", chan))?;
- for msg in msgs {
- self.process(msg, client, &state, stickers).await?;
- }
- }
-
- Ok(())
- }
-
- /// Process a single command
- pub async fn process<'a>(
- &self,
- msg: String,
- client: &mut Client,
- state: &PlayerStatus,
- stickers: &FilterStickerNames<'a>,
- ) -> Result<()> {
- if let Some(stripped) = msg.strip_prefix("findadd ") {
- self.findadd(stripped.to_string(), client, stickers, state)
- .await
- } else if let Some(stripped) = msg.strip_prefix("searchadd ") {
- self.searchadd(stripped.to_string(), client, stickers, state)
- .await
- } else {
- unreachable!("Unkonwn command")
- }
- }
-
- /// Handle `findadd': "FILTER [sort TYPE] [window START:END]"
- async fn findadd<'a>(
- &self,
- msg: String,
- client: &mut Client,
- stickers: &FilterStickerNames<'a>,
- _state: &PlayerStatus,
- ) -> Result<()> {
- let mut buf = msg.into_bytes();
- let args: VecDeque<&str> = tokenize(&mut buf)
- .map(|r| match r {
- Ok(buf) => Ok(std::str::from_utf8(buf)
- .context("Failed to interpete `findadd` string as utf8")?),
- Err(err) => Err(err),
- })
- .collect::<Result<VecDeque<&str>>>()?;
-
- debug!("findadd arguments: {:#?}", args);
-
- // there should be 1, 3 or 5 arguments. `sort' & `window' are not supported, yet.
-
- // ExpressionParser's not terribly ergonomic: it returns a ParesError<L, T, E>; T is the
- // offending token, which has the same lifetime as our input, which makes it tough to
- // capture. Nor is there a convenient way in which to treat all variants other than the
- // Error Trait.
- let ast = match ExpressionParser::new().parse(args[0]) {
- Ok(ast) => ast,
- Err(err) => {
- bail!("Failed to parse filter: `{}`", err)
- }
- };
-
- debug!("ast: {:#?}", ast);
-
- let mut results = Vec::new();
- for song in evaluate(&ast, true, client, stickers)
- .await
- .context("Failed to evaluate filter")?
- {
- results.push(client.add(&song).await);
- }
- match results
- .into_iter()
- .collect::<std::result::Result<Vec<()>, Error>>()
- {
- Ok(_) => Ok(()),
- Err(err) => Err(err),
- }
- }
-
- /// Handle `searchadd': "FILTER [sort TYPE] [window START:END]"
- async fn searchadd<'a>(
- &self,
- msg: String,
- client: &mut Client,
- stickers: &FilterStickerNames<'a>,
- _state: &PlayerStatus,
- ) -> Result<()> {
- // Tokenize the message
- let mut buf = msg.into_bytes();
- let args: VecDeque<&str> = tokenize(&mut buf)
- .map(|r| match r {
- Ok(buf) => Ok(std::str::from_utf8(buf)
- .context("Failed to interpete `searchadd` string as utf8")?),
- Err(err) => Err(err),
- })
- .collect::<Result<VecDeque<_>>>()?;
-
- debug!("searchadd arguments: {:#?}", args);
-
- // there should be 1, 3 or 5 arguments. `sort' & `window' are not supported, yet.
-
- // ExpressionParser's not terribly ergonomic: it returns a ParesError<L, T, E>; T is the
- // offending token, which has the same lifetime as our input, which makes it tough to
- // capture. Nor is there a convenient way in which to treat all variants other than the
- // Error Trait.
- let ast = match ExpressionParser::new().parse(args[0]) {
- Ok(ast) => ast,
- Err(err) => {
- bail!("Failed to parse filter: `{err}`")
- }
- };
-
- debug!("ast: {:#?}", ast);
-
- let mut results = Vec::new();
- for song in evaluate(&ast, false, client, stickers)
- .await
- .context("Failed to evaluate ast")?
- {
- results.push(client.add(&song).await);
- }
- match results
- .into_iter()
- .collect::<std::result::Result<Vec<()>, Error>>()
- {
- Ok(_) => Ok(()),
- Err(err) => Err(err),
- }
- }
-}
-
-#[cfg(test)]
-mod tokenize_tests {
- use super::Result;
- use super::tokenize;
-
- #[test]
- fn tokenize_smoke() {
- let mut buf1 = String::from("some-command").into_bytes();
- let x1: Vec<&[u8]> = tokenize(&mut buf1).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x1[0], b"some-command");
-
- let mut buf2 = String::from("a b").into_bytes();
- let x2: Vec<&[u8]> = tokenize(&mut buf2).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x2[0], b"a");
- assert_eq!(x2[1], b"b");
-
- let mut buf3 = String::from("a \"b c\"").into_bytes();
- let x3: Vec<&[u8]> = tokenize(&mut buf3).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x3[0], b"a");
- assert_eq!(x3[1], b"b c");
-
- let mut buf4 = String::from("a \"b c\" d").into_bytes();
- let x4: Vec<&[u8]> = tokenize(&mut buf4).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x4[0], b"a");
- assert_eq!(x4[1], b"b c");
- assert_eq!(x4[2], b"d");
-
- let mut buf5 = String::from("simple-command \"with space\" \"with '\"").into_bytes();
- let x5: Vec<&[u8]> = tokenize(&mut buf5).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x5[0], b"simple-command");
- assert_eq!(x5[1], b"with space");
- assert_eq!(x5[2], b"with '");
-
- let mut buf6 = String::from("cmd \"with\\\\slash and space\"").into_bytes();
- let x6: Vec<&[u8]> = tokenize(&mut buf6).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x6[0], b"cmd");
- assert_eq!(x6[1], b"with\\slash and space");
-
- let mut buf7 = String::from(" cmd \"with\\\\slash and space\" ").into_bytes();
- let x7: Vec<&[u8]> = tokenize(&mut buf7).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(x7[0], b"cmd");
- assert_eq!(x7[1], b"with\\slash and space");
- }
-
- #[test]
- fn tokenize_filter() {
- let mut buf1 = String::from(r#""(artist =~ \"foo\\\\bar\\\"\")""#).into_bytes();
- let x1: Vec<&[u8]> = tokenize(&mut buf1).collect::<Result<Vec<&[u8]>>>().unwrap();
- assert_eq!(1, x1.len());
- eprintln!("x1[0] is ``{}''", std::str::from_utf8(x1[0]).unwrap());
- assert_eq!(
- std::str::from_utf8(x1[0]).unwrap(),
- r#"(artist =~ "foo\\bar\"")"#
- );
- }
-}
diff --git a/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs
new file mode 100644
index 00000000..7db75672
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs
@@ -0,0 +1,140 @@
+use anyhow::{Context, Result, anyhow, bail, ensure};
+use clap::{Parser, Subcommand};
+use shlex::Shlex;
+use tracing::info;
+
+use crate::{
+ clients::{Client, IdleClient},
+ config::Mode,
+ dj::{Dj, algorithms::Discovery},
+};
+
+pub const COMMAND_CHANNEL: &str = "unwoundstack.com:commands";
+
+#[derive(Parser)]
+struct Commands {
+ #[command(subcommand)]
+ command: SubCommand,
+}
+
+#[derive(Parser)]
+enum SubCommand {
+ Dj {
+ #[command(subcommand)]
+ command: DjCommand,
+ },
+}
+
+#[derive(Subcommand)]
+enum DjCommand {
+ Start {
+ /// The chance to select a "positive" track
+ #[arg(long)]
+ positive_chance: f64,
+
+ /// The chance to select a "neutral" track
+ #[arg(long)]
+ neutral_chance: f64,
+
+ /// The chance to select a "negative" track
+ #[arg(long)]
+ negative_chance: f64,
+ },
+ Stop {},
+}
+
+pub(crate) struct MessageQueue {
+ dj: Option<Dj<Discovery>>,
+}
+
+impl MessageQueue {
+ pub(crate) fn new(mode: Mode) -> Self {
+ match mode {
+ Mode::Normal => Self { dj: None },
+ Mode::Dj => {
+ info!("Dj mode started on launch, as specified in config file");
+
+ Self {
+ dj: Some(Dj::new(Discovery::new(0.65, 0.5, 0.2))),
+ }
+ }
+ }
+ }
+
+ pub(crate) async fn advance_dj(&mut self, client: &mut Client) -> Result<()> {
+ if let Some(dj) = self.dj.as_mut() {
+ dj.add_track(client).await?;
+ }
+
+ Ok(())
+ }
+
+ /// Read messages off the commands channel & dispatch 'em
+ pub(crate) async fn check_messages(
+ &mut self,
+ client: &mut Client,
+ idle_client: &mut IdleClient,
+ ) -> Result<()> {
+ let m = idle_client
+ .get_messages()
+ .await
+ .context("Failed to `get_messages` from client")?;
+
+ for (chan, msgs) in m {
+ ensure!(chan == COMMAND_CHANNEL, "Unknown channel: `{}`", chan);
+
+ for msg in msgs {
+ self.process(client, msg).await?;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Process a single command
+ pub(crate) async fn process(&mut self, client: &mut Client, msg: String) -> Result<()> {
+ let split = {
+ let mut shl = Shlex::new(&msg);
+ let res: Vec<_> = shl.by_ref().collect();
+
+ if shl.had_error {
+ bail!("Failed to parse command '{msg}'")
+ }
+
+ assert_eq!(shl.line_no, 1, "A unexpected newline appeared");
+ assert!(!res.is_empty());
+
+ let mut base = vec!["base".to_owned()];
+ base.extend(res);
+ base
+ };
+
+ let args = Commands::parse_from(split);
+
+ match args.command {
+ SubCommand::Dj { command } => match command {
+ DjCommand::Start {
+ positive_chance,
+ neutral_chance,
+ negative_chance,
+ } => {
+ info!("Dj started");
+ self.dj = Some(Dj::new(Discovery::new(
+ positive_chance,
+ neutral_chance,
+ negative_chance,
+ )));
+ self.advance_dj(client).await?;
+ }
+ DjCommand::Stop {} => {
+ self.dj
+ .take()
+ .ok_or_else(|| anyhow!("Tried to disable already disabled dj mode"))?;
+ info!("Dj stopped");
+ }
+ },
+ }
+
+ Ok(())
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/playcounts.rs b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
index 7d646b4c..eac71948 100644
--- a/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
@@ -26,7 +26,7 @@
//!
use crate::clients::{Client, PlayerStatus};
-use crate::storage::{last_played, play_count, skipped};
+use crate::storage::{last_played, play_count, skip_count};
use anyhow::{Context, Error, Result, anyhow};
use tracing::{debug, info};
@@ -71,13 +71,16 @@ impl PlayState {
/// Poll the server-- update our status; maybe increment the current track's play count; the
/// caller must arrange to have this method invoked periodically to keep our state fresh
- pub async fn update(&mut self, client: &mut Client) -> Result<()> {
+ ///
+ /// Returns whether a song finished between the last call and this one.
+ /// That can be used to add a new song to the queue.
+ pub async fn update(&mut self, client: &mut Client) -> Result<bool> {
let new_stat = client
.status()
.await
.context("Failed to get client status")?;
- match (&self.last_server_stat, &new_stat) {
+ let previous_song_finished = match (&self.last_server_stat, &new_stat) {
(PlayerStatus::Play(last), PlayerStatus::Play(curr))
| (PlayerStatus::Pause(last), PlayerStatus::Play(curr))
| (PlayerStatus::Play(last), PlayerStatus::Pause(curr))
@@ -93,33 +96,59 @@ impl PlayState {
}
self.have_incr_play_count = false;
+
+ // We are now playing something else, as such the previous one must have
+ // finished or was skipped.
+ true
} else if last.elapsed > curr.elapsed
&& self.have_incr_play_count
&& curr.elapsed / curr.duration <= 0.1
{
debug!("Re-play-- resetting PC incremented flag.");
self.have_incr_play_count = false;
+
+ // We are still playing the same song, just skipped at the start again.
+ // This means that we don't need a new one.
+ false
+ } else {
+ // We are still playing the same song, so nothing changed
+ false
}
}
(PlayerStatus::Stopped, PlayerStatus::Play(_))
- | (PlayerStatus::Stopped, PlayerStatus::Pause(_))
- | (PlayerStatus::Pause(_), PlayerStatus::Stopped)
+ | (PlayerStatus::Stopped, PlayerStatus::Pause(_)) => {
+ self.have_incr_play_count = false;
+
+ // We didn't play anything before and now we play something. This means that we
+ // obviously have something to play and thus don't need to add another song.
+ false
+ }
+ (PlayerStatus::Pause(_), PlayerStatus::Stopped)
| (PlayerStatus::Play(_), PlayerStatus::Stopped) => {
self.have_incr_play_count = false;
+
+ // We played a song before and now we stopped, maybe because we ran out of songs to
+ // play. So we need to add another one.
+ true
}
- (PlayerStatus::Stopped, PlayerStatus::Stopped) => (),
- }
+ (PlayerStatus::Stopped, PlayerStatus::Stopped) => {
+ // We did not play before and we are still not playing, as such nothing really
+ // changed.
+ false
+ }
+ };
match &new_stat {
PlayerStatus::Play(curr) => {
let pct = curr.played_pct();
debug!("Updating status: {:.3}% complete.", 100.0 * pct);
+
if !self.have_incr_play_count && pct >= self.played_thresh {
info!(
- "Increment play count for '{}' (songid: {}) at {} played.",
+ "Increment play count for '{}' (songid: {}) at {:.2}% played.",
curr.file.display(),
curr.songid,
- curr.elapsed / curr.duration
+ (curr.elapsed / curr.duration) * 100.0
);
let file = curr.file.to_str().ok_or_else(|| {
@@ -143,6 +172,9 @@ impl PlayState {
play_count::set(client, file, curr_pc + 1).await?;
} else if self.last_song_was_skipped {
+ // TODO(@bpeetz): This should also record _when_ the skip was (e.g. a
+ // skip at 80% is not as important as one at 2%) <2026-09-05>
+
self.last_song_was_skipped = false;
let last = self
.last_server_stat
@@ -150,25 +182,25 @@ impl PlayState {
.expect("To exist, as it was skipped");
info!(
- "Marking '{}' (songid: {}) as skipped at {}.",
+ "Marking '{}' (songid: {}) as skipped at {:.2}%.",
last.file.display(),
last.songid,
- last.elapsed / last.duration
+ (last.elapsed / last.duration) * 100.0
);
let file = last.file.to_str().ok_or_else(|| {
anyhow!("Failed to parse path as utf8: `{}`", last.file.display())
})?;
- let skip_count = skipped::get(client, file).await?.unwrap_or_default();
- skipped::set(client, file, skip_count + 1).await?;
+ let skip_count = skip_count::get(client, file).await?.unwrap_or_default();
+ skip_count::set(client, file, skip_count + 1).await?;
}
}
PlayerStatus::Pause(_) | PlayerStatus::Stopped => (),
};
self.last_server_stat = new_stat;
- Ok(()) // No need to update the DB
+ Ok(previous_song_finished)
}
}
@@ -308,6 +340,6 @@ OK
assert!(check);
ps.update(&mut cli).await.unwrap();
- ps.update(&mut cli).await.unwrap()
+ ps.update(&mut cli).await.unwrap();
}
}
diff --git a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs
index 24d8dcb5..a6f20d5b 100644
--- a/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs
+++ b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs
@@ -1,4 +1,4 @@
-use anyhow::{Error, Result};
+use anyhow::Result;
pub mod play_count {
use anyhow::Context;
@@ -60,14 +60,14 @@ pub mod play_count {
}
}
-pub mod skipped {
+pub mod skip_count {
use anyhow::Context;
use crate::clients::Client;
use super::Result;
- const STICKER: &str = "unwoundstack.com:skipped_count";
+ pub(crate) const STICKER: &str = "unwoundstack.com:skipped_count";
/// Retrieve the skip count for a track
pub async fn get(client: &mut Client, file: &str) -> Result<Option<usize>> {
@@ -117,7 +117,7 @@ pub mod last_played {
}
}
-pub mod rating_count {
+pub mod rating {
use anyhow::Context;
use crate::clients::Client;
diff --git a/pkgs/by-name/mp/mpdpopm/update.sh b/pkgs/by-name/mp/mpdpopm/update.sh
index cf04b837..5aedf3b3 100755
--- a/pkgs/by-name/mp/mpdpopm/update.sh
+++ b/pkgs/by-name/mp/mpdpopm/update.sh
@@ -10,7 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/agpl.txt>.
-nix flake update
-
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive